Skip to content

Commit 47ec895

Browse files
committed
[IMP] point_of_sale: add basic receipt option for gifts
Enhanced the POS system by introducing a basic receipt option specifically for gift transactions. This allows users to easily print receipts for gift items. Task ID: 4081593 closes odoo#180459 Signed-off-by: Vlad Stroia (vlst) <[email protected]>
1 parent 4997fd6 commit 47ec895

File tree

16 files changed

+145
-101
lines changed

16 files changed

+145
-101
lines changed

addons/l10n_fr_pos_cert/static/src/xml/OrderReceipt.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<t t-name="l10n_fr_pos_cert.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
1313
<xpath expr="//Orderline" position="inside">
14-
<t t-if="props.data.l10n_fr_hash !== false and line.price_type === 'manual'">
14+
<t t-if="props.data.l10n_fr_hash !== false and line.price_type === 'manual' and !props.basic_receipt">
1515
<div class="pos-receipt-right-padding">
1616
Old unit price:
1717
<span class="oldPrice">

addons/l10n_gcc_pos/static/src/overrides/app/screens/receipt_screen/receipt/order_receipt.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</xpath>
3131
<xpath expr="//span[@t-esc='props.formatCurrency(props.data.amount_total)']/.." position="after">
3232
<div t-if="props.data.is_gcc_country" class="pos-receipt-amount pos-receipt-amount-arabic" t-translation="off">
33-
TOTAL / الإجمالي
33+
TOTAL / اﻹجمالي
3434
<span t-esc="props.formatCurrency(props.data.amount_total)" class="pos-receipt-right-align"/>
3535
</div>
3636
</xpath>

addons/point_of_sale/models/pos_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def _get_customer_display_types(self):
120120
set_maximum_difference = fields.Boolean('Set Maximum Difference', help="Set a maximum difference allowed between the expected and counted money during the closing of the session.")
121121
receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt.")
122122
receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt.")
123+
basic_receipt = fields.Boolean(string='Basic Receipt', help="Print basic ticket without prices. Can be used for gifts.")
123124
proxy_ip = fields.Char(string='IP Address', size=45,
124125
help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty.')
125126
active = fields.Boolean(default=True)

addons/point_of_sale/models/pos_order.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ def _prepare_refund_values(self, current_session):
11111111
'is_total_cost_computed': False
11121112
}
11131113

1114-
def _prepare_mail_values(self, email, ticket):
1114+
def _prepare_mail_values(self, email, ticket, basic_ticket):
11151115
message = Markup(
11161116
_("<p>Dear %(client_name)s,<br/>Here is your Receipt %(is_invoiced)sfor \
11171117
%(pos_name)s amounting in %(amount)s from %(company_name)s. </p>")
@@ -1129,7 +1129,7 @@ def _prepare_mail_values(self, email, ticket):
11291129
'author_id': self.env.user.partner_id.id,
11301130
'email_from': self.env.company.email or self.env.user.email_formatted,
11311131
'email_to': email,
1132-
'attachment_ids': self._add_mail_attachment(self.name, ticket),
1132+
'attachment_ids': self._add_mail_attachment(self.name, ticket, basic_ticket),
11331133
}
11341134

11351135
def _refund(self):
@@ -1178,7 +1178,8 @@ def action_send_mail(self):
11781178
'target': 'new'
11791179
}
11801180

1181-
def _add_mail_attachment(self, name, ticket):
1181+
def _add_mail_attachment(self, name, ticket, basic_ticket):
1182+
attachment = []
11821183
filename = 'Receipt-' + name + '.jpg'
11831184
receipt = self.env['ir.attachment'].create({
11841185
'name': filename,
@@ -1188,7 +1189,19 @@ def _add_mail_attachment(self, name, ticket):
11881189
'res_id': self.ids[0],
11891190
'mimetype': 'image/jpeg',
11901191
})
1191-
attachment = [(4, receipt.id)]
1192+
attachment += [(4, receipt.id)]
1193+
if basic_ticket:
1194+
filename = 'Receipt-' + name + '-1' + '.jpg'
1195+
basic_receipt = self.env['ir.attachment'].create({
1196+
'name': filename,
1197+
'type': 'binary',
1198+
'datas': basic_ticket,
1199+
'res_model': 'pos.order',
1200+
'res_id': self.ids[0],
1201+
'mimetype': 'image/jpeg',
1202+
})
1203+
attachment += [(4, basic_receipt.id)]
1204+
11921205

