From 7ad70064a7f362f369179e794f937c04526ab49a Mon Sep 17 00:00:00 2001 From: luis Date: Thu, 9 Oct 2025 10:09:32 +0200 Subject: [PATCH] Criptomart/red-supermercados-coop#4 add pos_order_backend_receipt --- pos_order_backend_receipt/__init__.py | 1 + pos_order_backend_receipt/__manifest__.py | 19 ++ pos_order_backend_receipt/i18n/es.po | 123 +++++++++++ .../i18n/pos_order_backend_receipt.pot | 124 +++++++++++ pos_order_backend_receipt/models/__init__.py | 1 + pos_order_backend_receipt/models/pos_order.py | 83 +++++++ .../report/pos_order_report.xml | 202 ++++++++++++++++++ .../static/src/css/receipt.css | 73 +++++++ .../views/pos_order_views.xml | 14 ++ 9 files changed, 640 insertions(+) create mode 100644 pos_order_backend_receipt/__init__.py create mode 100644 pos_order_backend_receipt/__manifest__.py create mode 100644 pos_order_backend_receipt/i18n/es.po create mode 100644 pos_order_backend_receipt/i18n/pos_order_backend_receipt.pot create mode 100644 pos_order_backend_receipt/models/__init__.py create mode 100644 pos_order_backend_receipt/models/pos_order.py create mode 100644 pos_order_backend_receipt/report/pos_order_report.xml create mode 100644 pos_order_backend_receipt/static/src/css/receipt.css create mode 100644 pos_order_backend_receipt/views/pos_order_views.xml diff --git a/pos_order_backend_receipt/__init__.py b/pos_order_backend_receipt/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/pos_order_backend_receipt/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_order_backend_receipt/__manifest__.py b/pos_order_backend_receipt/__manifest__.py new file mode 100644 index 0000000..046da72 --- /dev/null +++ b/pos_order_backend_receipt/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "POS Order Backend Receipt", + "summary": "Print the POS order receipt from the backend", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Criptomart", + "website": "https://criptomart.net", + "depends": ["point_of_sale"], + "data": [ + "report/pos_order_report.xml", + "views/pos_order_views.xml", + ], + "assets": { + "web.report_assets_common": [ + "pos_order_backend_receipt/static/src/css/receipt.css", + ], + }, + "installable": True, +} diff --git a/pos_order_backend_receipt/i18n/es.po b/pos_order_backend_receipt/i18n/es.po new file mode 100644 index 0000000..c03b0bd --- /dev/null +++ b/pos_order_backend_receipt/i18n/es.po @@ -0,0 +1,123 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_backend_receipt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-09 07:59+0000\n" +"PO-Revision-Date: 2025-10-09 07:59+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "% discount" +msgstr "% Descuento" + +#. module: pos_order_backend_receipt +#: model:ir.actions.report,print_report_name:pos_order_backend_receipt.action_report_pos_order_ticket +msgid "'Receipt-%s' % (object.name)" +msgstr "'Recibo-%s' % (object.name)" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Simplified Invoice" +msgstr "Factura Simplificada" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Amount" +msgstr "Total" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Base" +msgstr "Base" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Change:" +msgstr "Cambio:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Customer:" +msgstr "Cliente:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Email:" +msgstr "Email:" + +#. module: pos_order_backend_receipt +#: model:ir.actions.report,name:pos_order_backend_receipt.action_report_pos_order_ticket +msgid "POS Receipt" +msgstr "Ticket PDV" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Phone:" +msgstr "Tel.:" + +#. module: pos_order_backend_receipt +#: model:ir.model,name:pos_order_backend_receipt.model_pos_order +msgid "Point of Sale Orders" +msgstr "Pedidos del TPV" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.view_pos_order_form_inherit_backend_ticket +msgid "Print Ticket" +msgstr "Imprimir Ticket" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Served by:" +msgstr "Atendido por:" + +#. module: pos_order_backend_receipt +#. odoo-python +#: code:addons/pos_order_backend_receipt/models/pos_order.py:0 +#, python-format +msgid "You can only print the receipt when the order is paid, closed, or invoiced." +msgstr "Solo se puede imprimir el recibo cuando el pedido está pagado, cerrado o facturado." + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total Discount:" +msgstr "Total Descuento:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total Taxes:" +msgstr "Total Impuestos:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total:" +msgstr "Total:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "VAT %" +msgstr "IVA %" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "VAT:" +msgstr "IVA:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Website:" +msgstr "Web:" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "With a" +msgstr "Con un" diff --git a/pos_order_backend_receipt/i18n/pos_order_backend_receipt.pot b/pos_order_backend_receipt/i18n/pos_order_backend_receipt.pot new file mode 100644 index 0000000..77cbe17 --- /dev/null +++ b/pos_order_backend_receipt/i18n/pos_order_backend_receipt.pot @@ -0,0 +1,124 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_order_backend_receipt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-09 07:59+0000\n" +"PO-Revision-Date: 2025-10-09 07:59+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "% discount" +msgstr "" + +#. module: pos_order_backend_receipt +#: model:ir.actions.report,print_report_name:pos_order_backend_receipt.action_report_pos_order_ticket +msgid "'Receipt-%s' % (object.name)" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Simplified Invoice" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Amount" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Base" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Change:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Customer:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Email:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model:ir.actions.report,name:pos_order_backend_receipt.action_report_pos_order_ticket +msgid "POS Receipt" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Phone:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model:ir.model,name:pos_order_backend_receipt.model_pos_order +msgid "Point of Sale Orders" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.view_pos_order_form_inherit_backend_ticket +msgid "Print Ticket" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Served by:" +msgstr "" + +#. module: pos_order_backend_receipt +#. odoo-python +#: code:addons/pos_order_backend_receipt/models/pos_order.py:0 +#, python-format +msgid "" +"You can only print the receipt when the order is paid, closed, or invoiced." +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total Discount:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total Taxes:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Total:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "VAT %" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "VAT:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "Website:" +msgstr "" + +#. module: pos_order_backend_receipt +#: model_terms:ir.ui.view,arch_db:pos_order_backend_receipt.report_pos_order_ticket +msgid "With a" +msgstr "" diff --git a/pos_order_backend_receipt/models/__init__.py b/pos_order_backend_receipt/models/__init__.py new file mode 100644 index 0000000..e9ab911 --- /dev/null +++ b/pos_order_backend_receipt/models/__init__.py @@ -0,0 +1 @@ +from . import pos_order diff --git a/pos_order_backend_receipt/models/pos_order.py b/pos_order_backend_receipt/models/pos_order.py new file mode 100644 index 0000000..04b4a52 --- /dev/null +++ b/pos_order_backend_receipt/models/pos_order.py @@ -0,0 +1,83 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import base64 +from types import SimpleNamespace +from odoo.tools import formatLang + + +class PosOrder(models.Model): + _inherit = "pos.order" + + def action_print_backend_ticket(self): + self.ensure_one() + if self.state not in ("paid", "done", "invoiced"): + raise UserError( + _( + "Solo se puede imprimir el ticket cuando el pedido está pagado o cerrado." + ) + ) + return self.env.ref( + "pos_order_backend_receipt.action_report_pos_order_ticket" + ).report_action(self) + + # ----------------- Helpers Backend Ticket ----------------- + def _get_backend_tax_breakdown(self): + """Return list of dicts with tax_rate, base, amount for legal breakdown. + + Group by tax (percentage). Only sales taxes (positive or negative based on sign). + """ + self.ensure_one() + breakdown = [] + Tax = self.env["account.tax"] + currency = self.pricelist_id.currency_id or self.company_id.currency_id + # aggregate via line taxes after fiscal position + tax_map = {} + for line in self.lines: + # price_subtotal (sin impuestos), price_subtotal_incl (con impuestos) + base = line.price_subtotal + tax_amount_line = line.price_subtotal_incl - line.price_subtotal + # Derivar tipo (promedio) desde los impuestos aplicados + for tax in line.tax_ids_after_fiscal_position: + key = tax.id + entry = tax_map.setdefault( + key, + { + "tax": tax, + "base": 0.0, + "amount": 0.0, + }, + ) + # prorratear base / importe cuando múltiples impuestos + # simplificación: si hay varios impuestos no compuestos, se reparte proporcional a sus montos teóricos + if line.tax_ids_after_fiscal_position: + # compute_all para repartir preciso + taxes_res = tax.compute_all( + line.price_unit * (1 - (line.discount or 0.0) / 100.0), + currency, + line.qty, + product=line.product_id, + partner=self.partner_id, + ) + # encontrar este tax + for tline in taxes_res["taxes"]: + if tline["id"] == tax.id: + entry["amount"] += tline["amount"] + # base para ese impuesto específico + entry["base"] += tline["base"] + else: + entry["base"] += base + entry["amount"] += tax_amount_line + # formatear + for v in tax_map.values(): + tax = v["tax"] + rate = tax.amount if tax.amount_type == "percent" else 0.0 + breakdown.append( + { + "rate": rate, + "base": currency.round(v["base"]), + "amount": currency.round(v["amount"]), + } + ) + # ordenar por tasa + breakdown.sort(key=lambda x: x["rate"]) + return breakdown diff --git a/pos_order_backend_receipt/report/pos_order_report.xml b/pos_order_backend_receipt/report/pos_order_report.xml new file mode 100644 index 0000000..932d29f --- /dev/null +++ b/pos_order_backend_receipt/report/pos_order_report.xml @@ -0,0 +1,202 @@ + + + + + POS Receipt + pos.order + qweb-pdf + pos_order_backend_receipt.report_pos_order_ticket + pos_order_backend_receipt.report_pos_order_ticket + 'Receipt-%s' % (object.name) + + report + + + + diff --git a/pos_order_backend_receipt/static/src/css/receipt.css b/pos_order_backend_receipt/static/src/css/receipt.css new file mode 100644 index 0000000..089a64a --- /dev/null +++ b/pos_order_backend_receipt/static/src/css/receipt.css @@ -0,0 +1,73 @@ + +.receipt { + direction: ltr; + padding: 0; + margin: 0; + background-color: #f0eeee; + font-family: "Lato", "Lucida Grande", Helvetica, Verdana, Arial; + color: #555555; + font-size: 12px; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + text-shadow: none; +} + +.receipt .right-align { + text-align: right; +} +.receipt .center-align { + text-align: center; +} + +/* The receipt */ + +.receipt .receipt-container { + font-size: 0.75em; + text-align: center; + direction: ltr; +} + +.receipt .sale-ticket { + text-align: left; + width: 350px; + background-color: white; + margin: 0px; + padding: 15px; + font-size: 14px; + display: inline-block; + font-family: "Inconsolata"; + border: solid 1px rgb(220, 220, 220); + border-radius: 3px; +} +.receipt .sale-ticket pre { + font-family: "Inconsolata"; +} +.receipt .sale-ticket .emph { + font-size: 20px; + margin: 5px; +} +.receipt .sale-ticket table { + width: 100%; + border: 0; + table-layout: fixed; +} +.receipt .sale-ticket table td { + border: 0; + word-wrap: break-word; +} + +/* Company logo full width inside ticket */ +.receipt .sale-ticket .company_logo { + display: block; + width: 100%; /* Fill container width */ + max-width: 100%; + height: auto; /* Keep aspect ratio */ + object-fit: contain;/* Avoid distortion */ + margin: 0 0 10px 0; /* Space below logo */ +} \ No newline at end of file diff --git a/pos_order_backend_receipt/views/pos_order_views.xml b/pos_order_backend_receipt/views/pos_order_views.xml new file mode 100644 index 0000000..f187ce9 --- /dev/null +++ b/pos_order_backend_receipt/views/pos_order_views.xml @@ -0,0 +1,14 @@ + + + + pos.order.form.inherit.backend.ticket + pos.order + + + +