196 lines
9.4 KiB
Python
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',
|
|
}
|