11931206
if self.mapped('account_move'):
11941207
report = self.env['ir.actions.report']._render_qweb_pdf("account.account_invoices", self.account_move.ids[0])
@@ -1205,8 +1218,8 @@ def _add_mail_attachment(self, name, ticket):
12051218

12061219
return attachment
12071220

1208-
def action_send_receipt(self, email, ticket_image):
1209-
self.env['mail.mail'].sudo().create(self._prepare_mail_values(email, ticket_image)).send()
1221+
def action_send_receipt(self, email, ticket_image, basic_image):
1222+
self.env['mail.mail'].sudo().create(self._prepare_mail_values(email, ticket_image, basic_image)).send()
12101223
self.email = email
12111224

12121225
@api.model

addons/point_of_sale/models/res_config_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def _default_pos_config(self):
115115
pos_is_closing_entry_by_product = fields.Boolean(related='pos_config_id.is_closing_entry_by_product', readonly=False)
116116
pos_order_edit_tracking = fields.Boolean(related="pos_config_id.order_edit_tracking", readonly=False)
117117
pos_orderlines_sequence_in_cart_by_category = fields.Boolean(related='pos_config_id.orderlines_sequence_in_cart_by_category', readonly=False)
118+
pos_basic_receipt = fields.Boolean(related='pos_config_id.basic_receipt', readonly=False)
118119

119120
@api.model_create_multi
120121
def create(self, vals_list):

addons/point_of_sale/static/src/app/generic_components/orderline/orderline.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ export class Orderline extends Component {
2626
},
2727
showTaxGroupLabels: { type: Boolean, optional: true },
2828
slots: { type: Object, optional: true },
29+
basic_receipt: { type: Boolean, optional: true },
2930
};
3031
static defaultProps = {
3132
class: {},
3233
showTaxGroupLabels: false,
34+
basic_receipt: false,
3335
};
3436
}

addons/point_of_sale/static/src/app/generic_components/orderline/orderline.xml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<span class="text-wrap" t-esc="line.productName"/>
1414
<t t-slot="product-name"/>
1515
</div>
16-
<div class="product-price price fw-bolder">
16+
<div t-if="!props.basic_receipt" class="product-price price fw-bolder">
1717
<t t-if="line.price === 'free'">Free</t>
1818
<t t-else="" t-esc="line.price"/>
1919
</div>
@@ -22,15 +22,17 @@
2222
<ul class="info-list d-flex flex-column">
2323
<li class="price-per-unit">
2424
<span class="qty px-1 border rounded text-bg-view fw-bolder me-1" t-esc="line.qty"/>
25-
x
26-
<t t-if="line.price !== 0">
27-
<s t-esc="line.oldUnitPrice" t-if="line.oldUnitPrice" />
28-
<t t-esc="line.unitPrice" />
25+
<t t-if="!props.basic_receipt">
26+
x
27+
<t t-if="line.price !== 0">
28+
<s t-esc="line.oldUnitPrice" t-if="line.oldUnitPrice" />
29+
<t t-esc="line.unitPrice" />
30+
</t>
31+
/
2932
</t>
30-
/
3133
<t t-if="line.unit" t-esc="line.unit" />
3234
</li>
33-
<li t-if="line.price !== 0 and line.discount and line.discount !== '0'">
35+
<li t-if="line.price !== 0 and line.discount and line.discount !== '0' and !props.basic_receipt">
3436
With a <em><t t-esc="line.discount" />% </em> discount
3537
</li>
3638
<li t-if="line.customerNote" class="customer-note w-100 p-2 mt-2 rounded text-break text-bg-warning bg-opacity-25">

