[REF] pos*: simplify receipt printing
pos*: l10n_co_pos, l10n_fr_pos_cert, l10n_gcc_pos,
l10n_in_pos, l10n_sa_pos, point_of_sale,
pos_epson_printer, pos_loyalty, pos_mercury,
pos_online_payment, pos_restaurant, pos_sale
There is a need to print pos receipts in new environments,
such as the kiosk. In this commit we simplify the steps to
generate the receipt data and introduce a portable mechanism
for printing.
Changes:
- removed the `getOrderReceiptEnv` method; this method
was returning a lot of data that the receipt was not
actually using; we now simply use the data from
`Order.export_for_printing`. This means that it will be much
easier to recreate this data in other environments, such
as in the kiosk.
- removed the error prone `generate_wrapped_product_name` method.
It's aim was to split the orderline name, but this task is much
better accomplished by declarative css.
- removed the `Orderline.export_for_printing` method; instead,
we simply use the existing `Orderline.getDisplayData`;
- removed the `AbstractReceiptScreen` component and replaced
it's functionality with the new `printer` service;
- using this service means that we were free to remove
the `pos-receipt-print` div from the root of the pos app;
- made the receipt be a standalone component. It thus benefits
from all the advantages of components, such as prop validation
and usage of slots;
- replaced the `OrderLinesReceipt` template with the generic
`OrderWidget` component'; this greatly simplifies the code;
- created the `renderer` service. It's goal is to do for components
what `renderToElement` does for templates; we use this service to
render the receipt component for printing;
- removed jquery from the EpsonPrinter class, such that it can be
imported into projects that do not rely on jquery;
- created the `ReceiptHeader` component. This unifies the header
between all the different receipts; ( before, each receipt was
implementing it's own header )
Task: 3547597
Part-of: odoo/odoo#137397
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="l10n_co_pos.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-order-data')]" position="inside">
|
||||
<t t-if="receiptData.receipt.l10n_co_dian !== false">
|
||||
<div style="word-wrap:break-word;"><t t-esc="receiptData.receipt.l10n_co_dian"/></div>
|
||||
<t t-if="props.data.l10n_co_dian !== false">
|
||||
<div style="word-wrap:break-word;"><t t-esc="props.data.l10n_co_dian"/></div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]//div" position="before">
|
||||
<t t-set="partner" t-value="receiptData.order.get_partner()"/>
|
||||
<t t-set="isSimplifiedInvoice" t-value="receiptData.order.is_l10n_es_simplified_invoice"/>
|
||||
<t t-set="receipt" t-value="receiptData.receipt"/>
|
||||
<t t-if="pos.config.is_spanish">
|
||||
<t t-if="isSimplifiedInvoice">
|
||||
<div>Simplified invoice</div>
|
||||
<div class="simplified-invoice-number" t-esc="receiptData.order.invoice_name" />
|
||||
</t>
|
||||
<div t-if="receipt.company.street" t-esc="receipt.company.street" />
|
||||
<div t-if="receipt.company.zip" t-esc="receipt.company.zip" />
|
||||
<div t-if="receipt.company.city" t-esc="receipt.company.city" />
|
||||
<div t-if="receipt.company.state_id">(<t t-esc="receipt.company.state_id[1]"/>)</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]" position="inside">
|
||||
<t t-if="pos.config.is_spanish and partner?.id !== pos.config.simplified_partner_id[0]">
|
||||
<div>Customer: <t t-esc="partner.name" /></div>
|
||||
<div t-if="partner.vat">
|
||||
<t t-esc="receipt.company.vat_label" />:
|
||||
<t t-esc="partner.vat" />
|
||||
</div>
|
||||
<div t-if="partner.address" t-esc="partner.address"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="ReceiptHeader" t-inherit="point_of_sale.ReceiptHeader" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]//div" position="before">
|
||||
<t t-set="isSimplifiedInvoice" t-value="props.data.is_l10n_es_simplified_invoice"/>
|
||||
<t t-if="props.data.is_spanish">
|
||||
<t t-if="isSimplifiedInvoice">
|
||||
<div>Simplified invoice</div>
|
||||
<div class="simplified-invoice-number" t-esc="props.data.invoice_name" />
|
||||
</t>
|
||||
<div t-if="props.data.company.street" t-esc="props.data.company.street" />
|
||||
<div t-if="props.data.company.zip" t-esc="props.data.company.zip" />
|
||||
<div t-if="props.data.company.city" t-esc="props.data.company.city" />
|
||||
<div t-if="props.data.company.state_id">(<t t-esc="props.data.company.state_id[1]"/>)</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]" position="inside">
|
||||
<t t-set="partner" t-value="props.data.partner"/>
|
||||
<t t-if="props.data.is_spanish and partner?.id !== props.data.simplified_partner_id">
|
||||
<div>Customer: <t t-esc="partner.name" /></div>
|
||||
<div t-if="partner.vat"><t t-esc="props.data.company.country?.vat_label || 'Tax ID'"/>: <t t-esc="partner.vat" /></div>
|
||||
<div t-if="partner.address" t-esc="partner.address"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,16 @@
|
||||
/** @odoo-module */
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PosStore } from "@point_of_sale/app/store/pos_store";
|
||||
|
||||
patch(PosStore.prototype, {
|
||||
getReceiptHeaderData() {
|
||||
return {
|
||||
...super.getReceiptHeaderData(...arguments),
|
||||
is_spanish: this.config.is_spanish,
|
||||
simplified_partner_id: this.config.simplified_partner_id[0],
|
||||
is_l10n_es_simplified_invoice: this.get_order().is_l10n_es_simplified_invoice,
|
||||
partner: this.get_order().get_partner(),
|
||||
invoice_name: this.get_order().invoice_name,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -2,21 +2,21 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="l10n_fr_pos_cert.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-order-data')]" position="inside">
|
||||
<t t-if="receiptData.receipt.l10n_fr_hash !== false">
|
||||
<t t-if="props.data.l10n_fr_hash !== false">
|
||||
<br/>
|
||||
<div style="word-wrap:break-word;"><t t-out="receiptData.receipt.l10n_fr_hash"/></div>
|
||||
<div style="word-wrap:break-word;"><t t-out="props.data.l10n_fr_hash"/></div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="l10n_fr_pos_cert.OrderLinesReceipt" t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[@id='receipt-orderline']" position="inside">
|
||||
<t t-if="receiptData.receipt.l10n_fr_hash !== false and line.price_type === 'manual'">
|
||||
<t t-name="l10n_fr_pos_cert.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//Orderline" position="inside">
|
||||
<t t-if="props.data.l10n_fr_hash !== false and line.price_type === 'manual'">
|
||||
<div class="pos-receipt-right-padding">
|
||||
Old unit price:
|
||||
<span class="oldPrice">
|
||||
<s>
|
||||
<t t-out="env.utils.formatCurrency(line.taxed_lst_unit_price)" /> / Units
|
||||
<t t-out="props.formatCurrency(line.taxed_lst_unit_price)" /> / Units
|
||||
</s>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+34
-70
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
|
||||
<t t-inherit="point_of_sale.ReceiptHeader" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]" position="after">
|
||||
<t t-if="receiptData.receipt.is_gcc_country">
|
||||
<t t-if="props.data.is_gcc_country">
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="pos-receipt-header">
|
||||
@@ -15,99 +14,64 @@
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//t[@t-esc='receiptData.receipt.cashier']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
<xpath expr="//t[@t-esc='props.data.cashier']/.." position="attributes">
|
||||
<attribute name="t-if">!props.data.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-esc='receiptData.receipt.cashier']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" t-translation="off">
|
||||
<div>Served by / خدم بواسطة <t t-esc="receiptData.receipt.cashier"/></div>
|
||||
<xpath expr="//t[@t-esc='props.data.cashier']/.." position="after">
|
||||
<div t-if="props.data.is_gcc_country" t-translation="off">
|
||||
<div>Served by / خدم بواسطة <t t-esc="props.data.cashier"/></div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_with_tax)']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
<t t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.amount_total)']/.." position="attributes">
|
||||
<attribute name="t-if">!props.data.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_with_tax)']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.amount_total)']/.." position="after">
|
||||
<div t-if="props.data.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
|
||||
TOTAL / الإجمالي
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.amount_total)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.rounding_applied)']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
<xpath expr="//t[@t-if='props.data.rounding_applied']/.." position="attributes">
|
||||
<attribute name="t-if">!props.data.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.rounding_applied)']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
|
||||
<xpath expr="//t[@t-if='props.data.rounding_applied']/.." position="after">
|
||||
<div t-if="props.data.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
|
||||
Rounding / التقريب
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.rounding_applied)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.rounding_applied)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
<div class="pos-receipt-amount">
|
||||
To Pay
|
||||
<span t-esc="props.formatCurrency(props.data.amount_total + props.data.rounding_applied)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_rounded)']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.change)']/.." position="attributes">
|
||||
<attribute name="t-if">!props.data.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_rounded)']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
|
||||
To Pay / للسداد
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_rounded)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.change)']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.change)']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" class="pos-receipt-amount receipt-change pos-receipt-amount-arabic" t-translation="off">
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.change)']/.." position="after">
|
||||
<div t-if="props.data.is_gcc_country" class="pos-receipt-amount receipt-change pos-receipt-amount-arabic" t-translation="off">
|
||||
CHANGE / الباقي
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.change)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.change)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_discount)']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.total_discount)']/.." position="attributes">
|
||||
<attribute name="t-if">!props.data.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[@t-esc='env.utils.formatCurrency(receiptData.receipt.total_discount)']/.." position="after">
|
||||
<div t-if="receiptData.receipt.is_gcc_country" t-translation="off">
|
||||
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.total_discount)']/.." position="after">
|
||||
<div t-if="props.data.is_gcc_country" t-translation="off">
|
||||
Discounts / الخصومات
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_discount)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.total_discount)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('pos-receipt-taxes')]/span[text()='Total']" position="replace">
|
||||
<span t-if="receiptData.receipt.is_gcc_country" t-translation="off">Total Taxes / إجمالي الضرائب</span>
|
||||
<span t-if="props.data.is_gcc_country" t-translation="off">Total Taxes / إجمالي الضرائب</span>
|
||||
<span t-else="">Total</span>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//t[@t-esc='line.unit_name']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-esc='line.unit_name']/.." position="after">
|
||||
<t t-if="receiptData.receipt.is_gcc_country">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<t t-esc="Math.round(line.quantity * Math.pow(10, pos.dp['Product Unit of Measure'])) / Math.pow(10, pos.dp['Product Unit of Measure'])"/>
|
||||
<t t-if="!line.is_in_unit" t-esc="line.unit_name" />
|
||||
x
|
||||
<t t-esc="env.utils.formatCurrency(line.price_display_one, false)" />
|
||||
</div>
|
||||
<div class="responsive-price">
|
||||
<div class="pos-receipt-left-padding" style="display: inline-flex;">
|
||||
<div t-translation="off">Taxes / الضرائب</div>:<span t-esc="env.utils.formatCurrency(line.tax, false)" style="margin-left: 5px"/>
|
||||
</div>
|
||||
<span t-esc="env.utils.formatCurrency(line.price_display, false)" class="price_display pos-receipt-right-align"/>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//t[@t-esc='line.discount']/.." position="attributes">
|
||||
<attribute name="t-if">!receiptData.receipt.is_gcc_country</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-esc='line.discount']/.." position="after">
|
||||
<div class="pos-receipt-left-padding" style="display: inline-flex;" t-if="receiptData.receipt.is_gcc_country" t-translation="off">
|
||||
<div>Discount / الخصم</div>: <t t-esc="line.discount" />%
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
+7
-8
@@ -4,13 +4,12 @@ import { Order } from "@point_of_sale/app/store/models";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Order.prototype, {
|
||||
getOrderReceiptEnv() {
|
||||
const receiptData = super.getOrderReceiptEnv();
|
||||
const receipt = receiptData.receipt;
|
||||
const country = receiptData.order.pos.company.country;
|
||||
receipt.is_gcc_country = country
|
||||
? ["SA", "AE", "BH", "OM", "QA", "KW"].includes(country && country.code)
|
||||
: false;
|
||||
return receiptData;
|
||||
export_for_printing() {
|
||||
return {
|
||||
...super.export_for_printing(),
|
||||
is_gcc_country: ["SA", "AE", "BH", "OM", "QA", "KW"].includes(
|
||||
this.pos.company.country?.code
|
||||
),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="l10n_in_pos.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('orderlines')]" position="before">
|
||||
<t t-if="receiptData.receipt.partner and pos.company.country and pos.company.country.code == 'IN'">
|
||||
<t t-name="l10n_in_pos.ReceiptHeader" t-inherit="point_of_sale.ReceiptHeader" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt-contact')]" position="after">
|
||||
<t t-if="props.data.partner and props.data.company?.country?.code == 'IN'">
|
||||
<div class="pos-receipt-center-align">
|
||||
<div><t t-esc="receiptData.receipt.partner.name" /></div>
|
||||
<t t-if="receiptData.receipt.partner.phone">
|
||||
<div><t t-esc="props.data.partner.name" /></div>
|
||||
<t t-if="props.data.partner.phone">
|
||||
<div>
|
||||
<span>Phone: </span>
|
||||
<t t-esc="receiptData.receipt.partner.phone" />
|
||||
<t t-esc="props.data.partner.phone" />
|
||||
</div>
|
||||
</t>
|
||||
<br />
|
||||
@@ -17,8 +17,8 @@
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="l10n_in_pos.OrderLinesReceipt" t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[@id='receipt-orderline']" position="inside">
|
||||
<t t-name="l10n_in_pos.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//Orderline" position="inside">
|
||||
<t t-if="line.l10n_in_hsn_code and pos.company.country and pos.company.country.code == 'IN'">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<span>HSN Code: </span>
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Orderline } from "@point_of_sale/app/store/models";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Orderline.prototype, {
|
||||
export_for_printing() {
|
||||
const line = super.export_for_printing(...arguments);
|
||||
line.l10n_in_hsn_code = this.get_product().l10n_in_hsn_code;
|
||||
return line;
|
||||
getDisplayData() {
|
||||
return {
|
||||
...super.getDisplayData(),
|
||||
l10n_in_hsn_code: this.get_product().l10n_in_hsn_code,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<t t-inherit="point_of_sale.ReceiptHeader" t-inherit-mode="extension">
|
||||
<xpath expr="//img[hasclass('pos-receipt-logo')]" position="after">
|
||||
<t t-if="receiptData.receipt.is_gcc_country and !receiptData.receipt.is_settlement">
|
||||
<img t-if="receiptData.receipt.qr_code" id="qrcode" t-att-src="receiptData.receipt.qr_code" class="pos-receipt-logo"/>
|
||||
<t t-if="props.data.is_gcc_country and !props.data.is_settlement">
|
||||
<img t-if="props.data.qr_code" id="qrcode" t-att-src="props.data.qr_code" class="pos-receipt-logo"/>
|
||||
<br/>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@id='title_english']" position="replace">
|
||||
<t t-if="!receiptData.receipt.is_settlement">
|
||||
<t t-if="!props.data.is_settlement">
|
||||
<span id="title_english">Simplified Tax Invoice</span>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//span[@id='title_arabic']" position="replace">
|
||||
<t t-if="!receiptData.receipt.is_settlement">
|
||||
<t t-if="!props.data.is_settlement">
|
||||
<span id="title_arabic">فاتورة ضريبية مبسطة</span>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
@@ -7,13 +7,6 @@ import { onMounted } from "@odoo/owl";
|
||||
|
||||
export class ErrorPopup extends AbstractAwaitablePopup {
|
||||
static template = "point_of_sale.ErrorPopup";
|
||||
static props = {
|
||||
confirmText: String,
|
||||
title: String,
|
||||
body: String,
|
||||
cancelKey: Boolean,
|
||||
sound: Boolean,
|
||||
};
|
||||
static defaultProps = {
|
||||
confirmText: _t("Ok"),
|
||||
title: _t("Error"),
|
||||
|
||||
+2
-5
@@ -3,11 +3,8 @@
|
||||
<t t-name="point_of_sale.OrderWidget">
|
||||
<t t-if="props.lines?.length">
|
||||
<div t-ref="scrollable" class="order-container bg-view overflow-y-auto flex-grow-1 d-flex flex-column text-start">
|
||||
<!-- this is similar to doing `for i in range(mylist)` -->
|
||||
<!-- we do this instead of looping directly on the lines because the lines might not have -->
|
||||
<!-- an element that we could use as a `t-key` -->
|
||||
<t t-foreach="props.lines.map((el, idx) => idx)" t-as="i" t-key="i">
|
||||
<t t-slot="default" line="props.lines[i]"/>
|
||||
<t t-foreach="props.lines" t-as="line" t-key="line_index">
|
||||
<t t-slot="default" line="line"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="props.total or props.tax" class="order-summary w-100 py-2 px-3 bg-100 text-end fw-bolder fs-2 lh-sm">
|
||||
|
||||
@@ -19,6 +19,7 @@ export class Orderline extends Component {
|
||||
oldUnitPrice: { type: String, optional: true },
|
||||
customerNote: { type: String, optional: true },
|
||||
internalNote: { type: String, optional: true },
|
||||
"*": true,
|
||||
},
|
||||
},
|
||||
slots: { type: Object, optional: true },
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<li class="orderline p-2 lh-sm cursor-pointer" t-attf-class="{{ line.comboParent ? 'border-start border-3 ms-4' : '' }}" t-att-class="props.class">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="product-name d-inline-block flex-grow-1 fw-bolder pe-1 text-truncate">
|
||||
<span t-esc="line.productName"/>
|
||||
<span class="text-wrap" t-esc="line.productName"/>
|
||||
<t t-slot="product-name"/>
|
||||
</div>
|
||||
<div t-esc="line.price" class="price fw-bolder"/>
|
||||
@@ -16,15 +16,15 @@
|
||||
x <s t-esc="line.oldUnitPrice" t-if="line.oldUnitPrice" />
|
||||
<t t-esc="line.unitPrice" /> <span t-if="line.unit"> / <t t-esc="line.unit" /></span>
|
||||
</li>
|
||||
<li t-if="line.discount !== '0'">
|
||||
<li t-if="line.discount and line.discount !== '0'">
|
||||
With a <em><t t-esc="line.discount" />% </em> discount
|
||||
</li>
|
||||
<t t-slot="default" />
|
||||
<li t-if="line.customerNote" class="customer-note w-50 p-2 my-1 rounded text-bg-warning text-warning bg-opacity-25 text-break">
|
||||
<li t-if="line.customerNote" class="customer-note w-100 p-2 my-1 rounded text-break text-bg-warning text-warning bg-opacity-25">
|
||||
<i class="fa fa-sticky-note me-1" role="img" aria-label="Customer Note" title="Customer Note"/>
|
||||
<t t-esc="line.customerNote" />
|
||||
</li>
|
||||
<li t-if="line.internalNote" class="internal-note w-50 p-2 my-1 rounded text-bg-info text-info bg-opacity-25 text-break">
|
||||
<li t-if="line.internalNote" class="internal-note w-100 p-2 my-1 rounded text-bg-info text-info bg-opacity-25 text-break">
|
||||
<i class="fa fa-tag me-1" role="img" aria-label="Note" title="Note"/>
|
||||
<t t-esc="line.internalNote" />
|
||||
</li>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { parseFloat } from "@web/views/fields/parsers";
|
||||
import { useState } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
|
||||
import { AbstractAwaitablePopup } from "@point_of_sale/app/popup/abstract_awaitable_popup";
|
||||
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
|
||||
import { CashMoveReceipt } from "@point_of_sale/app/navbar/cash_move_popup/cash_move_receipt/cash_move_receipt";
|
||||
import { useAsyncLockedMethod } from "@point_of_sale/app/utils/hooks";
|
||||
import { Input } from "@point_of_sale/app/generic_components/inputs/input/input";
|
||||
|
||||
@@ -22,6 +21,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
|
||||
this.orm = useService("orm");
|
||||
this.pos = usePos();
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
this.printer = useService("printer");
|
||||
this.state = useState({
|
||||
/** @type {'in'|'out'} */
|
||||
type: "out",
|
||||
@@ -54,28 +54,13 @@ export class CashMovePopup extends AbstractAwaitablePopup {
|
||||
`${_t("Cash")} ${translatedType} - ${_t("Amount")}: ${formattedAmount}`,
|
||||
"CASH_DRAWER_ACTION"
|
||||
);
|
||||
|
||||
if (this.hardwareProxy.printer) {
|
||||
const renderedReceipt = renderToElement("point_of_sale.CashMoveReceipt", {
|
||||
_receipt: {
|
||||
type,
|
||||
reason,
|
||||
amount,
|
||||
translatedType,
|
||||
formattedAmount,
|
||||
cashier: this.pos.get_cashier(),
|
||||
company: this.pos.company,
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
});
|
||||
const printResult = await this.hardwareProxy.printer.printReceipt(renderedReceipt);
|
||||
if (!printResult.successful) {
|
||||
this.popup.add(ErrorPopup, {
|
||||
title: printResult.message.title,
|
||||
body: printResult.message.body,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.printer.print(CashMoveReceipt, {
|
||||
reason,
|
||||
translatedType,
|
||||
formattedAmount,
|
||||
headerData: this.pos.getReceiptHeaderData(),
|
||||
date: new Date().toLocaleString(),
|
||||
});
|
||||
this.props.close();
|
||||
this.notification.add(
|
||||
_t("Successfully made a cash %s of %s.", type, formattedAmount),
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { ReceiptHeader } from "@point_of_sale/app/screens/receipt_screen/receipt/receipt_header/receipt_header";
|
||||
|
||||
export class CashMoveReceipt extends Component {
|
||||
static template = "point_of_sale.CashMoveReceipt";
|
||||
static components = { ReceiptHeader };
|
||||
static props = {
|
||||
reason: String,
|
||||
translatedType: String,
|
||||
formattedAmount: String,
|
||||
headerData: Object,
|
||||
date: String,
|
||||
};
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.CashMoveReceipt">
|
||||
<div class="pos-receipt">
|
||||
<ReceiptHeader data="props.headerData" />
|
||||
<div class="pos-receipt-center-align">
|
||||
CASH
|
||||
<t t-esc="props.translatedType.toUpperCase()" />
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
AMOUNT
|
||||
<span t-esc="props.formattedAmount" class="pos-receipt-right-align" />
|
||||
</div>
|
||||
<div>
|
||||
REASON
|
||||
<span t-esc="props.reason" class="pos-receipt-right-align" />
|
||||
</div>
|
||||
<br />
|
||||
<div class="pos-receipt-order-data">
|
||||
<div><t t-esc="props.date" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.CashMoveReceipt">
|
||||
<div class="pos-receipt">
|
||||
<t t-if="_receipt.company.logo">
|
||||
<img class="pos-receipt-logo" t-att-src="_receipt.company.logo" alt="Logo" />
|
||||
<br />
|
||||
</t>
|
||||
<t t-if="!_receipt.company.logo">
|
||||
<h2 class="pos-receipt-center-align">
|
||||
<t t-esc="_receipt.company.name" />
|
||||
</h2>
|
||||
<br />
|
||||
</t>
|
||||
<div class="pos-receipt-contact">
|
||||
<div t-if="_receipt.company.contact_address">
|
||||
<t t-esc="_receipt.company.contact_address" />
|
||||
</div>
|
||||
<t t-if="_receipt.company.phone">
|
||||
<div>Tel:
|
||||
<t t-esc="_receipt.company.phone" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="_receipt.company.vat">
|
||||
<div>VAT:
|
||||
<t t-esc="_receipt.company.vat" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="_receipt.company.email">
|
||||
<div>
|
||||
<t t-esc="_receipt.company.email" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="_receipt.company.website">
|
||||
<div>
|
||||
<t t-esc="_receipt.company.website" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="_receipt.cashier">
|
||||
<div class="cashier">
|
||||
<div>--------------------------------</div>
|
||||
<div>Done by
|
||||
<t t-esc="_receipt.cashier.name" />
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="pos-receipt-center-align">
|
||||
CASH
|
||||
<t t-esc="_receipt.translatedType.toUpperCase()" />
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
AMOUNT
|
||||
<span t-esc="_receipt.formattedAmount" class="pos-receipt-right-align" />
|
||||
</div>
|
||||
<div>
|
||||
REASON
|
||||
<span t-esc="_receipt.reason" class="pos-receipt-right-align" />
|
||||
</div>
|
||||
<br />
|
||||
<div class="pos-receipt-order-data">
|
||||
<div><t t-esc="_receipt.date" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
<t t-name="point_of_sale.Chrome">
|
||||
<div class="pos dvh-100 d-flex flex-column">
|
||||
<div class="pos-receipt-print"></div>
|
||||
<Navbar />
|
||||
<div class="pos-content flex-grow-1 overflow-auto bg-200">
|
||||
<!-- FIXME POSREF: better error handling in main screens (currently, a crash in owl lifecycle of a main screen blows up the application and the error can't be displayed) -->
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* @odoo-module */
|
||||
/* global html2canvas */
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
import { htmlToCanvas } from "@point_of_sale/app/printer/render_service";
|
||||
/**
|
||||
* Implements basic printer functions.
|
||||
*/
|
||||
@@ -28,7 +27,9 @@ export class BasePrinter {
|
||||
let image, printResult;
|
||||
while (this.receiptQueue.length > 0) {
|
||||
receipt = this.receiptQueue.shift();
|
||||
image = await this.htmlToImg(receipt);
|
||||
image = this.processCanvas(
|
||||
await htmlToCanvas(receipt, { addClass: "pos-receipt-print" })
|
||||
);
|
||||
try {
|
||||
printResult = await this.sendPrintingJob(image);
|
||||
} catch {
|
||||
@@ -62,33 +63,6 @@ export class BasePrinter {
|
||||
return canvas.toDataURL("image/jpeg").replace("data:image/jpeg;base64,", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function renders `el` as an image for printing assuming the following
|
||||
* - a `div.pos-receipt-print` is present in the dom and will serve as the container
|
||||
* - a `.pos-receipt` is the root element of `el`.
|
||||
* @param {HTMLElement} el The element to be converted as image
|
||||
* @returns {HTMLCanvasElement}
|
||||
*/
|
||||
async htmlToImg(el) {
|
||||
const elClone = el.cloneNode(true);
|
||||
const receiptContainer = document.querySelector(".pos-receipt-print");
|
||||
receiptContainer.appendChild(elClone);
|
||||
const receipt = receiptContainer.querySelector(".pos-receipt");
|
||||
// Odoo RTL support automatically flip left into right but html2canvas
|
||||
// won't work as expected if the receipt is aligned to the right of the
|
||||
// screen so we need to flip it back.
|
||||
const parent = receipt.parentElement;
|
||||
parent.style.left = 0;
|
||||
parent.style.right = "auto";
|
||||
const canvas = await html2canvas(receipt, {
|
||||
height: Math.ceil(receipt.clientHeight),
|
||||
width: Math.ceil(receipt.clientWidth),
|
||||
scale: 1,
|
||||
});
|
||||
receipt.remove();
|
||||
return this.processCanvas(canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of this method will be the result of calling `printReceipt`
|
||||
* if it failed to connect to the IoT box.
|
||||
@@ -116,7 +90,7 @@ export class BasePrinter {
|
||||
"Please check if the printer is still connected. \n" +
|
||||
"Some browsers don't allow HTTP calls from websites to devices in the network (for security reasons). " +
|
||||
"If it is the case, you will need to follow Odoo's documentation for " +
|
||||
"'Self-signed certificate for ePOS printers' and 'Secure connection (HTTPS)' to solve the issue"
|
||||
"'Self-signed certificate for ePOS printers' and 'Secure connection (HTTPS)' to solve the issue. "
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
|
||||
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
|
||||
import { PrinterService } from "@point_of_sale/app/printer/printer_service";
|
||||
|
||||
const posPrinterService = {
|
||||
dependencies: ["hardware_proxy", "popup", "renderer"],
|
||||
start(env, { hardware_proxy, popup, renderer }) {
|
||||
return new PosPrinterService(env, { hardware_proxy, popup, renderer });
|
||||
},
|
||||
};
|
||||
class PosPrinterService extends PrinterService {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.setup(...args);
|
||||
}
|
||||
setup(env, { hardware_proxy, popup, renderer }) {
|
||||
this.renderer = renderer;
|
||||
this.hardware_proxy = hardware_proxy;
|
||||
this.popup = popup;
|
||||
this.device = hardware_proxy.printer;
|
||||
}
|
||||
printWeb() {
|
||||
try {
|
||||
return super.printWeb(...arguments);
|
||||
} catch {
|
||||
this.popup.add(ErrorPopup, {
|
||||
title: _t("Printing is not supported on some browsers"),
|
||||
body: _t("It is possible to print your tickets by making use of an IoT Box."),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async printHtml() {
|
||||
this.setPrinter(this.hardware_proxy.printer);
|
||||
try {
|
||||
return await super.printHtml(...arguments);
|
||||
} catch (error) {
|
||||
const { confirmed } = await this.popup.add(ConfirmPopup, {
|
||||
title: error.title || _t("Printing error"),
|
||||
body: error.body + _t("Do you want to print using the web printer? "),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
// We want to call the _printWeb when the popup is fully gone
|
||||
// from the screen which happens after the next animation frame.
|
||||
await new Promise(requestAnimationFrame);
|
||||
return await this.printWeb(...arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("services").add("printer", posPrinterService);
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export const printerService = {
|
||||
dependencies: ["renderer"],
|
||||
start(env, { renderer }) {
|
||||
return new PrinterService(env, { renderer });
|
||||
},
|
||||
};
|
||||
export class PrinterService {
|
||||
constructor(...args) {
|
||||
this.setup(...args);
|
||||
}
|
||||
setup(env, { renderer }) {
|
||||
this.renderer = renderer;
|
||||
this.device = null;
|
||||
}
|
||||
setPrinter(newDevice) {
|
||||
this.device = newDevice;
|
||||
}
|
||||
printWeb(el) {
|
||||
this.renderer.whenMounted({ el, callback: window.print });
|
||||
return true;
|
||||
}
|
||||
async printHtml(el, { webPrintFallback = false } = {}) {
|
||||
if (!this.device) {
|
||||
return webPrintFallback && this.printWeb(el);
|
||||
}
|
||||
const printResult = await this.device.printReceipt(el);
|
||||
if (printResult.successful) {
|
||||
return true;
|
||||
}
|
||||
throw {
|
||||
title: printResult.message.title || "Error",
|
||||
body: printResult.message.body,
|
||||
};
|
||||
}
|
||||
async print(component, props, options) {
|
||||
const el = await this.renderer.toHtml(component, props);
|
||||
return await this.printHtml(el, options);
|
||||
}
|
||||
is = () => Boolean(this.device?.printReceipt);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/** @odoo-module **/
|
||||
/* global html2canvas */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, onRendered, reactive, useRef, xml } from "@odoo/owl";
|
||||
|
||||
export class RenderContainer extends Component {
|
||||
static props = ["comp", "onRendered"];
|
||||
// the `.render-container` is used by other functions that need a
|
||||
// place where to momentarily render some html code
|
||||
// we should only intact with that div through the `whenMounted` function
|
||||
static template = xml`
|
||||
<div style="left: -1000px; position: absolute;">
|
||||
<div t-ref="ref">
|
||||
<t t-if="props.comp.component" t-component="props.comp.component" t-props="props.comp.props"/>
|
||||
</div>
|
||||
<div class="render-container" />
|
||||
</div>`;
|
||||
setup() {
|
||||
this.ref = useRef("ref");
|
||||
onRendered(async () => {
|
||||
// this timeout is needed in order to wait for the
|
||||
// component to arrive in it's final state
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
this.props.onRendered(this.ref?.el?.firstChild);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This service does for components what renderToElement does for templates.
|
||||
* In order to obtain the html code that represents a component, we need to
|
||||
* actually mount the respective component in the dom.
|
||||
*/
|
||||
const renderService = {
|
||||
dependencies: [],
|
||||
start() {
|
||||
const toBeRenderedComponentData = reactive({});
|
||||
let elem, resolver;
|
||||
registry.category("main_components").add("RenderContainer", {
|
||||
Component: RenderContainer,
|
||||
props: {
|
||||
comp: toBeRenderedComponentData,
|
||||
onRendered: (el) => {
|
||||
elem = el;
|
||||
resolver?.();
|
||||
// after obtaining the html code, we need to flush the
|
||||
// contents of the RenderContainer component
|
||||
toBeRenderedComponentData.component = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
const toHtml = async (component, props) => {
|
||||
Object.assign(toBeRenderedComponentData, { component, props });
|
||||
// we wait for the RenderContainer component to actually
|
||||
// render our component
|
||||
await new Promise((r) => (resolver = r));
|
||||
return elem;
|
||||
};
|
||||
const toCanvas = async (component, props, options) => {
|
||||
return htmlToCanvas(await toHtml(component, props), options);
|
||||
};
|
||||
const toJpeg = async (component, props, options) => {
|
||||
const canvas = await toCanvas(component, props, options);
|
||||
return canvas.toDataURL("image/jpeg").replace("data:image/jpeg;base64,", "");
|
||||
};
|
||||
const whenMounted = async ({ el, container, callback }) => {
|
||||
container ||= document.querySelector(".render-container");
|
||||
return await applyWhenMounted({ el, container, callback });
|
||||
};
|
||||
return { toHtml, toCanvas, toJpeg, whenMounted };
|
||||
},
|
||||
};
|
||||
registry.category("services").add("renderer", renderService);
|
||||
|
||||
/**
|
||||
* This function is meant to be used for the cases where an
|
||||
* action needs to be performed based on some html code, but
|
||||
* that html code has to be in the dom for the action to be
|
||||
* performed. ( for example calling html2canvas )
|
||||
*/
|
||||
const applyWhenMounted = async ({ el, container, callback }) => {
|
||||
const elClone = el.cloneNode(true);
|
||||
container.appendChild(elClone);
|
||||
const res = await callback(elClone);
|
||||
elClone.remove();
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function assumes that the `renderer` service is available.
|
||||
*/
|
||||
export const htmlToCanvas = async (el, options) => {
|
||||
el.classList.add(options.addClass || "");
|
||||
// html2canvas expects the given element to be in the DOM
|
||||
return await applyWhenMounted({
|
||||
el,
|
||||
container: document.querySelector(".render-container"),
|
||||
callback: async (el) =>
|
||||
await html2canvas(el, {
|
||||
height: Math.ceil(el.clientHeight),
|
||||
width: Math.ceil(el.clientWidth),
|
||||
scale: 1,
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -15,10 +15,10 @@ import { ConnectionLostError } from "@web/core/network/rpc_service";
|
||||
import { PaymentScreenPaymentLines } from "@point_of_sale/app/screens/payment_screen/payment_lines/payment_lines";
|
||||
import { PaymentScreenStatus } from "@point_of_sale/app/screens/payment_screen/payment_status/payment_status";
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { Component, useState, useRef, onMounted } from "@odoo/owl";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { Component, useState, onMounted } from "@odoo/owl";
|
||||
import { Numpad } from "@point_of_sale/app/generic_components/numpad/numpad";
|
||||
import { floatIsZero } from "@web/core/utils/numbers";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
|
||||
export class PaymentScreen extends Component {
|
||||
static template = "point_of_sale.PaymentScreen";
|
||||
@@ -36,7 +36,7 @@ export class PaymentScreen extends Component {
|
||||
this.report = useService("report");
|
||||
this.notification = useService("pos_notification");
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
this.orderReceipt = useRef("order-receipt");
|
||||
this.printer = useService("printer");
|
||||
this.payment_methods_from_config = this.pos.payment_methods.filter((method) =>
|
||||
this.pos.config.payment_method_ids.includes(method.id)
|
||||
);
|
||||
@@ -357,16 +357,12 @@ export class PaymentScreen extends Component {
|
||||
: true;
|
||||
|
||||
if (this.hardwareProxy.printer && invoiced_finalized) {
|
||||
const orderReceipt = renderToElement("point_of_sale.OrderReceipt", {
|
||||
receiptData: this.pos.get_order().getOrderReceiptEnv(),
|
||||
pos: this.pos,
|
||||
env: this.env,
|
||||
const printResult = await this.printer.print(OrderReceipt, {
|
||||
data: this.pos.get_order().export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
});
|
||||
|
||||
// Need to await to have the result in case of automatic skip screen.
|
||||
const printResult = await this.hardwareProxy.printer.printReceipt(orderReceipt);
|
||||
|
||||
if (printResult.successful && this.pos.config.iface_print_skip_screen) {
|
||||
if (printResult && this.pos.config.iface_print_skip_screen) {
|
||||
this.pos.removeOrder(this.currentOrder);
|
||||
this.pos.add_new_order();
|
||||
nextScreen = "ProductScreen";
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
|
||||
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* This relies on the assumption that there is a reference to
|
||||
* `order-receipt` so it is important to declare a `t-ref` to
|
||||
* `order-receipt` in the template of the Component that extends
|
||||
* this abstract component.
|
||||
*/
|
||||
export class AbstractReceiptScreen extends Component {
|
||||
setup() {
|
||||
this.orderReceipt = useRef("order-receipt");
|
||||
this.popup = useService("popup");
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
}
|
||||
async _printReceipt() {
|
||||
if (this.hardwareProxy.printer) {
|
||||
const printResult = await this.hardwareProxy.printer.printReceipt(
|
||||
this.orderReceipt.el.firstChild.firstChild
|
||||
);
|
||||
if (printResult.successful) {
|
||||
return true;
|
||||
} else {
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: printResult.message.title,
|
||||
body: printResult.message.body,
|
||||
});
|
||||
const { confirmed } = await this.popup.add(ConfirmPopup, {
|
||||
title: printResult.message.title,
|
||||
body: _t("Do you want to print using the web printer?"),
|
||||
});
|
||||
if (confirmed) {
|
||||
// We want to call the _printWeb when the popup is fully gone
|
||||
// from the screen which happens after the next animation frame.
|
||||
await new Promise(requestAnimationFrame);
|
||||
return await this._printWeb();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return await this._printWeb();
|
||||
}
|
||||
}
|
||||
async _printWeb() {
|
||||
try {
|
||||
window.print();
|
||||
return true;
|
||||
} catch {
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: _t("Printing is not supported on some browsers"),
|
||||
body: _t(
|
||||
"Printing is not supported on some browsers due to no default printing protocol " +
|
||||
"is available. It is possible to print your tickets by making use of an IoT Box."
|
||||
),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { Orderline } from "@point_of_sale/app/generic_components/orderline/orderline";
|
||||
import { OrderWidget } from "@point_of_sale/app/generic_components/order_widget/order_widget";
|
||||
import { ReceiptHeader } from "@point_of_sale/app/screens/receipt_screen/receipt/receipt_header/receipt_header";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
|
||||
export class OrderReceipt extends Component {
|
||||
static template = "point_of_sale.OrderReceipt";
|
||||
static components = {
|
||||
Orderline,
|
||||
OrderWidget,
|
||||
ReceiptHeader,
|
||||
};
|
||||
static props = {
|
||||
data: Object,
|
||||
formatCurrency: Function,
|
||||
};
|
||||
omit(...args) {
|
||||
return omit(...args);
|
||||
}
|
||||
}
|
||||
+52
-147
@@ -1,146 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.OrderReceipt">
|
||||
<div class="pos-receipt">
|
||||
<t t-if="receiptData.receipt.company.logo">
|
||||
<img class="pos-receipt-logo" t-att-src="receiptData.receipt.company.logo" alt="Logo"/>
|
||||
<br/>
|
||||
</t>
|
||||
<t t-if="!receiptData.receipt.company.logo">
|
||||
<h2 class="pos-receipt-center-align">
|
||||
<t t-esc="receiptData.receipt.company.name" />
|
||||
</h2>
|
||||
<br/>
|
||||
</t>
|
||||
<div class="pos-receipt-contact">
|
||||
<t t-if="receiptData.receipt.company.contact_address">
|
||||
<div><t t-esc="receiptData.receipt.company.contact_address" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.company.phone">
|
||||
<div>Tel:<t t-esc="receiptData.receipt.company.phone" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.company.vat">
|
||||
<div><t t-esc="receiptData.receipt.company.vat_label"/>:<t t-esc="receiptData.receipt.company.vat" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.company.email">
|
||||
<div><t t-esc="receiptData.receipt.company.email" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.company.website">
|
||||
<div><t t-esc="receiptData.receipt.company.website" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.header">
|
||||
<div style="white-space:pre-line"><t t-esc="receiptData.receipt.header" /></div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.cashier">
|
||||
<div class="cashier">
|
||||
<div>--------------------------------</div>
|
||||
<div>Served by <t t-esc="receiptData.receipt.cashier" /></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<br /><br />
|
||||
|
||||
<!-- Orderlines -->
|
||||
<div class="orderlines">
|
||||
<t t-call="point_of_sale.OrderLinesReceipt"/>
|
||||
</div>
|
||||
<ReceiptHeader data="props.data.headerData" />
|
||||
<OrderWidget lines="props.data.orderlines" t-slot-scope="scope">
|
||||
<t t-set="line" t-value="scope.line"/>
|
||||
<Orderline line="omit(scope.line, 'customerNote')">
|
||||
<li t-if="line.customerNote" class="customer-note w-100 p-2 my-1 rounded text-break">
|
||||
<i class="fa fa-sticky-note me-1" role="img" aria-label="Customer Note" title="Customer Note"/>
|
||||
<t t-esc="line.customerNote" />
|
||||
</li>
|
||||
<ul t-if="line.pack_lot_lines">
|
||||
<li t-foreach="line.get_lot_lines()" t-as="lot" t-key="lot.cid">
|
||||
SN <t t-esc="lot.lot_name"/>
|
||||
</li>
|
||||
</ul>
|
||||
</Orderline>
|
||||
</OrderWidget>
|
||||
|
||||
<!-- Total -->
|
||||
<div class="pos-receipt-right-align">--------</div>
|
||||
<br/>
|
||||
<div class="pos-receipt-amount">
|
||||
TOTAL
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.amount_total)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
<t t-if="receiptData.receipt.total_rounded != receiptData.receipt.total_with_tax">
|
||||
<t t-if="props.data.rounding_applied">
|
||||
<div class="pos-receipt-amount">
|
||||
Rounding
|
||||
<span t-esc='env.utils.formatCurrency(receiptData.receipt.rounding_applied)' class="pos-receipt-right-align"/>
|
||||
<span t-esc='props.formatCurrency(props.data.rounding_applied)' class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
<div class="pos-receipt-amount">
|
||||
To Pay
|
||||
<span t-esc='env.utils.formatCurrency(receiptData.receipt.total_rounded)' class="pos-receipt-right-align"/>
|
||||
<span t-esc='props.formatCurrency(props.data.amount_total + props.data.rounding_applied)' class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</t>
|
||||
<br/><br/>
|
||||
|
||||
<!-- Payment Lines -->
|
||||
|
||||
<t t-foreach="receiptData.receipt.paymentlines" t-as="line" t-key="line.cid">
|
||||
<div>
|
||||
<t t-esc="line.name" />
|
||||
<span t-esc="env.utils.formatCurrency(line.amount, false)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</t>
|
||||
<br/>
|
||||
|
||||
<div class="pos-receipt-amount receipt-change">
|
||||
CHANGE
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.change)" class="pos-receipt-right-align"/>
|
||||
<div class="paymentlines" t-foreach="props.data.paymentlines" t-as="line" t-key="line_index">
|
||||
<t t-esc="line.name" />
|
||||
<span t-esc="props.formatCurrency(line.amount, false)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
|
||||
<div class="pos-receipt-amount receipt-change mt-2">
|
||||
CHANGE
|
||||
<span t-esc="props.formatCurrency(props.data.change)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<!-- Extra Payment Info -->
|
||||
|
||||
<t t-if="receiptData.receipt.total_discount">
|
||||
<t t-if="props.data.total_discount">
|
||||
<div>
|
||||
Discounts
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_discount)" class="pos-receipt-right-align"/>
|
||||
<span t-esc="props.formatCurrency(props.data.total_discount)" class="pos-receipt-right-align"/>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="receiptData.receipt.tax_details.length > 0" class="pos-receipt-taxes">
|
||||
<div t-if="props.data.tax_details.length > 0" class="pos-receipt-taxes">
|
||||
<span />
|
||||
<span>VAT%</span>
|
||||
<span>VAT</span>
|
||||
<span>ExVAT</span>
|
||||
<span>Total</span>
|
||||
<t t-foreach="receiptData.receipt.tax_details" t-as="tax" t-key="tax.tax.id">
|
||||
<t t-foreach="props.data.tax_details" t-as="tax" t-key="tax.tax.id">
|
||||
<span />
|
||||
<span><t t-esc="tax.tax.amount"/> %</span>
|
||||
<span t-esc="env.utils.formatCurrency(tax.amount, false)" />
|
||||
<span t-esc="env.utils.formatCurrency(tax.base, false)" />
|
||||
<span t-esc="env.utils.formatCurrency(tax.amount + tax.base, false)" />
|
||||
<span t-esc="props.formatCurrency(tax.amount, false)" />
|
||||
<span t-esc="props.formatCurrency(tax.base, false)" />
|
||||
<span t-esc="props.formatCurrency(tax.amount + tax.base, false)" />
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.tax_details.length > 1">
|
||||
<t t-if="props.data.tax_details.length > 1">
|
||||
<span />
|
||||
<span />
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_tax, false)" />
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_without_tax, false)" />
|
||||
<span t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax, false)" />
|
||||
<span t-esc="props.formatCurrency(props.data.amount_tax, false)" />
|
||||
<span t-esc="props.formatCurrency(props.data.total_without_tax, false)" />
|
||||
<span t-esc="props.formatCurrency(props.data.amount_total, false)" />
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div class="before-footer" />
|
||||
|
||||
<div t-if="receiptData.receipt.pos_qr_code">
|
||||
<div t-if="props.data.pos_qr_code">
|
||||
<br /><br />
|
||||
<div class="pos-receipt-order-data">
|
||||
<div class="pos-receipt-order-data mb-2">
|
||||
Scan me to request an invoice for your purchase.
|
||||
</div>
|
||||
<img id="posqrcode" t-att-src="receiptData.receipt.pos_qr_code" class="pos-receipt-logo"/>
|
||||
<img id="posqrcode" t-att-src="props.data.pos_qr_code" class="pos-receipt-logo"/>
|
||||
</div>
|
||||
|
||||
<div t-if="receiptData.receipt.ticket_code">
|
||||
<div t-if="props.data.ticket_code">
|
||||
<br /><br />
|
||||
<div class="pos-receipt-order-data">
|
||||
You can go to <t t-out="receiptData.receipt.base_url"/>/pos/ticket and use the code below to request an invoice online
|
||||
You can go to <t t-out="props.data.base_url"/>/pos/ticket and use the code below to request an invoice online
|
||||
</div>
|
||||
<div class="pos-receipt-order-data">
|
||||
Unique Code: <t t-out="receiptData.receipt.ticket_code"/>
|
||||
Unique Code: <t t-out="props.data.ticket_code"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div t-if="receiptData.receipt.footer" class="pos-receipt-center-align" style="white-space:pre-line">
|
||||
<br/>
|
||||
<t t-esc="receiptData.receipt.footer" />
|
||||
<div t-if="props.data.footer" class="pos-receipt-center-align" style="white-space:pre-line">
|
||||
<br/>
|
||||
<t t-esc="props.data.footer" />
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div class="after-footer">
|
||||
<t t-foreach="receiptData.receipt.paymentlines" t-as="line" t-key="line_index">
|
||||
<t t-foreach="props.data.paymentlines" t-as="line" t-key="line_index">
|
||||
<t t-if="line.ticket">
|
||||
<br />
|
||||
<div class="pos-payment-terminal-receipt">
|
||||
@@ -151,81 +119,18 @@
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<t t-if="receiptData.shippingDate">
|
||||
<t t-if="props.shippingDate">
|
||||
<div class="pos-receipt-order-data">
|
||||
Expected delivery:
|
||||
<div><t t-esc="receiptData.shippingDate" /></div>
|
||||
<div><t t-esc="props.shippingDate" /></div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<br/>
|
||||
<div class="pos-receipt-order-data">
|
||||
<div><t t-esc="receiptData.receipt.name" /></div>
|
||||
<t t-if="receiptData.receipt.date.localestring">
|
||||
<div><t t-esc="receiptData.receipt.date.localestring" /></div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div><t t-esc="receiptData.receipt.date.validation_date" /></div>
|
||||
</t>
|
||||
<div t-esc="props.data.name" />
|
||||
<div t-esc="props.data.date" />
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="point_of_sale.OrderLinesReceipt">
|
||||
<div id="receipt-orderline" t-foreach="receiptData.receipt.orderlines" t-as="line" t-key="line.id" t-att-class="{'border-start border-3 ps-1': line.isPartOfCombo }">
|
||||
<div t-esc="line.product_name_wrapped[0]" />
|
||||
<span>
|
||||
<t t-foreach="line.product_name_wrapped.slice(1)" t-as="wrapped_line" t-key="wrapped_line_index">
|
||||
<t t-esc="wrapped_line"/>
|
||||
</t>
|
||||
</span>
|
||||
<t t-if="line.display_discount_policy == 'without_discount' and line.price != line.price_lst">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<t t-esc="env.utils.formatCurrency(line.price_lst, false)" />
|
||||
->
|
||||
<t t-esc="env.utils.formatCurrency(line.price, false)" />
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="line.discount !== 0">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<t t-if="pos.config.iface_tax_included === 'total'">
|
||||
<t t-esc="env.utils.formatCurrency(line.price_with_tax_before_discount, false)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="env.utils.formatCurrency(line.unitDisplayPriceBeforeDiscount, false)"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="line.discount !== 0">
|
||||
<div class="pos-receipt-left-padding">
|
||||
Discount: <t t-esc="line.discount" />%
|
||||
</div>
|
||||
</t>
|
||||
<div class="pos-receipt-left-padding">
|
||||
<t t-esc="Math.round(line.quantity * Math.pow(10, pos.dp['Product Unit of Measure'])) / Math.pow(10, pos.dp['Product Unit of Measure'])"/>
|
||||
<t t-if="!line.is_in_unit" t-esc="line.unit_name" />
|
||||
x
|
||||
<t t-esc="env.utils.formatCurrency(line.price_display_one)" />
|
||||
<div class="price_display pos-receipt-right-align">
|
||||
<span t-esc="env.utils.formatCurrency(line.price_display, false)" />
|
||||
<span class="tax-letter" />
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="line.customer_note">
|
||||
<div class="pos-receipt-left-padding pos-receipt-customer-note">
|
||||
<t t-esc="line.customer_note"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="line.pack_lot_lines">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<ul>
|
||||
<t t-foreach="line.pack_lot_lines" t-as="lot" t-key="lot.cid">
|
||||
<li>
|
||||
SN <t t-esc="lot.lot_name"/>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
export class ReceiptHeader extends Component {
|
||||
static template = "point_of_sale.ReceiptHeader";
|
||||
static props = {
|
||||
data: {
|
||||
type: Object,
|
||||
shape: {
|
||||
company: Object,
|
||||
header: { type: [String, { value: false }], optional: true },
|
||||
cashier: { type: String, optional: true },
|
||||
trackingNumber: { type: String, optional: true },
|
||||
"*": true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="point_of_sale.ReceiptHeader">
|
||||
<img t-attf-src="/web/image?model=res.company&id={{props.data.company.id}}&field=logo" alt="Logo" class="pos-receipt-logo"/>
|
||||
<br/>
|
||||
<div class="pos-receipt-contact">
|
||||
<!-- contact address -->
|
||||
<div t-if="props.data.company.partner_id?.[1]" t-esc="props.data.company.partner_id[1]" />
|
||||
<t t-if="props.data.company.phone">
|
||||
<div>Tel:<t t-esc="props.data.company.phone" /></div>
|
||||
</t>
|
||||
<t t-if="props.data.company.vat">
|
||||
<div><t t-esc="props.data.company.country?.vat_label || 'Tax ID'"/>: <t t-esc="props.data.company.vat" /></div>
|
||||
</t>
|
||||
<div t-if="props.data.company.email" t-esc="props.data.company.email" />
|
||||
<div t-if="props.data.company.website" t-esc="props.data.company.website" />
|
||||
<div t-if="props.data.header" style="white-space:pre-line" t-esc="props.data.header" />
|
||||
<div t-if="props.data.cashier" class="cashier">
|
||||
<div>--------------------------------</div>
|
||||
<div>Served by <t t-esc="props.data.cashier" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
</t>
|
||||
</templates>
|
||||
@@ -2,24 +2,25 @@
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useErrorHandlers } from "@point_of_sale/app/utils/hooks";
|
||||
import { AbstractReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/abstract_receipt_screen";
|
||||
import { OfflineErrorPopup } from "@point_of_sale/app/errors/popups/offline_error_popup";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useRef, useState, onWillStart } from "@odoo/owl";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
import { useRef, useState, onWillStart, Component } from "@odoo/owl";
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { BasePrinter } from "@point_of_sale/app/printer/base_printer";
|
||||
|
||||
export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
export class ReceiptScreen extends Component {
|
||||
static template = "point_of_sale.ReceiptScreen";
|
||||
static components = { OrderReceipt };
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.printer = useService("printer");
|
||||
useErrorHandlers();
|
||||
this.ui = useState(useService("ui"));
|
||||
this.orm = useService("orm");
|
||||
this.orderReceipt = useRef("order-receipt");
|
||||
this.renderer = useService("renderer");
|
||||
this.buttonMailReceipt = useRef("order-mail-receipt-button");
|
||||
this.buttonPrintReceipt = useRef("order-print-receipt-button");
|
||||
this.currentOrder = this.pos.get_order();
|
||||
@@ -36,9 +37,6 @@ export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
}
|
||||
});
|
||||
}
|
||||
get receiptData() {
|
||||
return this.pos.get_order().getOrderReceiptEnv();
|
||||
}
|
||||
_addNewOrder() {
|
||||
this.pos.add_new_order();
|
||||
}
|
||||
@@ -118,11 +116,17 @@ export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
}
|
||||
async printReceipt() {
|
||||
this.buttonPrintReceipt.el.className = "fa fa-fw fa-spin fa-circle-o-notch";
|
||||
const currentOrder = this.currentOrder;
|
||||
const isPrinted = await this._printReceipt();
|
||||
const isPrinted = await this.printer.print(
|
||||
OrderReceipt,
|
||||
{
|
||||
data: this.pos.get_order().export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
},
|
||||
{ webPrintFallback: true }
|
||||
);
|
||||
|
||||
if (isPrinted) {
|
||||
currentOrder._printed = true;
|
||||
this.currentOrder._printed = true;
|
||||
}
|
||||
|
||||
if (this.buttonPrintReceipt.el) {
|
||||
@@ -130,16 +134,24 @@ export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
}
|
||||
}
|
||||
async _sendReceiptToCustomer() {
|
||||
const printer = new BasePrinter();
|
||||
const receiptString = this.orderReceipt.el.firstChild;
|
||||
const ticketImage = await printer.htmlToImg(receiptString);
|
||||
const order = this.currentOrder;
|
||||
const partner = order.get_partner();
|
||||
const orderName = order.get_name();
|
||||
const partner = this.currentOrder.get_partner();
|
||||
const orderPartner = {
|
||||
email: this.orderUiState.inputEmail,
|
||||
name: partner ? partner.name : this.orderUiState.inputEmail,
|
||||
};
|
||||
await this.sendToCustomer(orderPartner, "action_receipt_to_customer");
|
||||
}
|
||||
async sendToCustomer(orderPartner, methodName) {
|
||||
const ticketImage = await this.renderer.toJpeg(
|
||||
OrderReceipt,
|
||||
{
|
||||
data: this.pos.get_order().export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
},
|
||||
{ addClass: "pos-receipt-print" }
|
||||
);
|
||||
const order = this.currentOrder;
|
||||
const orderName = order.get_name();
|
||||
const order_server_id = this.pos.validated_orders_name_server_id_map[orderName];
|
||||
if (!order_server_id) {
|
||||
this.popup.add(OfflineErrorPopup, {
|
||||
@@ -150,7 +162,7 @@ export class ReceiptScreen extends AbstractReceiptScreen {
|
||||
});
|
||||
return Promise.reject();
|
||||
}
|
||||
await this.orm.call("pos.order", "action_receipt_to_customer", [
|
||||
await this.orm.call("pos.order", methodName, [
|
||||
[order_server_id],
|
||||
orderName,
|
||||
orderPartner,
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.pos .receipt-screen .pos-receipt-container, .pos .receipt-screen .pos-receipt-container * {
|
||||
.pos .receipt-screen .pos-receipt-container, .pos .receipt-screen .pos-receipt-container *, .render-container * {
|
||||
visibility: visible;
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
@@ -45,4 +45,9 @@
|
||||
font-size: 14px !important;
|
||||
width: 266px !important;
|
||||
}
|
||||
.render-container {
|
||||
position: absolute !important;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pos-receipt-container bg-200 text-center overflow-auto" t-ref="order-receipt">
|
||||
<div class="pos-receipt-container bg-200 text-center overflow-auto">
|
||||
<div class="d-inline-block m-3 p-3 border rounded bg-view text-start overflow-hidden">
|
||||
<t t-call="point_of_sale.OrderReceipt" />
|
||||
<OrderReceipt data="pos.get_order().export_for_printing()" formatCurrency="env.utils.formatCurrency" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+16
-8
@@ -1,27 +1,35 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { AbstractReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/abstract_receipt_screen";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ReprintReceiptScreen extends AbstractReceiptScreen {
|
||||
export class ReprintReceiptScreen extends Component {
|
||||
static template = "point_of_sale.ReprintReceiptScreen";
|
||||
static components = { OrderReceipt };
|
||||
static storeOnOrder = false;
|
||||
static props = ["order"];
|
||||
setup() {
|
||||
super.setup();
|
||||
this.pos = usePos();
|
||||
this.printer = useService("printer");
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.pos.showScreen("TicketScreen", { reuseSavedUIState: true });
|
||||
}
|
||||
|
||||
async tryReprint() {
|
||||
await this._printReceipt();
|
||||
}
|
||||
|
||||
get receiptData() {
|
||||
return this.props.order.getOrderReceiptEnv();
|
||||
tryReprint() {
|
||||
this.printer.print(
|
||||
OrderReceipt,
|
||||
{
|
||||
data: this.props.order.export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
},
|
||||
{ webPrintFallback: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
<button class="button print btn btn-lg btn-primary" t-on-click="tryReprint">
|
||||
<i class="fa fa-print ms-2"></i> Print Receipt
|
||||
</button>
|
||||
<div class="pos-receipt-container mt-2 bg-200 text-center overflow-y-auto" t-ref="order-receipt">
|
||||
<div class="pos-receipt-container mt-2 bg-200 text-center overflow-y-auto">
|
||||
<div class="d-inline-block m-3 p-3 border rounded bg-view text-start overflow-hidden">
|
||||
<t t-call="point_of_sale.OrderReceipt" />
|
||||
<OrderReceipt data="props.order.export_for_printing()" formatCurrency="env.utils.formatCurrency" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+7
-16
@@ -2,8 +2,8 @@
|
||||
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
import { useAsyncLockedMethod } from "@point_of_sale/app/utils/hooks";
|
||||
|
||||
export class ReprintReceiptButton extends Component {
|
||||
@@ -11,7 +11,7 @@ export class ReprintReceiptButton extends Component {
|
||||
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
this.printer = useService("printer");
|
||||
this.click = useAsyncLockedMethod(this.click);
|
||||
}
|
||||
|
||||
@@ -19,19 +19,10 @@ export class ReprintReceiptButton extends Component {
|
||||
if (!this.props.order) {
|
||||
return;
|
||||
}
|
||||
if (this.hardwareProxy.printer) {
|
||||
const orderReceipt = renderToElement("point_of_sale.OrderReceipt", {
|
||||
receiptData: {
|
||||
...this.props.order.getOrderReceiptEnv(),
|
||||
},
|
||||
pos: this.pos,
|
||||
env: this.env,
|
||||
});
|
||||
|
||||
// Need to await to have the result in case of automatic skip screen.
|
||||
await this.hardwareProxy.printer.printReceipt(orderReceipt);
|
||||
} else {
|
||||
this.pos.showScreen("ReprintReceiptScreen", { order: this.props.order });
|
||||
}
|
||||
// Need to await to have the result in case of automatic skip screen.
|
||||
(await this.printer.print(OrderReceipt, {
|
||||
data: this.props.order.export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
})) || this.pos.showScreen("ReprintReceiptScreen", { order: this.props.order });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_pop
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { ProductCustomAttribute } from "./models/product_custom_attribute";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
@@ -792,68 +793,7 @@ export class Orderline extends PosModel {
|
||||
combo_parent_id: this.comboParent?.id || this.comboParent?.cid,
|
||||
};
|
||||
}
|
||||
//used to create a json of the ticket, to be sent to the printer
|
||||
export_for_printing() {
|
||||
return {
|
||||
id: this.id,
|
||||
quantity: this.get_quantity(),
|
||||
unit_name: this.get_unit().name,
|
||||
is_in_unit: this.get_unit().id == this.pos.uom_unit_id,
|
||||
price: this.get_unit_display_price(),
|
||||
discount: this.get_discount(),
|
||||
product_name: this.get_product().display_name,
|
||||
product_name_wrapped: this.generate_wrapped_product_name(),
|
||||
price_lst: this.get_taxed_lst_unit_price(),
|
||||
fixed_lst_price: this.get_fixed_lst_price(),
|
||||
price_type: this.price_type,
|
||||
display_discount_policy: this.display_discount_policy(),
|
||||
price_display_one: this.get_display_price_one(),
|
||||
price_display: this.get_display_price(),
|
||||
price_with_tax: this.get_price_with_tax(),
|
||||
price_without_tax: this.get_price_without_tax(),
|
||||
price_with_tax_before_discount: this.get_price_with_tax_before_discount(),
|
||||
tax: this.get_tax(),
|
||||
tax_details: this.get_tax_details(),
|
||||
product_description: this.get_product().description,
|
||||
product_description_sale: this.get_product().description_sale,
|
||||
pack_lot_lines: this.get_lot_lines(),
|
||||
customer_note: this.get_customer_note(),
|
||||
taxed_lst_unit_price: this.get_taxed_lst_unit_price(),
|
||||
isPartOfCombo: this.isPartOfCombo(),
|
||||
custom_attribute_value_ids: this.custom_attribute_value_ids,
|
||||
unitDisplayPriceBeforeDiscount: this.getUnitDisplayPriceBeforeDiscount(),
|
||||
};
|
||||
}
|
||||
generate_wrapped_product_name() {
|
||||
var MAX_LENGTH = 24; // 40 * line ratio of .6
|
||||
var wrapped = [];
|
||||
var name = this.get_full_product_name();
|
||||
var current_line = "";
|
||||
|
||||
while (name.length > 0) {
|
||||
var space_index = name.indexOf(" ");
|
||||
|
||||
if (space_index === -1) {
|
||||
space_index = name.length;
|
||||
}
|
||||
|
||||
if (current_line.length + space_index > MAX_LENGTH) {
|
||||
if (current_line.length) {
|
||||
wrapped.push(current_line);
|
||||
}
|
||||
current_line = "";
|
||||
}
|
||||
|
||||
current_line += name.slice(0, space_index + 1);
|
||||
name = name.slice(space_index + 1);
|
||||
}
|
||||
|
||||
if (current_line.length) {
|
||||
wrapped.push(current_line);
|
||||
}
|
||||
|
||||
return wrapped;
|
||||
}
|
||||
// changes the base price of the product for this orderline
|
||||
set_unit_price(price) {
|
||||
this.order.assert_editable();
|
||||
@@ -1296,7 +1236,6 @@ export class Payment extends PosModel {
|
||||
//exports as JSON for receipt printing
|
||||
export_for_printing() {
|
||||
return {
|
||||
cid: this.cid,
|
||||
amount: this.get_amount(),
|
||||
name: this.name,
|
||||
ticket: this.ticket,
|
||||
@@ -1556,97 +1495,42 @@ export class Order extends PosModel {
|
||||
}
|
||||
return json;
|
||||
}
|
||||
_exportShippingDateForPrinting() {
|
||||
const shippingDate = DateTime.fromJSDate(new Date(this.shippingDate));
|
||||
return formatDate(shippingDate);
|
||||
}
|
||||
export_for_printing() {
|
||||
var orderlines = [];
|
||||
|
||||
this.orderlines.forEach(function (orderline) {
|
||||
orderlines.push(orderline.export_for_printing());
|
||||
});
|
||||
|
||||
// If order is locked (paid), the 'change' is saved as negative payment,
|
||||
// and is flagged with is_change = true. A receipt that is printed first
|
||||
// time doesn't show this negative payment so we filter it out.
|
||||
var paymentlines = this.paymentlines
|
||||
.filter(function (paymentline) {
|
||||
return !paymentline.is_change;
|
||||
})
|
||||
.map(function (paymentline) {
|
||||
return paymentline.export_for_printing();
|
||||
});
|
||||
const partner = this.partner;
|
||||
const cashier = this.cashier;
|
||||
const company = this.pos.company;
|
||||
const date = new Date();
|
||||
|
||||
var receipt = {
|
||||
orderlines: orderlines,
|
||||
paymentlines: paymentlines,
|
||||
subtotal: this.get_subtotal(),
|
||||
total_with_tax: this.get_total_with_tax(),
|
||||
total_rounded: this.get_total_with_tax() + this.get_rounding_applied(),
|
||||
const paymentlines = this.paymentlines
|
||||
.filter((p) => !p.is_change)
|
||||
.map((p) => p.export_for_printing());
|
||||
this.receiptDate ||= formatDateTime(luxon.DateTime.now());
|
||||
return {
|
||||
orderlines: this.orderlines.map((l) => omit(l.getDisplayData(), "internalNote")),
|
||||
paymentlines,
|
||||
amount_total: this.get_total_with_tax(),
|
||||
total_without_tax: this.get_total_without_tax(),
|
||||
total_tax: this.get_total_tax(),
|
||||
amount_tax: this.get_total_tax(),
|
||||
total_paid: this.get_total_paid(),
|
||||
total_discount: this.get_total_discount(),
|
||||
rounding_applied: this.get_rounding_applied(),
|
||||
tax_details: this.get_tax_details(),
|
||||
change: this.locked ? this.amount_return : this.get_change(),
|
||||
name: this.get_name(),
|
||||
partner: partner ? partner : null,
|
||||
invoice_id: null, //TODO
|
||||
cashier: cashier ? cashier.name : null,
|
||||
precision: {
|
||||
price: 2,
|
||||
money: 2,
|
||||
quantity: 3,
|
||||
},
|
||||
date: {
|
||||
year: date.getFullYear(),
|
||||
month: date.getMonth(),
|
||||
date: date.getDate(), // day of the month
|
||||
day: date.getDay(), // day of the week
|
||||
hour: date.getHours(),
|
||||
minute: date.getMinutes(),
|
||||
isostring: date.toISOString(),
|
||||
localestring: formatDateTime(luxon.DateTime.now()),
|
||||
date_order: this.date_order,
|
||||
},
|
||||
company: {
|
||||
email: company.email,
|
||||
website: company.website,
|
||||
company_registry: company.company_registry,
|
||||
contact_address: company.partner_id[1],
|
||||
vat: company.vat,
|
||||
vat_label: (company.country && company.country.vat_label) || _t("Tax ID"),
|
||||
name: company.name,
|
||||
phone: company.phone,
|
||||
logo: this.pos.company_logo_base64,
|
||||
},
|
||||
currency: this.pos.currency,
|
||||
cashier: this.cashier?.name,
|
||||
date: this.receiptDate,
|
||||
pos_qr_code:
|
||||
this.pos.company.point_of_sale_use_ticket_qr_code &&
|
||||
qrCodeSrc(
|
||||
`${this.pos.base_url}/pos/ticket/validate?access_token=${this.access_token}`
|
||||
),
|
||||
ticket_code: this.pos.company.point_of_sale_ticket_unique_code
|
||||
? this.ticketCode
|
||||
: false,
|
||||
ticket_code: this.pos.company.point_of_sale_ticket_unique_code && this.ticketCode,
|
||||
base_url: this.pos.base_url,
|
||||
footer: this.pos.config.receipt_footer,
|
||||
// FIXME: isn't there a better way to handle this date?
|
||||
shippingDate:
|
||||
this.shippingDate && formatDate(DateTime.fromJSDate(new Date(this.shippingDate))),
|
||||
headerData: this.pos.getReceiptHeaderData(),
|
||||
};
|
||||
|
||||
const isHeaderOrFooter = this.pos.config.is_header_or_footer;
|
||||
receipt.header = (isHeaderOrFooter && this.pos.config.receipt_header) || "";
|
||||
receipt.footer = (isHeaderOrFooter && this.pos.config.receipt_footer) || "";
|
||||
|
||||
if (!receipt.date.localestring && (!this.state || this.state == "draft")) {
|
||||
receipt.date.localestring = formatDateTime(DateTime.local());
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
// This function send order change to printer
|
||||
// For sending changes to preparation display see sendChanges function.
|
||||
@@ -2751,23 +2635,6 @@ export class Order extends PosModel {
|
||||
wait_for_push_order() {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* @returns {Object} object to use as props for instantiating OrderReceipt.
|
||||
*/
|
||||
getOrderReceiptEnv() {
|
||||
// Formerly get_receipt_render_env defined in ScreenWidget.
|
||||
const receipt = this.export_for_printing();
|
||||
const getOrderlineTaxes = (line) =>
|
||||
Object.keys(line.tax_details).map((taxId) => this.pos.taxes_by_id[taxId]);
|
||||
return {
|
||||
getOrderlineTaxes: getOrderlineTaxes,
|
||||
order: this,
|
||||
receipt: receipt,
|
||||
orderlines: this.get_orderlines(),
|
||||
paymentlines: this.get_paymentlines(),
|
||||
shippingDate: this.shippingDate ? this._exportShippingDateForPrinting() : false,
|
||||
};
|
||||
}
|
||||
updatePricelistAndFiscalPosition(newPartner) {
|
||||
let newPartnerPricelist, newPartnerFiscalPosition;
|
||||
const defaultFiscalPosition = this.pos.fiscal_positions.find(
|
||||
|
||||
@@ -276,7 +276,6 @@ export class PosStore extends Reactive {
|
||||
this.fiscal_positions = loadedData["account.fiscal.position"];
|
||||
this.base_url = loadedData["base_url"];
|
||||
this.pos_has_valid_product = loadedData["pos_has_valid_product"];
|
||||
await this._loadPictures();
|
||||
await this._loadPosPrinters(loadedData["pos.printer"]);
|
||||
this.open_orders_json = loadedData["open_orders"];
|
||||
}
|
||||
@@ -1869,8 +1868,6 @@ export class PosStore extends Reactive {
|
||||
const image = new Image();
|
||||
image.src = `/web/image?model=pos.category&field=image_128&id=${category.id}&unique=${category.write_date}`;
|
||||
}
|
||||
const image = new Image();
|
||||
image.src = "/point_of_sale/static/src/img/backspace.png";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1910,6 +1907,14 @@ export class PosStore extends Reactive {
|
||||
doNotAllowRefundAndSales() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getReceiptHeaderData() {
|
||||
return {
|
||||
company: this.company,
|
||||
cashier: this.get_cashier()?.name,
|
||||
header: this.config.receipt_header,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
PosStore.prototype.electronic_payment_interfaces = {};
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
.pos-receipt-print {
|
||||
width: 512px;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
font-size: 27px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PaymentScreen } from "@point_of_sale/../tests/tours/helpers/PaymentScre
|
||||
import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods";
|
||||
import { ReceiptScreen } from "@point_of_sale/../tests/tours/helpers/ReceiptScreenTourMethods";
|
||||
import { TextAreaPopup } from "@point_of_sale/../tests/tours/helpers/TextAreaPopupTourMethods";
|
||||
import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils";
|
||||
import { getSteps, startSteps, insertSteps } from "@point_of_sale/../tests/tours/helpers/utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
|
||||
|
||||
@@ -159,7 +159,7 @@ registry.category("web_tour.tours").add("FiscalPositionNoTax", {
|
||||
PaymentScreen.check.remainingIs("0.00");
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.isShown();
|
||||
ReceiptScreen.check.noOrderlineContainsDiscount();
|
||||
insertSteps(Order.doesNotHaveLine({ discount: "" }));
|
||||
return getSteps();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,8 @@ import { ReceiptScreen } from "@point_of_sale/../tests/tours/helpers/ReceiptScre
|
||||
import { PaymentScreen } from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods";
|
||||
import { Chrome } from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods";
|
||||
import { NumberPopup } from "@point_of_sale/../tests/tours/helpers/NumberPopupTourMethods";
|
||||
import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils";
|
||||
import { getSteps, startSteps, insertSteps } from "@point_of_sale/../tests/tours/helpers/utils";
|
||||
import * as Order from "@point_of_sale/../tests/tours/helpers/generic_components/OrderWidgetMethods";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { nbsp } from "@web/core/utils/strings";
|
||||
|
||||
@@ -71,7 +72,7 @@ registry
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod("Bank");
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.customerNoteIsThere("Test customer note");
|
||||
insertSteps(Order.hasLine({ customerNote: "Test customer note" }));
|
||||
return getSteps();
|
||||
}
|
||||
});
|
||||
@@ -92,7 +93,7 @@ registry
|
||||
ProductScreen.do.clickPayButton();
|
||||
PaymentScreen.do.clickPaymentMethod("Cash");
|
||||
PaymentScreen.do.clickValidate();
|
||||
ReceiptScreen.check.discountAmountIs("0.7");
|
||||
insertSteps(Order.hasLine({ discount: "0.7" }));
|
||||
return getSteps();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -93,32 +93,6 @@ class Check {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
customerNoteIsThere(note) {
|
||||
return [
|
||||
{
|
||||
trigger: `.receipt-screen .orderlines .pos-receipt-left-padding:contains("${note}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
discountAmountIs(value) {
|
||||
return [
|
||||
{
|
||||
trigger: `.pos-receipt>div:contains("Discounts")>span:contains("${value}")`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
noOrderlineContainsDiscount() {
|
||||
return [
|
||||
{
|
||||
trigger: `.orderlines:not(:contains('->'))`,
|
||||
run: () => {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {
|
||||
|
||||
+8
@@ -27,6 +27,8 @@ export function hasLine({
|
||||
quantity,
|
||||
price,
|
||||
customerNote,
|
||||
comboParent,
|
||||
discount,
|
||||
} = {}) {
|
||||
let trigger = `.order-container .orderline${withClass}`;
|
||||
if (withoutClass) {
|
||||
@@ -44,6 +46,12 @@ export function hasLine({
|
||||
if (customerNote) {
|
||||
trigger += `:has(.info-list .customer-note:contains("${customerNote}"))`;
|
||||
}
|
||||
if (comboParent) {
|
||||
trigger += `:has(.info-list .combo-parent-name:contains("${comboParent}"))`;
|
||||
}
|
||||
if (discount || discount === "") {
|
||||
trigger += `:has(.info-list .discount.em:contains("${discount}"))`;
|
||||
}
|
||||
const args = JSON.stringify(arguments[0]);
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -60,7 +60,9 @@ export function startSteps() {
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function insertSteps(...steps) {
|
||||
steps.push(...steps);
|
||||
}
|
||||
export function getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
@@ -61,15 +61,17 @@ export class EpsonPrinter extends BasePrinter {
|
||||
* @override
|
||||
*/
|
||||
async sendPrintingJob(img) {
|
||||
const res = await $.ajax({
|
||||
url: this.address,
|
||||
const res = await fetch(this.address, {
|
||||
method: "POST",
|
||||
data: img,
|
||||
body: img,
|
||||
});
|
||||
const response = $(res).find("response");
|
||||
const body = await res.text();
|
||||
const parser = new DOMParser();
|
||||
const parsedBody = parser.parseFromString(body, "application/xml");
|
||||
const response = parsedBody.querySelector("response");
|
||||
return {
|
||||
result: response.attr("success") === "true",
|
||||
printerErrorCode: response.attr("code"),
|
||||
result: response.getAttribute("success") === "true",
|
||||
printerErrorCode: response.getAttribute("code"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -152,21 +154,10 @@ export class EpsonPrinter extends BasePrinter {
|
||||
* @override
|
||||
*/
|
||||
getActionError() {
|
||||
const printRes = {
|
||||
successful: false,
|
||||
message: {
|
||||
title: _t("Connection to the printer failed"),
|
||||
body: _t(
|
||||
"Please check if the printer is still connected. \n" +
|
||||
"Some browsers don't allow HTTP calls from websites to devices in the network (for security reasons). " +
|
||||
"If it is the case, you will need to follow Odoo's documentation for " +
|
||||
"'Self-signed certificate for ePOS printers' and 'Secure connection (HTTPS)' to solve the issue"
|
||||
),
|
||||
},
|
||||
};
|
||||
const printRes = super.getResultsError();
|
||||
if (window.location.protocol === "https:") {
|
||||
printRes.message.body += _t(
|
||||
"If you are on a secure server (HTTPS) please make sure you manually accepted the certificate by accessing %s",
|
||||
"If you are on a secure server (HTTPS) please make sure you manually accepted the certificate by accessing %s. ",
|
||||
this.url
|
||||
);
|
||||
}
|
||||
|
||||
+24
-26
@@ -3,40 +3,38 @@
|
||||
|
||||
<t t-name="pos_coupon.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt')]//div[hasclass('before-footer')]" position="inside">
|
||||
<t t-if='receiptData.receipt.loyaltyStats'>
|
||||
<t t-foreach="receiptData.receipt.loyaltyStats" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<!-- Show only if portal_visible. -->
|
||||
<div t-if="_loyaltyStat.program.portal_visible" class='loyalty'>
|
||||
<span class="pos-receipt-center-align">
|
||||
<div>--------------------------------</div>
|
||||
<br/>
|
||||
<div t-esc='_loyaltyStat.program.name' class="pos-receipt-title" />
|
||||
<div t-esc='receiptData.receipt.partner.name' />
|
||||
<br />
|
||||
</span>
|
||||
<t t-if='_loyaltyStat.points.won'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Won: <span t-esc='_loyaltyStat.points.won' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.spent'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Spent: <span t-esc='_loyaltyStat.points.spent' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<!-- Don't use points.total, it's wrong in this context (after the order synced). -->
|
||||
<!-- Show balance as it's updated during _postPushOrderResolve. -->
|
||||
<t t-if='_loyaltyStat.points.balance'>
|
||||
<div>Balance <t t-esc="_loyaltyStat.points.name"/>: <span t-esc='_loyaltyStat.points.balance' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<t t-foreach="props.data.loyaltyStats or []" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<!-- Show only if portal_visible. -->
|
||||
<div t-if="_loyaltyStat.program.portal_visible" class='loyalty'>
|
||||
<span class="pos-receipt-center-align">
|
||||
<div>--------------------------------</div>
|
||||
<br/>
|
||||
<div t-esc='_loyaltyStat.program.name' class="pos-receipt-title" />
|
||||
<div t-esc='props.data.partner.name' />
|
||||
<br />
|
||||
</div>
|
||||
</t>
|
||||
</span>
|
||||
<t t-if='_loyaltyStat.points.won'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Won: <span t-esc='_loyaltyStat.points.won' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.spent'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Spent: <span t-esc='_loyaltyStat.points.spent' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<!-- Don't use points.total, it's wrong in this context (after the order synced). -->
|
||||
<!-- Show balance as it's updated during _postPushOrderResolve. -->
|
||||
<t t-if='_loyaltyStat.points.balance'>
|
||||
<div>Balance <t t-esc="_loyaltyStat.points.name"/>: <span t-esc='_loyaltyStat.points.balance' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<br />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.new_coupon_info and receiptData.receipt.new_coupon_info.length !== 0">
|
||||
<t t-if="props.data.new_coupon_info and props.data.new_coupon_info.length !== 0">
|
||||
<div class="pos-coupon-rewards">
|
||||
<div>------------------------</div>
|
||||
<br/>
|
||||
<div>
|
||||
Coupon Codes
|
||||
</div>
|
||||
<t t-foreach="receiptData.receipt.new_coupon_info" t-as="coupon_info" t-key="coupon_info.code">
|
||||
<t t-foreach="props.data.new_coupon_info" t-as="coupon_info" t-key="coupon_info.code">
|
||||
<div class="coupon-container">
|
||||
<div style="font-size: 110%;">
|
||||
<t t-esc="coupon_info['program_name']"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_mercury.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//t[@t-foreach='receiptData.receipt.paymentlines']" position="inside">
|
||||
<xpath expr="//div[hasclass('paymentlines')]" position="inside">
|
||||
<t t-if="line.mercury_data">
|
||||
<div class="pos-receipt-left-padding">
|
||||
<span>APPROVAL CODE: </span>
|
||||
@@ -13,7 +13,7 @@
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('pos-receipt-order-data')]" position="after">
|
||||
<div t-if="receiptData.hasPosMercurySignature">
|
||||
<div t-if="props.data.hasPosMercurySignature">
|
||||
<br />
|
||||
<div>CARDHOLDER WILL PAY CARD ISSUER</div>
|
||||
<div>ABOVE AMOUNT PURSUANT</div>
|
||||
|
||||
@@ -4,15 +4,12 @@ import { Order } from "@point_of_sale/app/store/models";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(Order.prototype, {
|
||||
getOrderReceiptEnv() {
|
||||
const receiptData = super.getOrderReceiptEnv();
|
||||
|
||||
receiptData.hasPosMercurySignature = receiptData.paymentlines.some((line) => {
|
||||
if (line.mercury_data) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return receiptData;
|
||||
export_for_printing() {
|
||||
return {
|
||||
...super.export_for_printing(),
|
||||
hasPosMercurySignature: this.get_paymentlines().some((line) => {
|
||||
line.mercury_data;
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class BillScreen extends ReceiptScreen {
|
||||
static template = "pos_restaurant.BillScreen";
|
||||
static components = { OrderReceipt };
|
||||
confirm() {
|
||||
if (!this.env.isMobile) {
|
||||
this.props.resolve({ confirmed: true, payload: null });
|
||||
@@ -22,10 +24,6 @@ export class BillScreen extends ReceiptScreen {
|
||||
get isBill() {
|
||||
return true;
|
||||
}
|
||||
|
||||
get receiptData() {
|
||||
return this.pos.get_order().getOrderReceiptEnv();
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("pos_screens").add("BillScreen", BillScreen);
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<span> </span>
|
||||
<span>Print</span>
|
||||
</div>
|
||||
<div class="pos-receipt-container text-center" t-ref="order-receipt">
|
||||
<div class="pos-receipt-container text-center">
|
||||
<div class="d-inline-block m-3 p-3 border rounded bg-view text-start overflow-hidden">
|
||||
<t t-call="point_of_sale.OrderReceipt" />
|
||||
<OrderReceipt data="pos.get_order().export_for_printing()" formatCurrency="env.utils.formatCurrency" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+7
-16
@@ -3,7 +3,7 @@
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useAsyncLockedMethod } from "@point_of_sale/app/utils/hooks";
|
||||
|
||||
@@ -12,7 +12,7 @@ export class PrintBillButton extends Component {
|
||||
|
||||
setup() {
|
||||
this.pos = usePos();
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
this.printer = useService("printer");
|
||||
this.click = useAsyncLockedMethod(this.click);
|
||||
}
|
||||
|
||||
@@ -25,20 +25,11 @@ export class PrintBillButton extends Component {
|
||||
}
|
||||
|
||||
async click() {
|
||||
if (this.hardwareProxy.printer) {
|
||||
const orderReceipt = renderToElement("point_of_sale.OrderReceipt", {
|
||||
receiptData: {
|
||||
...this.pos.get_order().getOrderReceiptEnv(),
|
||||
},
|
||||
pos: this.pos,
|
||||
env: this.env,
|
||||
});
|
||||
|
||||
// Need to await to have the result in case of automatic skip screen.
|
||||
await this.hardwareProxy.printer.printReceipt(orderReceipt);
|
||||
} else {
|
||||
this.pos.showTempScreen("BillScreen");
|
||||
}
|
||||
// Need to await to have the result in case of automatic skip screen.
|
||||
(await this.printer.print(OrderReceipt, {
|
||||
data: this.pos.get_order().export_for_printing(),
|
||||
formatCurrency: this.env.utils.formatCurrency,
|
||||
})) || this.pos.showTempScreen("BillScreen");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { ReceiptHeader } from "@point_of_sale/app/screens/receipt_screen/receipt/receipt_header/receipt_header";
|
||||
|
||||
export class TipReceipt extends Component {
|
||||
static template = "pos_restaurant.TipReceipt";
|
||||
static components = { ReceiptHeader };
|
||||
static props = ["headerData", "data", "total"];
|
||||
}
|
||||
@@ -3,44 +3,7 @@
|
||||
|
||||
<t t-name="pos_restaurant.TipReceipt">
|
||||
<div class="pos-receipt">
|
||||
<t t-if="receipt.company.logo">
|
||||
<img class="pos-receipt-logo" t-att-src="receipt.company.logo" alt="Logo"/>
|
||||
<br/>
|
||||
</t>
|
||||
<t t-if="!receipt.company.logo">
|
||||
<h2 class="pos-receipt-center-align">
|
||||
<t t-esc="receipt.company.name" />
|
||||
</h2>
|
||||
<br/>
|
||||
</t>
|
||||
<div class="pos-receipt-contact">
|
||||
<t t-if="receipt.company.contact_address">
|
||||
<div><t t-esc="receipt.company.contact_address" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.company.phone">
|
||||
<div>Tel:<t t-esc="receipt.company.phone" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.company.vat">
|
||||
<div>VAT:<t t-esc="receipt.company.vat" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.company.email">
|
||||
<div><t t-esc="receipt.company.email" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.company.website">
|
||||
<div><t t-esc="receipt.company.website" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.header">
|
||||
<div><t t-esc="receipt.header" /></div>
|
||||
</t>
|
||||
<t t-if="receipt.cashier">
|
||||
<div class="cashier">
|
||||
<div>--------------------------------</div>
|
||||
<div>Served by <t t-esc="receipt.cashier" /></div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<ReceiptHeader data="props.headerData" />
|
||||
<div class="pos-payment-terminal-receipt">
|
||||
<pre t-esc="data" />
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { parseFloat } from "@web/views/fields/parsers";
|
||||
import { renderToElement } from "@web/core/utils/render";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
|
||||
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
|
||||
import { usePos } from "@point_of_sale/app/store/pos_hook";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, useRef, onMounted } from "@odoo/owl";
|
||||
import { TipReceipt } from "@pos_restaurant/app/tip_receipt/tip_receipt";
|
||||
|
||||
export class TipScreen extends Component {
|
||||
static template = "pos_restaurant.TipScreen";
|
||||
@@ -17,12 +17,12 @@ export class TipScreen extends Component {
|
||||
this.posReceiptContainer = useRef("pos-receipt-container");
|
||||
this.popup = useService("popup");
|
||||
this.orm = useService("orm");
|
||||
this.hardwareProxy = useService("hardware_proxy");
|
||||
this.printer = useService("printer");
|
||||
this.state = this.currentOrder.uiState.TipScreen;
|
||||
this._totalAmount = this.currentOrder.get_total_with_tax();
|
||||
|
||||
onMounted(() => {
|
||||
this.printTipReceipt();
|
||||
onMounted(async () => {
|
||||
await this.printTipReceipt();
|
||||
});
|
||||
}
|
||||
get overallAmountStr() {
|
||||
@@ -110,46 +110,16 @@ export class TipScreen extends Component {
|
||||
this.currentOrder.selected_paymentline.ticket,
|
||||
this.currentOrder.selected_paymentline.cashier_receipt,
|
||||
];
|
||||
|
||||
for (let i = 0; i < receipts.length; i++) {
|
||||
const data = receipts[i];
|
||||
var receipt = renderToElement("pos_restaurant.TipReceipt", {
|
||||
receipt: this.currentOrder.getOrderReceiptEnv().receipt,
|
||||
data: data,
|
||||
total: this.env.utils.formatCurrency(this.totalAmount),
|
||||
});
|
||||
|
||||
if (this.hardwareProxy.printer) {
|
||||
await this._printIoT(receipt);
|
||||
} else {
|
||||
await this._printWeb(receipt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _printIoT(receipt) {
|
||||
const printResult = await this.hardwareProxy.printer.printReceipt(receipt);
|
||||
if (!printResult.successful) {
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: printResult.message.title,
|
||||
body: printResult.message.body,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async _printWeb(receipt) {
|
||||
try {
|
||||
this.posReceiptContainer.el.textContent = "";
|
||||
this.posReceiptContainer.el.appendChild(receipt);
|
||||
window.print();
|
||||
} catch {
|
||||
await this.popup.add(ErrorPopup, {
|
||||
title: _t("Printing is not supported on some browsers"),
|
||||
body: _t(
|
||||
"Printing is not supported on some browsers due to no default printing protocol " +
|
||||
"is available. It is possible to print your tickets by making use of an IoT Box."
|
||||
),
|
||||
});
|
||||
await this.printer.print(
|
||||
TipReceipt,
|
||||
{
|
||||
headerData: this.pos.getReceiptHeaderData(),
|
||||
data: receipts[i],
|
||||
total: this.env.utils.formatCurrency(this.totalAmount),
|
||||
},
|
||||
{ webPrintFallback: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<i class="fa fa-angle-double-left me-2"/>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<button class="button btn btn-lg btn-outline-primary" t-if="hardwareProxy.printer" t-on-click="printTipReceipt">
|
||||
<button class="button btn btn-lg btn-outline-primary" t-if="printer.is()" t-on-click="printTipReceipt">
|
||||
<i class="fa fa-print"></i>
|
||||
<span> </span>
|
||||
<span>Reprint receipts</span>
|
||||
|
||||
+13
-11
@@ -10,14 +10,6 @@
|
||||
<xpath expr="//div[hasclass('receipt-change')]" position="attributes">
|
||||
<attribute name="t-if">!isBill</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('cashier')]" position="after">
|
||||
<t t-if="receiptData.receipt.table">
|
||||
at table <t t-esc="receiptData.receipt.table" />
|
||||
</t>
|
||||
<t t-if="receiptData.receipt.table and receiptData.receipt.customer_count">
|
||||
<div>Guests: <t t-esc="receiptData.receipt.customer_count" /></div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('before-footer')]" position="after">
|
||||
<t t-if="isBill and pos.config.set_tip_after_payment">
|
||||
<div class="tip-form py-3">
|
||||
@@ -26,19 +18,19 @@
|
||||
<div class="option d-flex flex-column flex-nowrap align-items-center justify-content-center flex-grow-1">
|
||||
<div>15%</div>
|
||||
<div class="amount">
|
||||
<t t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax * 0.15)"></t>
|
||||
<t t-esc="props.formatCurrency(props.data.amount_total * 0.15)"></t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option d-flex flex-column flex-nowrap align-items-center justify-content-center flex-grow-1">
|
||||
<div>20%</div>
|
||||
<div class="amount">
|
||||
<t t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax * 0.20)"></t>
|
||||
<t t-esc="props.formatCurrency(props.data.amount_total * 0.20)"></t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option d-flex flex-column flex-nowrap align-items-center justify-content-center flex-grow-1">
|
||||
<div>25%</div>
|
||||
<div class="amount">
|
||||
<t t-esc="env.utils.formatCurrency(receiptData.receipt.total_with_tax * 0.25)"></t>
|
||||
<t t-esc="props.formatCurrency(props.data.amount_total * 0.25)"></t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,5 +38,15 @@
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-name="pos_restaurant.ReceiptHeader" t-inherit="point_of_sale.ReceiptHeader" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('cashier')]" position="after">
|
||||
<t t-if="props.data.table">
|
||||
at table <t t-esc="props.data.table" />
|
||||
</t>
|
||||
<t t-if="props.data.table and props.data.customer_count">
|
||||
<div>Guests: <t t-esc="props.data.customer_count" /></div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -32,17 +32,6 @@ patch(Order.prototype, {
|
||||
this.customerCount = json.customer_count;
|
||||
}
|
||||
},
|
||||
//@override
|
||||
export_for_printing() {
|
||||
const json = super.export_for_printing(...arguments);
|
||||
if (this.pos.config.module_pos_restaurant) {
|
||||
if (this.getTable()) {
|
||||
json.table = this.getTable().name;
|
||||
}
|
||||
json.customer_count = this.getCustomerCount();
|
||||
}
|
||||
return json;
|
||||
},
|
||||
getCustomerCount() {
|
||||
return this.customerCount;
|
||||
},
|
||||
|
||||
@@ -62,6 +62,16 @@ patch(PosStore.prototype, {
|
||||
this.showScreen("FloorScreen", { floor: table?.floor });
|
||||
}
|
||||
},
|
||||
getReceiptHeaderData() {
|
||||
const json = super.getReceiptHeaderData(...arguments);
|
||||
if (this.config.module_pos_restaurant) {
|
||||
if (this.get_order().getTable()) {
|
||||
json.table = this.get_order().getTable().name;
|
||||
}
|
||||
json.customer_count = this.get_order().getCustomerCount();
|
||||
}
|
||||
return json;
|
||||
},
|
||||
shouldResetIdleTimer() {
|
||||
const stayPaymentScreen =
|
||||
this.mainScreen.component === PaymentScreen && this.get_order().paymentlines.length > 0;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_sale.OrderReceipt" t-inherit="point_of_sale.OrderLinesReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//t[@t-foreach]" position="inside">
|
||||
<t t-name="pos_sale.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//Orderline" position="inside">
|
||||
<div class="pos-receipt-left-padding" t-if="line.so_reference">From <t t-esc="line.so_reference"/></div>
|
||||
<div class="pos-receipt-left-padding" t-if="line.down_payment_details">
|
||||
<table class="sale-order-info ms-2 text-truncate">
|
||||
|
||||
@@ -60,13 +60,12 @@ patch(Orderline.prototype, {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
export_for_printing() {
|
||||
var json = super.export_for_printing(...arguments);
|
||||
json.down_payment_details = this.down_payment_details;
|
||||
if (this.sale_order_origin_id) {
|
||||
json.so_reference = this.sale_order_origin_id.name;
|
||||
}
|
||||
return json;
|
||||
getDisplayData() {
|
||||
return {
|
||||
...super.getDisplayData(),
|
||||
down_payment_details: this.down_payment_details,
|
||||
so_reference: this.sale_order_origin_id?.name,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Set quantity based on the give sale order line.
|
||||
|
||||
Reference in New Issue
Block a user