pos_export_bc/wizard/pos_export_bc_wizard.py
2026-03-24 15:15:58 +07:00

196 lines
9.4 KiB
Python

import base64
import io
from datetime import datetime
import pytz
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import odoo.tools
try:
import xlsxwriter
except ImportError:
xlsxwriter = None
class PosExportBcWizard(models.TransientModel):
_name = 'pos.export.bc.wizard'
_description = 'POS Export BC Format Wizard'
start_date = fields.Date(string="Start Date", required=True, default=fields.Date.context_today)
end_date = fields.Date(string="End Date", required=True, default=fields.Date.context_today)
def action_export_bc(self):
self.ensure_one()
if not xlsxwriter:
raise UserError(_("The Python library 'xlsxwriter' is required. Please install it."))
if self.start_date > self.end_date:
raise UserError(_("Start Date must be earlier or equal to End Date."))
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
# Define formats
header_format = workbook.add_format({
'bold': True, 'align': 'center', 'valign': 'vcenter', 'border': 1
})
title_format = workbook.add_format({
'bold': True, 'align': 'center', 'valign': 'vcenter', 'font_color': 'red', 'size': 14
})
subtitle_format = workbook.add_format({
'bold': True, 'align': 'center', 'valign': 'vcenter', 'font_color': 'black', 'size': 12
})
date_format = workbook.add_format({'num_format': 'dd-mm-yyyy'})
date_time_format = workbook.add_format({'num_format': 'dd-mm-yyyy hh:mm:ss'})
number_format = workbook.add_format({'num_format': '#,##0.00'})
headers = [
"No", "Date", "Outlet", "Table/Customer", "Invoice", "Category", "SKU", "Product", "Quantity",
"Price Type", "Price", "Price Cut", "Subtotal", "Discount", "Tax", "Service", "Takeaway Charge",
"Packaging Fee", "Rounding", "Charge", "Paid", "Pax", "Paid At", "Return", "Refund", "Payment",
"Note", "Dinein", "User", "Promo", "Order from", "Nama Penerima", "Alamat Penerima", "Link Maps"
]
# Datetime timezone conversion
# We need these in UTC for the domain search
user_tz_str = self.env.user.tz or 'UTC'
user_tz = pytz.timezone(user_tz_str)
start_datetime = datetime.combine(self.start_date, datetime.min.time())
end_datetime = datetime.combine(self.end_date, datetime.max.time())
start_utc = user_tz.localize(start_datetime).astimezone(pytz.UTC).replace(tzinfo=None)
end_utc = user_tz.localize(end_datetime).astimezone(pytz.UTC).replace(tzinfo=None)
def write_sheet(sheet_name, domain):
sheet = workbook.add_worksheet(sheet_name)
# Title
sheet.merge_range('A1:AF1', 'MIE MAPAN', title_format)
sheet.merge_range('A2:AF2', 'INVOICES', subtitle_format)
# Period
start_dt_str = start_datetime.strftime('%d-%m-%Y 00:00')
end_dt_str = end_datetime.strftime('%d-%m-%Y 23:59')
sheet.write('A3', 'Period')
sheet.write('B3', ':')
sheet.write('C3', f"{start_dt_str} - {end_dt_str}")
# Headers
for col_num, header in enumerate(headers):
sheet.write(4, col_num, header, header_format)
row_num = 5
orders = self.env['pos.order'].search(domain, order='date_order asc')
order_no = 1
for order in orders:
local_date = order.date_order.replace(tzinfo=pytz.UTC).astimezone(user_tz) if order.date_order else False
outlet = order.config_id.name or ''
table = order.table_id.display_name if 'table_id' in order._fields and order.table_id else ''
customer = order.partner_id.name or ''
table_customer = table or customer
invoice = order.pos_reference or order.name
subtotal = sum(l.price_subtotal for l in order.lines)
discount_order = 0.0 # Standard Odoo doesn't have an order level discount easily isolated from line discounts
tax = order.amount_tax
charge = order.amount_total
# In Odoo, payment amount can be negative for change.
# To get the total amount tendered before change, we sum only the positive payments.
# The change/return is typically stored in order.amount_return
paid = sum(p.amount for p in order.payment_ids if p.amount > 0)
pax = order.customer_count if 'customer_count' in order._fields else 1
return_amt = order.amount_return if 'amount_return' in order._fields else (paid - charge if paid > charge else 0)
payment_methods = ', '.join(order.payment_ids.mapped('payment_method_id.name'))
note = order.note if 'note' in order._fields else ''
# dinein = 'dinein' if table else 'takeaway'
preset = order.preset_id.name
if "dine" in preset.lower():
dinein = "dinein"
else:
dinein = "takeaway"
user = order.user_id.name or ''
is_first_line = True
for line in order.lines:
sheet.write(row_num, 0, order_no)
if local_date:
date_str = local_date.strftime('%d-%m-%Y %H:%M:%S')
sheet.write_string(row_num, 1, date_str)
sheet.write(row_num, 2, outlet)
sheet.write(row_num, 3, table_customer)
sheet.write(row_num, 4, invoice)
category = line.product_id.pos_categ_ids[0].name if line.product_id.pos_categ_ids else ''
sku = line.product_id.x_studio_popcorn_sku if 'x_studio_popcorn_sku' in line.product_id._fields and line.product_id.x_studio_popcorn_sku else ''
product_name = line.product_id.name or ''
qty = line.qty
price_type = order.pricelist_id.name or 'DEFAULT'
price = line.price_unit
price_cut = (line.price_unit * line.discount / 100) if line.discount else 0.0
sheet.write(row_num, 5, category)
sheet.write(row_num, 6, sku)
sheet.write(row_num, 7, product_name)
sheet.write(row_num, 8, qty)
sheet.write(row_num, 9, price_type)
sheet.write(row_num, 10, price, number_format)
sheet.write(row_num, 11, price_cut, number_format)
if is_first_line:
sheet.write(row_num, 12, subtotal, number_format)
sheet.write(row_num, 13, discount_order, number_format)
sheet.write(row_num, 14, tax, number_format)
sheet.write(row_num, 15, 0, number_format) # Service
sheet.write(row_num, 16, 0, number_format) # Takeaway Charge
sheet.write(row_num, 17, 0, number_format) # Packaging Fee
sheet.write(row_num, 18, 0, number_format) # Rounding
sheet.write(row_num, 19, charge, number_format)
sheet.write(row_num, 20, paid, number_format)
sheet.write(row_num, 21, pax)
if local_date:
date_str = local_date.strftime('%d-%m-%Y %H:%M:%S')
sheet.write_string(row_num, 22, date_str)
sheet.write(row_num, 23, return_amt, number_format)
sheet.write(row_num, 24, 0, number_format) # Refund
sheet.write(row_num, 25, payment_methods)
sheet.write(row_num, 26, note)
sheet.write(row_num, 27, dinein)
sheet.write(row_num, 28, user)
sheet.write(row_num, 29, "") # Promo
sheet.write(row_num, 30, "cashier") # Order from
sheet.write(row_num, 31, "")
sheet.write(row_num, 32, "")
sheet.write(row_num, 33, "")
is_first_line = False
row_num += 1
order_no += 1
domain_base = [('date_order', '>=', start_utc), ('date_order', '<=', end_utc)]
write_sheet('Invoice', domain_base + [('amount_total', '>=', 0)])
write_sheet('Refund', domain_base + [('amount_total', '<', 0)])
workbook.close()
output.seek(0)
# Save as an ir.attachment and return action to download
attachment = self.env['ir.attachment'].create({
'name': f"POS_BC_{self.start_date}_to_{self.end_date}.xlsx",
'type': 'binary',
'datas': base64.b64encode(output.read()),
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
return {
'type': 'ir.actions.act_url',
'url': f'/web/content/{attachment.id}?download=true',
'target': 'self',
}