addons/point_of_sale/static/src/app/screens/receipt_screen/receipt/order_receipt.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class OrderReceipt extends Component {
1414
static props = {
1515
data: Object,
1616
formatCurrency: Function,
17+
basic_receipt: { type: Boolean, optional: true },
18+
};
19+
static defaultProps = {
20+
basic_receipt: false,
1721
};
1822
omit(...args) {
1923
return omit(...args);

addons/point_of_sale/static/src/app/screens/receipt_screen/receipt/order_receipt.xml

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,98 +6,99 @@
66
<ReceiptHeader data="props.data.headerData" />
77
<OrderWidget lines="props.data.orderlines" t-slot-scope="scope">
88
<t t-set="line" t-value="scope.line"/>
9-
<Orderline line="omit(scope.line, 'customerNote')" class="{ 'px-0': true }" showTaxGroupLabels="showTaxGroupLabels">
9+
<Orderline basic_receipt="props.basic_receipt" line="omit(scope.line, 'customerNote')" class="{ 'px-0': true }" showTaxGroupLabels="showTaxGroupLabels">
1010
<li t-if="line.customerNote" class="customer-note w-100 p-2 my-1 rounded text-break">
1111
<i class="fa fa-sticky-note me-1" role="img" aria-label="Customer Note" title="Customer Note"/>
1212
<t t-esc="line.customerNote" />
1313
</li>
1414
</Orderline>
1515
</OrderWidget>
16+
<t t-if="!props.basic_receipt">
17+
<div t-if="props.data.tax_details.length > 0" class="pos-receipt-taxes">
18+
<div class="text-center">--------------------------------</div>
19+
<div class="d-flex">
20+
<span t-if="showTaxGroupLabels" class="me-2" style="visibility: hidden;">A</span>
21+
<span class="fw-bolder">Untaxed Amount</span>
22+
<span t-esc="props.formatCurrency(props.data.total_without_tax)" class="ms-auto"/>
23+
</div>
24+
25+
<div t-foreach="props.data.tax_details" t-as="tax" t-key="tax.id" class="d-flex">
26+
<t t-if="showTaxGroupLabels">
27+
<span t-if="tax.tax_group_id.pos_receipt_label" t-esc="tax.tax_group_id.pos_receipt_label" class="me-2"/>
28+
<span t-else="" class="me-2" style="visibility: hidden;">A</span>
29+
</t>
30+
<span>
31+
<span t-esc="tax.name"/>
32+
on
33+
<span t-esc="props.formatCurrency(tax.base)"/>
34+
</span>
35+
<span t-esc="props.formatCurrency(tax.amount)" class="ms-auto"/>
36+
</div>
37+
</div>
1638

17-
<div t-if="props.data.tax_details.length > 0" class="pos-receipt-taxes">
39+
<!-- Total -->
1840
<div class="text-center">--------------------------------</div>
19-
<div class="d-flex">
20-
<span t-if="showTaxGroupLabels" class="me-2" style="visibility: hidden;">A</span>
21-
<span class="fw-bolder">Untaxed Amount</span>
22-
<span t-esc="props.formatCurrency(props.data.total_without_tax)" class="ms-auto"/>
41+
<div class="pos-receipt-amount">
42+
TOTAL
43+
<span t-esc="props.formatCurrency(props.data.amount_total)" class="pos-receipt-right-align"/>
2344
</div>
24-
25-
<div t-foreach="props.data.tax_details" t-as="tax" t-key="tax.id" class="d-flex">
26-
<t t-if="showTaxGroupLabels">
27-
<span t-if="tax.tax_group_id.pos_receipt_label" t-esc="tax.tax_group_id.pos_receipt_label" class="me-2"/>
28-
<span t-else="" class="me-2" style="visibility: hidden;">A</span>
29-
</t>
30-
<span>
31-
<span t-esc="tax.name"/>
32-
on
33-
<span t-esc="props.formatCurrency(tax.base)"/>
34-
</span>
35-
<span t-esc="props.formatCurrency(tax.amount)" class="ms-auto"/>
45+
<t t-if="props.data.rounding_applied">
46+
<div class="pos-receipt-amount">
47+
Rounding
48+
<span t-esc='props.formatCurrency(props.data.rounding_applied)' class="pos-receipt-right-align"/>
49+
</div>
50+
<div class="pos-receipt-amount">
51+
To Pay
52+
<span t-esc='props.formatCurrency(props.data.amount_total + props.data.rounding_applied)' class="pos-receipt-right-align"/>
3653
</div>
37-
</div>
54+
</t>
3855

39-
<!-- Total -->
40-
<div class="text-center">--------------------------------</div>
41-
<div class="pos-receipt-amount">
42-
TOTAL
43-
<span t-esc="props.formatCurrency(props.data.amount_total)" class="pos-receipt-right-align"/>
44-
</div>
45-
<t t-if="props.data.rounding_applied">
46-
<div class="pos-receipt-amount">
47-
Rounding
48-
<span t-esc='props.formatCurrency(props.data.rounding_applied)' class="pos-receipt-right-align"/>
56+
<!-- Payment Lines -->
57+
58+
<div class="paymentlines text-start" t-foreach="props.data.paymentlines" t-as="line" t-key="line_index">
59+
<t t-esc="line.name" />
60+
<span t-esc="props.formatCurrency(line.amount)" class="pos-receipt-right-align"/>
4961
</div>
50-
<div class="pos-receipt-amount">
51-
To Pay
52-
<span t-esc='props.formatCurrency(props.data.amount_total + props.data.rounding_applied)' class="pos-receipt-right-align"/>
53-
</div>
54-
</t>
5562

56-
<!-- Payment Lines -->
63+
<div t-if="props.data.change != 0" class="pos-receipt-amount receipt-change">
64+
CHANGE
65+
<span t-esc="props.formatCurrency(props.data.change)" class="pos-receipt-right-align"/>
66+
</div>
5767

58-
<div class="paymentlines text-start" t-foreach="props.data.paymentlines" t-as="line" t-key="line_index">
59-
<t t-esc="line.name" />
60-
<span t-esc="props.formatCurrency(line.amount)" class="pos-receipt-right-align"/>
61-
</div>
68+
<!-- Extra Payment Info -->
6269

63-
<div t-if="props.data.change != 0" class="pos-receipt-amount receipt-change">
64-
CHANGE
65-
<span t-esc="props.formatCurrency(props.data.change)" class="pos-receipt-right-align"/>
66-
</div>
70+
<t t-if="props.data.total_discount">
71+
<div class="text-center">
72+
Discounts
73+
<span t-esc="props.formatCurrency(props.data.total_discount)" class="pos-receipt-right-align"/>
74+
</div>
75+
</t>
6776

68-
<!-- Extra Payment Info -->
77+
<div class="before-footer" />
6978

70-
<t t-if="props.data.total_discount">
71-
<div class="text-center">
72-
Discounts
73-
<span t-esc="props.formatCurrency(props.data.total_discount)" class="pos-receipt-right-align"/>
79+
<div t-if="props.data.pos_qr_code">
80+
<br/>
81+
<div class="pos-receipt-order-data mb-2">
82+
Need an invoice for your purchase ?
83+
</div>
7484
</div>
75-
</t>
7685

77-
<div class="before-footer" />
78-
79-
<div t-if="props.data.pos_qr_code">
80-
<br/>
81-
<div class="pos-receipt-order-data mb-2">
82-
Need an invoice for your purchase ?
86+
<div t-if="['qr_code', 'qr_code_and_url'].includes(props.data.headerData.company.point_of_sale_ticket_portal_url_display_mode) and props.data.pos_qr_code" class="mb-2">
87+
<img id="posqrcode" t-att-src="props.data.pos_qr_code" class="pos-receipt-logo"/>
8388
</div>
84-
</div>
8589

86-
<div t-if="['qr_code', 'qr_code_and_url'].includes(props.data.headerData.company.point_of_sale_ticket_portal_url_display_mode) and props.data.pos_qr_code" class="mb-2">
87-
<img id="posqrcode" t-att-src="props.data.pos_qr_code" class="pos-receipt-logo"/>
88-
</div>
89-
90-
<div t-if="props.data.pos_qr_code">
91-
<div class="pos-receipt-order-data">
92-
Unique Code: <t t-esc="props.data.ticket_code"/>
90+
<div t-if="props.data.pos_qr_code">
91+
<div class="pos-receipt-order-data">
92+
Unique Code: <t t-esc="props.data.ticket_code"/>
93+
</div>
9394
</div>
94-
</div>
9595

96-
<div t-if="['url', 'qr_code_and_url'].includes(props.data.headerData.company.point_of_sale_ticket_portal_url_display_mode) and props.data.pos_qr_code">
97-
<div class="pos-receipt-order-data" t-attf-class="{{ props.data.ticket_portal_url_display_mode === 'qr_code_and_url' ? 'mt-3' : '' }}">
98-
Portal URL: <t t-out="props.data.base_url"/>/pos/ticket
96+
<div t-if="['url', 'qr_code_and_url'].includes(props.data.headerData.company.point_of_sale_ticket_portal_url_display_mode) and props.data.pos_qr_code">
97+
<div class="pos-receipt-order-data" t-attf-class="{{ props.data.ticket_portal_url_display_mode === 'qr_code_and_url' ? 'mt-3' : '' }}">
98+
Portal URL: <t t-out="props.data.base_url"/>/pos/ticket
99+
</div>
99100
</div>
100-
</div>
101+
</t>
101102

102103
<!-- Footer -->
103104
<div t-if="props.data.footer" class="pos-receipt-center-align" style="white-space:pre-line">

addons/point_of_sale/static/src/app/screens/receipt_screen/receipt_screen.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export class ReceiptScreen extends Component {
2727
mode: "email",
2828
});
2929
this.sendReceipt = useTrackedAsync(this._sendReceiptToCustomer.bind(this));
30-
this.doPrint = useTrackedAsync(() => this.pos.printReceipt());
30+
this.doFullPrint = useTrackedAsync(() => this.pos.printReceipt());
31+
this.doBasicPrint = useTrackedAsync(() => this.pos.printReceipt({ basic: true }));
3132
onMounted(() => {
3233
const order = this.pos.get_order();
3334
this.currentOrder.uiState.locked = true;
@@ -83,6 +84,17 @@ export class ReceiptScreen extends Component {
8384
const { name, props } = this.nextScreen;
8485
this.pos.showScreen(name, props);
8586
}
87+
88+
generateTicketImage = async (isBasicReceipt = false) =>
89+
await this.renderer.toJpeg(
90+
OrderReceipt,
91+
{
92+
data: this.pos.orderExportForPrinting(this.pos.get_order()),
93+
formatCurrency: this.env.utils.formatCurrency,
94+
basic_receipt: isBasicReceipt,
95+
},
96+
{ addClass: "pos-receipt-print p-3" }
97+
);
8698
async _sendReceiptToCustomer({ action }) {
8799
const order = this.currentOrder;
88100
if (typeof order.id !== "number") {
@@ -94,15 +106,14 @@ export class ReceiptScreen extends Component {
94106
});
95107
return Promise.reject();
96108
}
97-
const ticketImage = await this.renderer.toJpeg(
98-
OrderReceipt,
99-
{
100-
data: this.pos.orderExportForPrinting(this.pos.get_order()),
101-
formatCurrency: this.env.utils.formatCurrency,
102-
},
103-
{ addClass: "pos-receipt-print p-3" }
104-
);
105-
await this.pos.data.call("pos.order", action, [[order.id], this.state.input, ticketImage]);
109+
const fullTicketImage = await this.generateTicketImage();
110+
const basicTicketImage = await this.generateTicketImage(true);
111+
await this.pos.data.call("pos.order", action, [
112+
[order.id],
113+
this.state.input,
114+
fullTicketImage,
115+
this.pos.basic_receipt ? basicTicketImage : null,
116+
]);
106117
}
107118
isValidEmail(email) {
108119
return email && /^.+@.+$/.test(email);

0 commit comments

Comments
 (0)