From 18035b2cf225c034ade64e39bb2f55ee89db08f0 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Wed, 16 Oct 2024 16:27:52 +0200 Subject: [PATCH] [IMP] fiscal_epos_print: Print courtesy receipt --- fiscal_epos_print/models/point_of_sale.py | 4 + .../static/src/js/epson_epos_print.js | 252 +++++++++++++++++- fiscal_epos_print/static/src/js/models.js | 35 +++ .../static/src/js/pos_order_mgmt.js | 4 +- fiscal_epos_print/static/src/js/screens.js | 6 +- fiscal_epos_print/views/point_of_sale.xml | 10 + 6 files changed, 305 insertions(+), 6 deletions(-) diff --git a/fiscal_epos_print/models/point_of_sale.py b/fiscal_epos_print/models/point_of_sale.py index 4a9df0dc700c..d4b8a6ea3f39 100644 --- a/fiscal_epos_print/models/point_of_sale.py +++ b/fiscal_epos_print/models/point_of_sale.py @@ -21,6 +21,10 @@ class PosConfig(models.Model): fiscal_printer_serial = fields.Char(string='Fiscal Printer Serial') fiscal_cashdrawer = fields.Boolean(string='Fiscal Printer Open CashDrawer') + epos_print_courtesy_receipt = fields.Boolean( + string="Print courtesy receipt", + help="Print a courtesy receipt when the order has to be invoiced", + ) @api.constrains('printer_ip', 'iface_reprint_done_order') def _check_fiscal_epos_reprint(self): diff --git a/fiscal_epos_print/static/src/js/epson_epos_print.js b/fiscal_epos_print/static/src/js/epson_epos_print.js index e4430826a9d6..f42cf5e52865 100644 --- a/fiscal_epos_print/static/src/js/epson_epos_print.js +++ b/fiscal_epos_print/static/src/js/epson_epos_print.js @@ -8,6 +8,33 @@ odoo.define("fiscal_epos_print.epson_epos_print", function (require) { var _t = core._t; var round_pr = utils.round_precision; + /** + * Return true if the fiscal printer will print a fiscal receipt for `order`. + */ + function eposWillPrintReceipt(order) { + return order.pos.config.printer_ip && !order.is_to_invoice(); + }; + /** + * Return true if the fiscal printer will print a courtesy receipt for `order`. + */ + function eposWillPrintCourtesyReceipt(order) { + return order.pos.config.printer_ip && order.is_to_invoice() && order.pos.config.epos_print_courtesy_receipt; + }; + /** + * Send `receipt` to the fiscal `printer`, + * this might print a receipt (fiscal or not) for `receipt`. + */ + function eposPrint(printer, receipt) { + var order = printer.order; + var result; + if (eposWillPrintReceipt(order)) { + result = printer.printFiscalReceipt(receipt); + } else if (eposWillPrintCourtesyReceipt(order)) { + result = printer.printNonFiscalReceipt(receipt); + } + return result + } + function addPadding(str, padding=4) { var pad = new Array(padding).fill(0).join('') + str; return pad.substr(pad.length - padding, padding); @@ -436,6 +463,226 @@ odoo.define("fiscal_epos_print.epson_epos_print", function (require) { console.log(xml); }, + addNonFiscalNode: function(xml, data, {font="1", operator="1"}) { + var node = xml.createElement("printNormal"); + node.setAttribute("data", data); + node.setAttribute("font", font); + node.setAttribute("operator", operator); + xml.documentElement.appendChild(node); + }, + + formatAmountCurrency: function(amount, currency) { + if (currency.position === "after") { + return amount + " " + (currency.symbol || ""); + } else { + return (currency.symbol || "") + " " + amount; + } + }, + + addNonFiscalEmptyLine: function(xml) { + var node = xml.createElement("printNormal"); + node.setAttribute("data", ""); + node.setAttribute("font", "1"); + node.setAttribute("operator", "1"); + xml.documentElement.appendChild(node); + }, + + addXMLNonFiscalHeader: function(xml, receipt) { + var beginNonFiscal = xml.createElement("beginNonFiscal"); + beginNonFiscal.setAttribute("operator", receipt.operator || "1"); + xml.documentElement.appendChild(beginNonFiscal); + + this.addNonFiscalNode( + xml, + _t(`Courtesy receipt`), + { + font: "3", + operator: receipt.operator, + }, + ); + + var invoice = receipt.epos_invoice; + this.addNonFiscalNode( + xml, + _t(`Copy of invoice ${invoice.name} of ${invoice.date}`), + { + operator: receipt.operator, + }, + ); + }, + + addXMLNonFiscalClient: function(xml, receipt) { + var client = this.order.get_client(); + + var clientName = client.name; + this.addNonFiscalNode( + xml, + _t(`Customer: ${clientName}`), + { + operator: receipt.operator, + }, + ); + + var clientAddress = client.address; + if (clientAddress) { + this.addNonFiscalNode( + xml, + _t(`Address: ${clientAddress}`), + { + operator: receipt.operator, + }, + ); + } + + var clientVAT = client.vat; + if (clientVAT) { + this.addNonFiscalNode( + xml, + _t(`VAT: ${clientVAT}`), + { + operator: receipt.operator, + }, + ); + } + }, + + addXMLNonFiscalOrderLine: function(xml, line, receipt) { + this.addNonFiscalNode( + xml, + line.product_name, + { + operator: receipt.operator, + }, + ); + var currency = receipt.currency; + var unit_amount = this.formatAmountCurrency(line.full_price, currency); + var total_amount = this.formatAmountCurrency(line.price_display, currency); + this.addNonFiscalNode( + xml, + _t(`${line.quantity} x ${unit_amount} = ${total_amount}`), + { + operator: receipt.operator, + }, + ); + var tax_amount = this.formatAmountCurrency(line.tax, currency); + this.addNonFiscalNode( + xml, + _t(`Tax code ${line.tax_department.code}: ${tax_amount}`), + { + operator: receipt.operator, + }, + ); + }, + + addXMLNonFiscalTaxDetail: function(xml, tax_detail, receipt) { + this.addNonFiscalNode( + xml, + _t(`Tax code ${tax_detail.tax.fpdeptax}: ${tax_detail.name}`), + { + operator: receipt.operator, + }, + ); + this.addNonFiscalNode( + xml, + this.formatAmountCurrency(tax_detail.amount, receipt.currency), + { + operator: receipt.operator, + }, + ); + }, + + addXMLNonFiscalTotal: function(xml, receipt) { + var invoice = this.order.epos_invoice; + this.addNonFiscalNode( + xml, + _t(`Total`), + { + operator: receipt.operator, + }, + ); + var currency = receipt.currency; + this.addNonFiscalNode( + xml, + _t(`Base: ${this.formatAmountCurrency(invoice.amount_untaxed, currency)}`), + { + operator: receipt.operator, + }, + ); + this.addNonFiscalNode( + xml, + _t(`Tax: ${this.formatAmountCurrency(invoice.amount_tax, currency)}`), + { + operator: receipt.operator, + }, + ); + this.addNonFiscalNode( + xml, + _t(`Total: ${this.formatAmountCurrency(invoice.amount_total, currency)}`), + { + operator: receipt.operator, + }, + ); + }, + + addXMLNonFiscalFooter: function(xml, receipt) { + this.addNonFiscalNode( + xml, + _t("Courtesy copy not valid for tax purposes. " + + "The original invoice has been sent to the SdI " + + "and can be consulted on the Revenue Agency website, " + + "in the Reserved Area."), + { + operator: receipt.operator, + }, + ); + + var endNonFiscal = xml.createElement("endNonFiscal"); + endNonFiscal.setAttribute("operator", receipt.operator || "1"); + xml.documentElement.appendChild(endNonFiscal); + }, + + prepareXMLNonFiscalReceipt: function(receipt) { + var xml = document.implementation.createDocument(null, "printerNonFiscal"); + + this.addXMLNonFiscalHeader(xml, receipt); + this.addNonFiscalEmptyLine(xml); + + this.addXMLNonFiscalClient(xml, receipt); + this.addNonFiscalEmptyLine(xml); + + for (let line of receipt.orderlines) { + this.addXMLNonFiscalOrderLine(xml, line, receipt); + this.addNonFiscalEmptyLine(xml); + } + this.addNonFiscalEmptyLine(xml); + + this.addNonFiscalNode( + xml, + _t("Taxes summary"), + { + operator: receipt.operator, + }, + ); + for (let tax_detail of receipt.tax_details) { + this.addXMLNonFiscalTaxDetail(xml, tax_detail, receipt); + this.addNonFiscalEmptyLine(xml); + } + this.addNonFiscalEmptyLine(xml); + + this.addXMLNonFiscalTotal(xml, receipt); + this.addNonFiscalEmptyLine(xml); + + this.addXMLNonFiscalFooter(xml, receipt); + return xml; + }, + + printNonFiscalReceipt: function(receipt) { + var xml = this.prepareXMLNonFiscalReceipt(receipt); + var xmlString = new XMLSerializer().serializeToString(xml); + this.fiscalPrinter.send(this.url, xmlString); + console.log(xmlString); + }, + printFiscalReport: function() { var xml = ''; xml += ''; @@ -474,7 +721,10 @@ odoo.define("fiscal_epos_print.epson_epos_print", function (require) { }); return { - eposDriver: eposDriver + eposDriver: eposDriver, + eposWillPrintReceipt: eposWillPrintReceipt, + eposWillPrintCourtesyReceipt: eposWillPrintCourtesyReceipt, + eposPrint: eposPrint, } }); diff --git a/fiscal_epos_print/static/src/js/models.js b/fiscal_epos_print/static/src/js/models.js index c336dab38205..ff0956396b33 100644 --- a/fiscal_epos_print/static/src/js/models.js +++ b/fiscal_epos_print/static/src/js/models.js @@ -7,6 +7,7 @@ odoo.define('fiscal_epos_print.models', function (require) { var _t = core._t; var round_pr = utils.round_precision; var OrderSuper = models.Order; + var epson_epos_print = require('fiscal_epos_print.epson_epos_print'); models.load_fields("account.journal", ["fiscalprinter_payment_type", "fiscalprinter_payment_index"]); @@ -92,6 +93,7 @@ odoo.define('fiscal_epos_print.models', function (require) { receipt.fiscal_z_rep_number = this.fiscal_z_rep_number; receipt.fiscal_printer_serial = this.fiscal_printer_serial; receipt.fiscal_printer_debug_info = this.fiscal_printer_debug_info; + receipt.epos_invoice = this.epos_invoice; return receipt }, @@ -204,6 +206,39 @@ odoo.define('fiscal_epos_print.models', function (require) { tax_model.fields.push('fpdeptax'); return _super_posmodel.initialize.call(this, session, attributes); }, + push_and_invoice_order: function(order) { + var self = this; + var invoiced = _super_posmodel.push_and_invoice_order.apply(this, arguments); + invoiced.done(function(){ + // Get invoice from PoS `order` + self.chrome._rpc({ + model: "pos.order", + method: "search_read", + args: [[["pos_reference", "=", order.name]], ["invoice_id",]], + }).then(function (server_orders) { + if (server_orders.length) { + var invoice_id = server_orders[0].invoice_id[0]; + // Read invoice fields + self.chrome._rpc({ + model: "account.invoice", + method: "read", + args: [invoice_id], + }).then(function (server_invoices) { + if (server_invoices.length) { + // Update `order` and print courtesy receipt + order.epos_invoice = server_invoices[0]; + var printer_options = order.getPrinterOptions(); + printer_options.order = order; + var receipt = order.export_for_printing(); + var fp90 = new epson_epos_print.eposDriver(printer_options, order.pos.gui.current_screen); + return epson_epos_print.eposPrint(fp90, receipt); + } + }); + } + }); + }); + return invoiced; + }, }); }); diff --git a/fiscal_epos_print/static/src/js/pos_order_mgmt.js b/fiscal_epos_print/static/src/js/pos_order_mgmt.js index 3cc7965b6eee..ea33ff73674f 100644 --- a/fiscal_epos_print/static/src/js/pos_order_mgmt.js +++ b/fiscal_epos_print/static/src/js/pos_order_mgmt.js @@ -31,10 +31,10 @@ odoo.define("fiscal_epos_print.pos_order_mgmt", function (require) { // copiato da screens.PaymentScreenWidget sendToFP90Printer: function(receipt, printer_options) { var fp90 = new eposDriver(printer_options, this); - fp90.printFiscalReceipt(receipt); + return epson_epos_print.eposPrint(fp90, receipt); }, action_print: function (order_data, order) { - if (this.pos.config.printer_ip) { + if (epson_epos_print.eposWillPrintReceipt(currentOrder)) { if (order_data.fiscal_receipt_number) { this.pos.gui.show_popup('error', { 'title': _t('Order already printed'), diff --git a/fiscal_epos_print/static/src/js/screens.js b/fiscal_epos_print/static/src/js/screens.js index 2edd9370d864..18d05fdd117e 100644 --- a/fiscal_epos_print/static/src/js/screens.js +++ b/fiscal_epos_print/static/src/js/screens.js @@ -26,7 +26,7 @@ odoo.define("fiscal_epos_print.screens", function (require) { sendToFP90Printer: function(receipt, printer_options) { var fp90 = new eposDriver(printer_options, this); - fp90.printFiscalReceipt(receipt); + return epson_epos_print.eposPrint(fp90, receipt); }, render_receipt: function() { @@ -59,7 +59,7 @@ odoo.define("fiscal_epos_print.screens", function (require) { }, sendToFP90Printer: function(receipt, printer_options) { var fp90 = new eposDriver(printer_options, this); - fp90.printFiscalReceipt(receipt); + return epson_epos_print.eposPrint(fp90, receipt); }, finalize_validation: function() { // we need to get currentOrder before calling the _super() @@ -67,7 +67,7 @@ odoo.define("fiscal_epos_print.screens", function (require) { // the receipt preview var currentOrder = this.pos.get('selectedOrder'); this._super.apply(this, arguments); - if (this.pos.config.printer_ip && !currentOrder.is_to_invoice()) { + if (epson_epos_print.eposWillPrintReceipt(currentOrder)) { this.chrome.loading_show(); this.chrome.loading_message(_t('Connecting to the fiscal printer')); var printer_options = currentOrder.getPrinterOptions(); diff --git a/fiscal_epos_print/views/point_of_sale.xml b/fiscal_epos_print/views/point_of_sale.xml index 4176a81d8509..f45228be9c08 100644 --- a/fiscal_epos_print/views/point_of_sale.xml +++ b/fiscal_epos_print/views/point_of_sale.xml @@ -42,6 +42,16 @@
+
+
+ +
+
+