summaryrefslogtreecommitdiff
path: root/addons/pos_restaurant/static/src
diff options
context:
space:
mode:
authorJoseph Caburnay <jcb@odoo.com>2020-07-31 09:36:39 +0000
committerJoseph Caburnay <jcb@odoo.com>2020-08-12 09:17:34 +0000
commitb0107e9230da5cb5c90819e5bee3e9cd323816ee (patch)
tree110e65cd9a180fbf782a70c392b9378a1f662e89 /addons/pos_restaurant/static/src
parent96f7c5eda84d6b7a1eade31efb8c35aff4ce94c4 (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')
-rw-r--r--addons/pos_restaurant/static/src/css/restaurant.css156
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js4
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/PaymentScreen.js17
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js12
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/TipScreen.js80
-rw-r--r--addons/pos_restaurant/static/src/js/floors.js24
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml4
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml44
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/TipScreen.xml55
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>