diff options
| author | Joseph Caburnay <jcb@odoo.com> | 2020-07-31 09:36:39 +0000 |
|---|---|---|
| committer | Joseph Caburnay <jcb@odoo.com> | 2020-08-12 09:17:34 +0000 |
| commit | b0107e9230da5cb5c90819e5bee3e9cd323816ee (patch) | |
| tree | 110e65cd9a180fbf782a70c392b9378a1f662e89 /addons/pos_restaurant/static/src | |
| parent | 96f7c5eda84d6b7a1eade31efb8c35aff4ce94c4 (diff) | |
[IMP] pos_restaurant,point_of_sale: set tip after payment
1. Introduce an option in restaurant config to allow setting tips after payment.
2. Tip form is shown in bills so the customers can choose/set their tips.
3. Validated orders are kept in TipScreen (access via TicketScreen) so that at the
end of the day, the user/cashier can set the tips for each validated order.
closes odoo/odoo#55488
Task-id: 2117029
Related: odoo/upgrade#1606
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
Diffstat (limited to 'addons/pos_restaurant/static/src')
9 files changed, 374 insertions, 22 deletions
diff --git a/addons/pos_restaurant/static/src/css/restaurant.css b/addons/pos_restaurant/static/src/css/restaurant.css index c7cb17e3643..cd4ec3043fd 100644 --- a/addons/pos_restaurant/static/src/css/restaurant.css +++ b/addons/pos_restaurant/static/src/css/restaurant.css @@ -391,3 +391,159 @@ flex: none; } } + +.tip-screen .tip-options .total-amount { + text-align: center; + font-size: x-large; + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.tip-screen .tip-amount-options { + background: white; + border-radius: 3px; + margin-left: 4rem; + margin-right: 4rem; +} + +.tip-screen .percentage-amounts { + display: flex; +} + +.tip-screen .tip-options .button { + background: #FAFAFA; + border: solid 1px rgb(209, 209, 209); + border-radius: 3px; + flex: 1; + margin: 1rem; + display: flex; + flex-flow: column nowrap; + justify-content: center; + cursor: pointer; +} + +.tip-screen .tip-options .button .percentage { + text-align: center; + font-size: xx-large; + color: #2196F3; + padding-top: 4rem; + padding-bottom: 0.75rem; +} + +.tip-screen .tip-options .button .amount { + text-align: center; + font-size: large; + color: #757575; + padding-bottom: 4rem; +} + +.tip-screen .custom-amount-form { + display: flex; +} + +.tip-screen .custom-amount-form .item { + font-size: x-large; + color: #2196F3; + margin: 1rem; + flex: 1 +} + +.tip-screen .custom-amount-form .label { + padding-top: 1.5rem; + padding-bottom: 1.5rem; + text-align: center; +} + +.tip-screen .custom-amount-form .input { + background: #FAFAFA; + border: solid 1px rgb(209, 209, 209); + border-radius: 3px; + margin: 1rem; + text-align: right; + overflow: hidden; + position: relative; +} + +.tip-screen .custom-amount-form .input .currency { + position: absolute; + display: flex; + right: 0; + top: 0; + bottom: 0; + width: 3rem; + justify-content: center; + align-items: center; +} + +.tip-screen .custom-amount-form .input input { + border: none; + padding: 0; + outline: none; + font-size: x-large; + text-align: right; + margin-left: 1rem; + margin-right: 3rem; + height: 100%; + width: calc(100% - 4rem); + background: #FAFAFA; + color: #2196F3; +} + +.tip-screen .custom-amount-form .input input:focus { + border: none; + padding: 0; + outline: none; +} + +.tip-screen .custom-amount-form .add { + text-align: center; +} + +.tip-screen .no-tip { + display: flex; + color: #2196F3; + font-size: x-large; +} + +.tip-screen .no-tip .button { + text-align: center; + flex: 1; + padding: 1.5rem; +} + +.pos-receipt .tip-form { + padding-top: 10px; + padding-bottom: 10px; +} + +.pos-receipt .tip-form > div { + margin-top: 10px; +} + +.pos-receipt .tip-form .checkbox { + display: inline-block; + height: 25px; + width: 25px; + border: solid 1px black; + margin-right: 10px; + box-shadow: 2px 2px 0px 0px rgba(171,171,171,1); +} + +.pos-receipt .tip-form .option { + display: flex; + flex-flow: row nowrap; + align-items: center; + flex: 1; + justify-content: center; +} + +.pos-receipt .tip-form .option .percentage { + font-size: 10px; + color: #757575; +} + +.pos-receipt .tip-form .percentage-options { + display: flex; + flex-flow: row nowrap; + margin-bottom: 16px; +} diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js index 1bf06c838f5..1a4a1a2f14d 100644 --- a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js @@ -271,7 +271,9 @@ odoo.define('pos_restaurant.FloorScreen', function (require) { .filter( (o) => o.server_id === undefined && - (o.orderlines.length !== 0 || o.paymentlines.length !== 0) + (o.orderlines.length !== 0 || o.paymentlines.length !== 0) && + // do not count the orders that are already finalized + !o.finalized ).length; table_obj.order_count = table.orders + unsynced_orders; }); diff --git a/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js b/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js new file mode 100644 index 00000000000..f45d32ba6e2 --- /dev/null +++ b/addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js @@ -0,0 +1,17 @@ +odoo.define('pos_restaurant.PosResPaymentScreen', function (require) { + 'use strict'; + + const PaymentScreen = require('point_of_sale.PaymentScreen'); + const Registries = require('point_of_sale.Registries'); + + const PosResPaymentScreen = (PaymentScreen) => + class extends PaymentScreen { + get nextScreen() { + return this.env.pos.config.set_tip_after_payment && !this.currentOrder.is_tipped ? 'TipScreen' : super.nextScreen; + } + }; + + Registries.Component.extend(PaymentScreen, PosResPaymentScreen); + + return PosResPaymentScreen; +}); diff --git a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js index 5909292e0db..689c3bf40ca 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js @@ -20,18 +20,6 @@ odoo.define('pos_restaurant.ReceiptScreen', function(require) { return super.nextScreen; } } - /** - * If this order is synced to/from the server, then the draft needs to be deleted - * after the order is paid and the receipt screen is exited via 'Next Order' button. - * Thus, we set this order to be removed from server. Note that only the draft version - * is deleted if we trace the algorithm. - */ - orderDone() { - if (this.env.pos.config.iface_floorplan) { - this.env.pos.db.set_order_to_remove_from_server(this.currentOrder); - } - super.orderDone(); - } }; Registries.Component.extend(ReceiptScreen, PosResReceiptScreen); diff --git a/addons/pos_restaurant/static/src/js/Screens/TipScreen.js b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js new file mode 100644 index 00000000000..efbd83ea007 --- /dev/null +++ b/addons/pos_restaurant/static/src/js/Screens/TipScreen.js @@ -0,0 +1,80 @@ +odoo.define('pos_restaurant.TipScreen', function (require) { + 'use strict'; + + const Registries = require('point_of_sale.Registries'); + const PosComponent = require('point_of_sale.PosComponent'); + const { parse } = require('web.field_utils'); + const { useState } = owl.hooks; + + class TipScreen extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ inputCustomAmount: '' }); + this._totalAmount = this.currentOrder.get_total_with_tax(); + } + get totalAmount() { + return this._totalAmount; + } + get currentOrder() { + return this.env.pos.get_order(); + } + get percentageTips() { + return [ + { percentage: '15%', amount: 0.15 * this.totalAmount }, + { percentage: '20%', amount: 0.2 * this.totalAmount }, + { percentage: '25%', amount: 0.25 * this.totalAmount }, + ]; + } + async setTip(amount) { + const order = this.env.pos.get_order(); + const serverId = this.env.pos.validated_orders_name_server_id_map[order.name]; + + if (!serverId) { + this.showPopup('ErrorPopup', { + title: 'Unsynced order', + body: 'This order is not yet synced to server. Make sure it is synced then try again.', + }); + return; + } + + // set the tip by temporarily allowing order modification + order.finalized = false; + order.set_tip(amount); + order.finalized = true; + + // set_tip calls add_product which sets the new line as the selected_orderline + const tip_line = order.selected_orderline; + await this.rpc({ + method: 'set_tip', + model: 'pos.order', + args: [serverId, tip_line.export_as_JSON()], + }); + this.goNextScreen(); + } + async tipCustomAmount() { + const tipAmount = parse.float(this.state.inputCustomAmount); + if (tipAmount > 0.25 * this._totalAmount) { + const { confirmed } = await this.showPopup('ConfirmPopup', { + title: 'Are you sure?', + body: `${this.env.pos.format_currency( + tipAmount + )} is more than 25% of the order's total amount. Are you sure of this tip amount?`, + }); + if (!confirmed) return; + } + await this.setTip(tipAmount); + } + goNextScreen() { + this.env.pos.get_order().finalize(); + this.showScreen(this.nextScreen); + } + get nextScreen() { + return 'FloorScreen'; + } + } + TipScreen.template = 'pos_restaurant.TipScreen'; + + Registries.Component.add(TipScreen); + + return TipScreen; +}); diff --git a/addons/pos_restaurant/static/src/js/floors.js b/addons/pos_restaurant/static/src/js/floors.js index 51ded242c63..807176d5fbc 100644 --- a/addons/pos_restaurant/static/src/js/floors.js +++ b/addons/pos_restaurant/static/src/js/floors.js @@ -201,7 +201,17 @@ models.PosModel = models.PosModel.extend({ var orders = this.get_order_list(); if (orders.length) { order = order ? orders.find((o) => o.uid === order.uid) : null; - this.set_order(order || orders[0]); + if (order) { + this.set_order(order); + } else { + // do not mindlessly set the first order in the list. + orders = orders.filter(order => !order.finalized); + if (orders.length) { + this.set_order(orders[0]); + } else { + this.add_new_order(); + } + } } else { this.add_new_order(); // or create a new order with the current table } @@ -215,12 +225,10 @@ models.PosModel = models.PosModel.extend({ this._get_from_server(table.id).then(function (server_orders) { var orders = self.get_order_list(); orders.forEach(function(order){ - const screen = order.get_screen_data(); - // Only remove the 'Receipt' status order when 'Next Order' - // from the receipt screen is clicked. We are bypassing the - // removal of paid order here to make sure those orders are - // still in the tickets list. - if (order.server_id && screen.name !== 'ReceiptScreen'){ + // We don't remove the validated orders because we still want to see them + // in the ticket screen. Orders in 'ReceiptScreen' or 'TipScreen' are validated + // orders. + if (order.server_id && !order.finalized){ self.get("orders").remove(order); order.destroy(); } @@ -339,7 +347,7 @@ models.PosModel = models.PosModel.extend({ // get customer count at table get_customer_count: function(table) { - var orders = this.get_table_orders(table); + var orders = this.get_table_orders(table).filter(order => !order.finalized); var count = 0; for (var i = 0; i < orders.length; i++) { count += orders[i].get_customer_count(); diff --git a/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml index 0c904ece232..34fe66e36ec 100644 --- a/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml +++ b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml @@ -10,7 +10,9 @@ <span> </span> <span>Back</span> </span> - <h1>Bill Printing</h1> + <div class="top-content-center"> + <h1>Bill Printing</h1> + </div> <span class="button next highlight" t-on-click="confirm"> <span>Ok</span> <span> </span> diff --git a/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml b/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml index 8d195f6cca2..e8f129507e7 100644 --- a/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml +++ b/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml @@ -10,6 +10,50 @@ <xpath expr="//div[hasclass('receipt-change')]" position="attributes"> <attribute name="t-if">!props.isBill</attribute> </xpath> + <xpath expr="//div[hasclass('before-footer')]" position="after"> + <t t-if="env.pos.config.set_tip_after_payment and !props.order.is_tipped"> + <div class="tip-form"> + <div>Add a tip:</div> + <div class="percentage-options"> + <div class="option"> + <div class="checkbox"></div> + <div> + <div class="amount"> + <t t-esc="env.pos.format_currency(receipt.total_with_tax * 0.15)"></t> + </div> + <div class="percentage">(15%)</div> + </div> + </div> + <div class="option"> + <div class="checkbox"></div> + <div> + <div class="amount"> + <t t-esc="env.pos.format_currency(receipt.total_with_tax * 0.20)"></t> + </div> + <div class="percentage">(20%)</div> + </div> + </div> + <div class="option"> + <div class="checkbox"></div> + <div> + <div class="amount"> + <t t-esc="env.pos.format_currency(receipt.total_with_tax * 0.25)"></t> + </div> + <div class="percentage">(25%)</div> + </div> + </div> + </div> + <div class="custom-amount"> + <span>Custom amount:</span> + <div class="pos-receipt-right-align">________________________</div> + </div> + <div class="signature"> + <span>Signature:</span> + <div class="pos-receipt-right-align">________________________</div> + </div> + </div> + </t> + </xpath> </t> </templates> diff --git a/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml new file mode 100644 index 00000000000..467292a7646 --- /dev/null +++ b/addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + + <t t-name="pos_restaurant.TipScreen" owl="1"> + <div class="tip-screen screen"> + <div class="screen-content"> + <div class="top-content"> + <span class="button back" t-on-click="showScreen('FloorScreen')"> + <i class="fa fa-angle-double-left"></i> + <span> </span> + <span>Back</span> + </span> + <div class="top-content-center"> + <h1>Add a tip</h1> + </div> + </div> + <div class="tip-options"> + <div class="total-amount"> + <t t-esc="env.pos.format_currency(totalAmount)" /> + </div> + <div class="tip-amount-options"> + <div class="percentage-amounts"> + <t t-foreach="percentageTips" t-as="tip" t-key="tip.percentage"> + <div class="button" t-on-click="setTip(tip.amount)"> + <div class="percentage"> + <t t-esc="tip.percentage"></t> + </div> + <div class="amount"> + <t t-esc="env.pos.format_currency(tip.amount)" /> + </div> + </div> + </t> + </div> + <div class="custom-amount-form"> + <div class="item label">Custom Amount</div> + <div class="item input"> + <input type="text" t-model="state.inputCustomAmount" /> + <div class="currency"> + <t t-esc="env.pos.getCurrencySymbol()" /> + </div> + </div> + <div class="item add button" t-on-click="tipCustomAmount"> + <span>Add</span> + </div> + </div> + <div class="no-tip" t-on-click="goNextScreen"> + <div class="button">No Tip</div> + </div> + </div> + </div> + </div> + </div> + </t> + +</templates> |
