This commit is contained in:
Luis 2025-10-09 10:09:32 +02:00
parent 6aec5669be
commit 7ad70064a7
9 changed files with 640 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import models

View file

@ -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,
}

View file

@ -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 "<span>Simplified Invoice</span>"
msgstr "<span>Factura Simplificada</span>"
#. 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"

View file

@ -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 "<span>Simplified Invoice</span>"
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 ""

View file

@ -0,0 +1 @@
from . import pos_order

View file

@ -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

View file

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_report_pos_order_ticket" model="ir.actions.report">
<field name="name">POS Receipt</field>
<field name="model">pos.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">pos_order_backend_receipt.report_pos_order_ticket</field>
<field name="report_file">pos_order_backend_receipt.report_pos_order_ticket</field>
<field name="print_report_name">'Receipt-%s' % (object.name)</field>
<field name="binding_model_id" ref="model_pos_order" />
<field name="binding_type">report</field>
</record>
<template id="report_pos_order_ticket">
<t t-foreach="docs" t-as="o">
<t t-call="web.html_container">
<t t-call="web.basic_layout">
<t t-set="o" t-value="o.with_context(lang=o.partner_id.lang)" />
<div class="receipt">
<div class="receipt-container">
<div class="sale-ticket">
<div class="center-align">
<img t-if="o.company_id.logo" t-att-src="image_data_uri(o.company_id.logo)" class="company_logo"/>
<t t-if="o.is_l10n_es_simplified_invoice">
<span>Simplified Invoice</span>
<span t-field="o.l10n_es_unique_id"/>
</t>
<t t-else="">
<span t-field="o.name"/>
</t>
<br/>
<h5 t-field="o.company_id.name" />
<span t-field="o.company_id.street" />
<span t-field="o.company_id.city" />
<span t-field="o.company_id.zip" />
<br />
<t t-if="o.company_id.phone">
<div class="receipt-phone">
Phone:
<span t-field="o.company_id.phone" />
</div>
</t>
<t t-if="o.company_id.vat">
<div class="receipt-vat">
VAT:
<span t-field="o.company_id.vat" />
</div>
</t>
<t t-if="o.company_id.email">
<div class="receipt-email">
Email:
<span t-field="o.company_id.email" />
</div>
</t>
<t t-if="o.company_id.website">
<div class="receipt-website">
Website:
<span t-field="o.company_id.website" />
</div>
</t>
<t t-if="o.user_id.name">
<div class="receipt-cashier">
Served by:
<span t-field="o.user_id.name" />
</div>
</t>
<t t-if="o.partner_id.name">
<div class="receipt-customer">
Customer:
<span t-field="o.partner_id.name" />
</div>
</t>
<br />
</div>
<br />
<table class='receipt-orderlines'>
<colgroup>
<col width='50%' />
<col width='25%' />
<col width='25%' />
</colgroup>
<tr t-foreach="o.lines" t-as="line">
<td>
<span t-field="line.product_id.name" />
<t t-if="line.discount > 0">
<div class="disc-font">
With a <span
t-field="line.discount"
/>% discount
</div>
</t>
</td>
<td class="right-align">
<span t-field="line.qty" /> <span
t-field="line.product_id.uom_id.name"
/>
</td>
<td class="right-align">
<span
t-field="line.price_subtotal_incl"
t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"
/>
</td>
</tr>
</table>
<table class='receipt-total'>
<t t-set="tax_breakdown" t-value="o._get_backend_tax_breakdown()"/>
<tr t-if="tax_breakdown">
<td colspan="2">
<table class="receipt-taxdetail">
<thead>
<tr>
<th class="right-align">Base</th>
<th class="right-align">VAT %</th>
<th class="right-align">Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="tax_breakdown" t-as="tax">
<td class="right-align">
<span t-esc="tax['base']" t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"/>
</td>
<td class="right-align"><t t-esc="tax['rate']"/>%</td>
<td class="right-align">
<span t-esc="tax['amount']" t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"/>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<!-- Tax breakdown (IVA) -->
<tr>
<td>Total Taxes:</td>
<td class="right-align">
<span
t-field="o.amount_tax"
t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"
/>
</td>
</tr>
<br/>
<br/>
<!-- Total discount row -->
<t t-set="total_discount" t-value="sum(line.price_unit * line.qty * (line.discount or 0.0)/100.0 for line in o.lines)"/>
<tr t-if="total_discount">
<td>Total Discount:</td>
<td class="right-align">
<span t-esc="-total_discount" t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"/>
</td>
</tr>
<br/>
<tr class="emph">
<td>Total:</td>
<td class="right-align">
<span
t-out="o.amount_total"
t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"
/>
</td>
</tr>
<br />
<table class='receipt-paymentlines'>
<t t-foreach="o.payment_ids" t-as="line">
<t t-if="line.amount > 0">
<tr>
<td>
<span t-field="line.payment_method_id" />
</td>
<td class="right-align">
<span
t-field="line.amount"
t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"
/>
</td>
</tr>
</t>
</t>
</table>
<table class='receipt-change'>
<tr>
<td>Change:</td>
<td class="right-align">
<span
t-field="o.amount_return"
t-options="{'widget': 'monetary', 'display_currency': o.company_id.currency_id}"
/>
</td>
</tr>
</table>
</table>
<br/>
<div class="center-align"><span t-field="o.pos_reference" /></div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View file

@ -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 */
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_pos_order_form_inherit_backend_ticket" model="ir.ui.view">
<field name="name">pos.order.form.inherit.backend.ticket</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="action_print_backend_ticket" type="object" string="Print Ticket" attrs="{'invisible': [('state', 'not in', ('paid','done','invoiced'))]}"/>
</xpath>
</field>
</record>
</odoo>