summaryrefslogtreecommitdiff
path: root/addons/pos_restaurant/static/src
diff options
context:
space:
mode:
authorJoseph Caburnay <jcb@odoo.com>2020-05-05 07:50:04 +0000
committerPierre Masereel <pim@odoo.com>2020-05-12 12:48:58 +0000
commitd8726778b670611326851d9baa980edbd21b3b09 (patch)
tree0dd236dd942cd65b224a208f14ce9c099bf35c72 /addons/pos_restaurant/static/src
parentc6397ab3b3654da336a4269a81d5d91016baf520 (diff)
[REF] pos_restaurant: migration to OWL framework
Diffstat (limited to 'addons/pos_restaurant/static/src')
-rw-r--r--addons/pos_restaurant/static/src/css/restaurant.css9
-rw-r--r--addons/pos_restaurant/static/src/js/Chrome.js88
-rw-r--r--addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js28
-rw-r--r--addons/pos_restaurant/static/src/js/ChromeWidgets/OrderSelector.js20
-rw-r--r--addons/pos_restaurant/static/src/js/Resizeable.js302
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/BillScreen.js21
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js19
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js60
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js296
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js73
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js42
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js38
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js33
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js70
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js44
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js30
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ProductScreen/Orderline.js46
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js28
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js164
-rw-r--r--addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitOrderline.js25
-rw-r--r--addons/pos_restaurant/static/src/js/floors.js825
-rw-r--r--addons/pos_restaurant/static/src/js/multiprint.js74
-rw-r--r--addons/pos_restaurant/static/src/js/notes.js32
-rw-r--r--addons/pos_restaurant/static/src/js/printbill.js57
-rw-r--r--addons/pos_restaurant/static/src/js/splitbill.js203
-rw-r--r--addons/pos_restaurant/static/src/js/tours/pos_restaurant.js240
-rw-r--r--addons/pos_restaurant/static/src/xml/Chrome.xml13
-rw-r--r--addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml18
-rw-r--r--addons/pos_restaurant/static/src/xml/ChromeWidgets/OrderSelector.xml20
-rw-r--r--addons/pos_restaurant/static/src/xml/Resizeable.xml8
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml34
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditBar.xml63
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditableTable.xml31
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/FloorScreen/FloorScreen.xml36
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/FloorScreen/TableWidget.xml19
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.xml12
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/PrintBillButton.xml12
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SplitBillButton.xml12
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SubmitOrderButton.xml12
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml14
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TransferOrderButton.xml12
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ProductScreen/Orderline.xml15
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml15
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitBillScreen.xml42
-rw-r--r--addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitOrderline.xml48
-rw-r--r--addons/pos_restaurant/static/src/xml/floors.xml181
-rw-r--r--addons/pos_restaurant/static/src/xml/multiprint.xml7
-rw-r--r--addons/pos_restaurant/static/src/xml/notes.xml20
-rw-r--r--addons/pos_restaurant/static/src/xml/printbill.xml44
-rw-r--r--addons/pos_restaurant/static/src/xml/splitbill.xml86
50 files changed, 1880 insertions, 1761 deletions
diff --git a/addons/pos_restaurant/static/src/css/restaurant.css b/addons/pos_restaurant/static/src/css/restaurant.css
index b17a3002ce6..6dccbc2de50 100644
--- a/addons/pos_restaurant/static/src/css/restaurant.css
+++ b/addons/pos_restaurant/static/src/css/restaurant.css
@@ -111,7 +111,6 @@
overflow: hidden;
position: relative;
bottom: 5px;
- z-index: 5;
}
.floor-map .table.selected {
outline: solid rgba(255,255,255,0.3);
@@ -166,7 +165,7 @@
}
.floor-map .edit-bar .color-picker {
position: absolute;
- left: -106px;
+ left: 36px;
top: 40px;
width: 180px;
height: 180px;
@@ -209,26 +208,24 @@
}
.floor-map .table.selected .table-handle {
+ padding: 0px;
position: absolute;
width: 48px;
height: 48px;
left: 50%;
top: 50%;
border-radius: 24px;
- margin-left: -24px;
- margin-top: -24px;
background: white;
box-shadow: 0px 2px 3px rgba(0,0,0,0.2);
cursor: grab;
transition: all 150ms linear;
z-index: 100;
+ transform: translate(-50%, -50%);
}
.floor-map .table.selected .table-handle:hover {
width: 60px;
height: 60px;
border-radius: 30px;
- margin-left: -30px;
- margin-top: -30px;
}
.floor-map .table .table-handle.top { top: 0; }
.floor-map .table .table-handle.bottom { top: 100%; }
diff --git a/addons/pos_restaurant/static/src/js/Chrome.js b/addons/pos_restaurant/static/src/js/Chrome.js
new file mode 100644
index 00000000000..cff845b2650
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Chrome.js
@@ -0,0 +1,88 @@
+odoo.define('pos_restaurant.chrome', function(require) {
+ 'use strict';
+
+ const Chrome = require('point_of_sale.Chrome');
+ const { onWillUnmount } = owl.hooks;
+ const Registries = require('point_of_sale.Registries');
+
+ const PosResChrome = Chrome =>
+ class extends Chrome {
+ /**
+ * @override
+ */
+ async start() {
+ await super.start();
+ if (this.env.pos.config.iface_floorplan) {
+ this._setActivityListeners();
+ }
+ }
+ /**
+ * @override
+ * Do not set `FloorScreen` to the order.
+ */
+ _setScreenData(name) {
+ if (name === 'FloorScreen') return;
+ super._setScreenData(...arguments);
+ }
+ /**
+ * @override
+ * `FloorScreen` is the start screen if there are floors.
+ */
+ get startScreen() {
+ if (this.env.pos.config.iface_floorplan) {
+ const table = this.env.pos.table;
+ return { name: 'FloorScreen', props: { floor: table ? table.floor : null } };
+ } else {
+ return super.startScreen;
+ }
+ }
+ /**
+ * @override
+ * Order is set to null when table is selected. There is no saved
+ * screen for null order so show `FloorScreen` instead.
+ */
+ _showSavedScreen(pos, newSelectedOrder) {
+ if (!newSelectedOrder) {
+ this.showScreen('FloorScreen', { floor: pos.table.floor });
+ } else {
+ super._showSavedScreen(pos, newSelectedOrder);
+ }
+ }
+ _setActivityListeners() {
+ const events = 'mousemove mousedown touchstart click scroll keypress'.split(' ');
+ const boundHandler = this._setIdleTimer.bind(this);
+ for (let eventName of events) {
+ this.el.addEventListener(eventName, boundHandler);
+ onWillUnmount(() => {
+ this.el.removeEventListener(eventName, boundHandler);
+ });
+ }
+ }
+ _setIdleTimer() {
+ if (this._shouldResetIdleTimer()) {
+ clearTimeout(this.idleTimer);
+ this.idleTimer = setTimeout(() => {
+ this._actionAfterIdle();
+ }, 60000);
+ }
+ }
+ _actionAfterIdle() {
+ this.showScreen('FloorScreen', { floor: this.env.pos.table.floor });
+ }
+ _shouldResetIdleTimer() {
+ return (
+ this.env.pos.config.iface_floorplan &&
+ this.mainScreen.name !== 'FloorScreen' &&
+ !this.tempScreen.isShown
+ );
+ }
+ __showScreen() {
+ super.__showScreen(...arguments);
+ this._setIdleTimer();
+ }
+ };
+
+ Registries.Component.extend(Chrome, PosResChrome);
+
+ return Chrome;
+});
diff --git a/addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js b/addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js
new file mode 100644
index 00000000000..dbd21c5fd32
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/ChromeWidgets/BackToFloorButton.js
@@ -0,0 +1,28 @@
+odoo.define('pos_restaurant.BackToFloorButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class BackToFloorButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this._backToFloorScreen);
+ }
+ get table() {
+ return this.props.table;
+ }
+ get floor() {
+ return this.props.table.floor;
+ }
+ async _backToFloorScreen() {
+ this.showScreen('FloorScreen', { floor: this.floor });
+ }
+ }
+ BackToFloorButton.template = 'BackToFloorButton';
+
+ Registries.Component.add(BackToFloorButton);
+
+ return BackToFloorButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/ChromeWidgets/OrderSelector.js b/addons/pos_restaurant/static/src/js/ChromeWidgets/OrderSelector.js
new file mode 100644
index 00000000000..255b84db073
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/ChromeWidgets/OrderSelector.js
@@ -0,0 +1,20 @@
+odoo.define('pos_restaurant.OrderSelector', function(require) {
+ 'use strict';
+
+ const OrderSelector = require('point_of_sale.OrderSelector');
+ const Registries = require('point_of_sale.Registries');
+
+ const PosResOrderSelector = OrderSelector =>
+ class extends OrderSelector {
+ get currentTable() {
+ return this.env.pos.table;
+ }
+ get backToFloorButtonIsShown() {
+ return this.env.pos.config.iface_floorplan && this.currentTable;
+ }
+ };
+
+ Registries.Component.extend(OrderSelector, PosResOrderSelector);
+
+ return OrderSelector;
+});
diff --git a/addons/pos_restaurant/static/src/js/Resizeable.js b/addons/pos_restaurant/static/src/js/Resizeable.js
new file mode 100644
index 00000000000..a5bfe243c9a
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Resizeable.js
@@ -0,0 +1,302 @@
+odoo.define('pos_restaurant.Resizeable', function(require) {
+ 'use strict';
+
+ const { useExternalListener } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class Resizeable extends PosComponent {
+ constructor() {
+ super(...arguments);
+
+ useExternalListener(document, 'mousemove', this.resizeN);
+ useExternalListener(document, 'mouseup', this.endResizeN);
+ useListener('mousedown', '.resize-handle-n', this.startResizeN);
+
+ useExternalListener(document, 'mousemove', this.resizeS);
+ useExternalListener(document, 'mouseup', this.endResizeS);
+ useListener('mousedown', '.resize-handle-s', this.startResizeS);
+
+ useExternalListener(document, 'mousemove', this.resizeW);
+ useExternalListener(document, 'mouseup', this.endResizeW);
+ useListener('mousedown', '.resize-handle-w', this.startResizeW);
+
+ useExternalListener(document, 'mousemove', this.resizeE);
+ useExternalListener(document, 'mouseup', this.endResizeE);
+ useListener('mousedown', '.resize-handle-e', this.startResizeE);
+
+ useExternalListener(document, 'mousemove', this.resizeNW);
+ useExternalListener(document, 'mouseup', this.endResizeNW);
+ useListener('mousedown', '.resize-handle-nw', this.startResizeNW);
+
+ useExternalListener(document, 'mousemove', this.resizeNE);
+ useExternalListener(document, 'mouseup', this.endResizeNE);
+ useListener('mousedown', '.resize-handle-ne', this.startResizeNE);
+
+ useExternalListener(document, 'mousemove', this.resizeSW);
+ useExternalListener(document, 'mouseup', this.endResizeSW);
+ useListener('mousedown', '.resize-handle-sw', this.startResizeSW);
+
+ useExternalListener(document, 'mousemove', this.resizeSE);
+ useExternalListener(document, 'mouseup', this.endResizeSE);
+ useListener('mousedown', '.resize-handle-se', this.startResizeSE);
+
+ this.size = { height: 0, width: 0 };
+ this.loc = { top: 0, left: 0 };
+ this.tempSize = {};
+ }
+ mounted() {
+ this.limitArea = this.props.limitArea
+ ? document.querySelector(this.props.limitArea)
+ : this.el.offsetParent;
+ this.limitAreaBoundingRect = this.limitArea.getBoundingClientRect();
+ if (this.limitArea === this.el.offsetParent) {
+ this.limitLeft = 0;
+ this.limitTop = 0;
+ this.limitRight = this.limitAreaBoundingRect.width;
+ this.limitBottom = this.limitAreaBoundingRect.height;
+ } else {
+ this.limitLeft = -this.el.offsetParent.offsetLeft;
+ this.limitTop = -this.el.offsetParent.offsetTop;
+ this.limitRight =
+ this.limitAreaBoundingRect.width - this.el.offsetParent.offsetLeft;
+ this.limitBottom =
+ this.limitAreaBoundingRect.height - this.el.offsetParent.offsetTop;
+ }
+ this.limitAreaWidth = this.limitAreaBoundingRect.width;
+ this.limitAreaHeight = this.limitAreaBoundingRect.height;
+ }
+ startResizeN(event) {
+ let realEvent;
+ if (event instanceof CustomEvent) {
+ realEvent = event.detail;
+ } else {
+ realEvent = event;
+ }
+ const { y } = this._getEventLoc(realEvent);
+ this.isResizingN = true;
+ this.startY = y;
+ this.size.height = this.el.offsetHeight;
+ this.loc.top = this.el.offsetTop;
+ event.stopPropagation();
+ }
+ resizeN(event) {
+ if (this.isResizingN) {
+ const { y: newY } = this._getEventLoc(event);
+ let dY = newY - this.startY;
+ if (dY < 0 && Math.abs(dY) > this.loc.top) {
+ dY = -this.loc.top;
+ } else if (dY > 0 && dY > this.size.height) {
+ dY = this.size.height;
+ }
+ this.el.style.height = `${this.size.height - dY}px`;
+ this.el.style.top = `${this.loc.top + dY}px`;
+ }
+ event.preventDefault();
+ }
+ endResizeN() {
+ if (this.isResizingN && !this.isResizingE && !this.isResizingW && !this.isResizingS) {
+ this.isResizingN = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeS(event) {
+ let realEvent;
+ if (event instanceof CustomEvent) {
+ realEvent = event.detail;
+ } else {
+ realEvent = event;
+ }
+ const { y } = this._getEventLoc(realEvent);
+ this.isResizingS = true;
+ this.startY = y;
+ this.size.height = this.el.offsetHeight;
+ this.loc.top = this.el.offsetTop;
+ event.stopPropagation();
+ }
+ resizeS(event) {
+ if (this.isResizingS) {
+ const { y: newY } = this._getEventLoc(event);
+ let dY = newY - this.startY;
+ if (dY > 0 && dY > this.limitAreaHeight - (this.size.height + this.loc.top)) {
+ dY = this.limitAreaHeight - (this.size.height + this.loc.top);
+ } else if (dY < 0 && Math.abs(dY) > this.size.height) {
+ dY = -this.size.height;
+ }
+ this.el.style.height = `${this.size.height + dY}px`;
+ }
+ event.preventDefault();
+ }
+ endResizeS() {
+ if (!this.isResizingN && !this.isResizingE && !this.isResizingW && this.isResizingS) {
+ this.isResizingS = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeW(event) {
+ let realEvent;
+ if (event instanceof CustomEvent) {
+ realEvent = event.detail;
+ } else {
+ realEvent = event;
+ }
+ const { x } = this._getEventLoc(realEvent);
+ this.isResizingW = true;
+ this.startX = x;
+ this.size.width = this.el.offsetWidth;
+ this.loc.left = this.el.offsetLeft;
+ event.stopPropagation();
+ }
+ resizeW(event) {
+ if (this.isResizingW) {
+ const { x: newX } = this._getEventLoc(event);
+ let dX = newX - this.startX;
+ if (dX > 0 && dX > this.size.width) {
+ dX = this.size.width;
+ } else if (dX < 0 && Math.abs(dX) > this.loc.left + Math.abs(this.limitLeft)) {
+ dX = -this.loc.left + this.limitLeft;
+ }
+ this.el.style.width = `${this.size.width - dX}px`;
+ this.el.style.left = `${this.loc.left + dX}px`;
+ }
+ event.preventDefault();
+ }
+ endResizeW() {
+ if (!this.isResizingN && !this.isResizingE && this.isResizingW && !this.isResizingS) {
+ this.isResizingW = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeE(event) {
+ let realEvent;
+ if (event instanceof CustomEvent) {
+ realEvent = event.detail;
+ } else {
+ realEvent = event;
+ }
+ const { x } = this._getEventLoc(realEvent);
+ this.isResizingE = true;
+ this.startX = x;
+ this.size.width = this.el.offsetWidth;
+ this.loc.left = this.el.offsetLeft;
+ event.stopPropagation();
+ }
+ resizeE(event) {
+ if (this.isResizingE) {
+ const { x: newX } = this._getEventLoc(event);
+ let dX = newX - this.startX;
+ if (
+ dX > 0 &&
+ dX >
+ this.limitAreaWidth -
+ (this.size.width + this.loc.left + Math.abs(this.limitLeft))
+ ) {
+ dX =
+ this.limitAreaWidth -
+ (this.size.width + this.loc.left + Math.abs(this.limitLeft));
+ } else if (dX < 0 && Math.abs(dX) > this.size.width) {
+ dX = -this.size.width;
+ }
+ this.el.style.width = `${this.size.width + dX}px`;
+ }
+ event.preventDefault();
+ }
+ endResizeE() {
+ if (!this.isResizingN && this.isResizingE && !this.isResizingW && !this.isResizingS) {
+ this.isResizingE = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeNW(event) {
+ this.startResizeN(event);
+ this.startResizeW(event);
+ }
+ resizeNW(event) {
+ this.resizeN(event);
+ this.resizeW(event);
+ }
+ endResizeNW() {
+ if (this.isResizingN && !this.isResizingE && this.isResizingW && !this.isResizingS) {
+ this.isResizingN = false;
+ this.isResizingW = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeNE(event) {
+ this.startResizeN(event);
+ this.startResizeE(event);
+ }
+ resizeNE(event) {
+ this.resizeN(event);
+ this.resizeE(event);
+ }
+ endResizeNE() {
+ if (this.isResizingN && this.isResizingE && !this.isResizingW && !this.isResizingS) {
+ this.isResizingN = false;
+ this.isResizingE = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeSE(event) {
+ this.startResizeS(event);
+ this.startResizeE(event);
+ }
+ resizeSE(event) {
+ this.resizeS(event);
+ this.resizeE(event);
+ }
+ endResizeSE() {
+ if (!this.isResizingN && this.isResizingE && !this.isResizingW && this.isResizingS) {
+ this.isResizingS = false;
+ this.isResizingE = false;
+ this._triggerResizeEnd();
+ }
+ }
+ startResizeSW(event) {
+ this.startResizeS(event);
+ this.startResizeW(event);
+ }
+ resizeSW(event) {
+ this.resizeS(event);
+ this.resizeW(event);
+ }
+ endResizeSW() {
+ if (!this.isResizingN && !this.isResizingE && this.isResizingW && this.isResizingS) {
+ this.isResizingS = false;
+ this.isResizingW = false;
+ this._triggerResizeEnd();
+ }
+ }
+ _getEventLoc(event) {
+ let coordX, coordY;
+ if (event.touches && event.touches[0]) {
+ coordX = event.touches[0].clientX;
+ coordY = event.touches[0].clientY;
+ } else {
+ coordX = event.clientX;
+ coordY = event.clientY;
+ }
+ return {
+ x: coordX,
+ y: coordY,
+ };
+ }
+ _triggerResizeEnd() {
+ const size = {
+ height: this.el.offsetHeight,
+ width: this.el.offsetWidth,
+ };
+ const loc = {
+ top: this.el.offsetTop,
+ left: this.el.offsetLeft,
+ };
+ this.trigger('resize-end', { size, loc });
+ }
+ }
+ Resizeable.template = 'Resizeable';
+
+ Registries.Component.add(Resizeable);
+
+ return Resizeable;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/BillScreen.js b/addons/pos_restaurant/static/src/js/Screens/BillScreen.js
new file mode 100644
index 00000000000..7b7c5b0f8b9
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/BillScreen.js
@@ -0,0 +1,21 @@
+odoo.define('pos_restaurant.BillScreen', function (require) {
+ 'use strict';
+
+ const ReceiptScreen = require('point_of_sale.ReceiptScreen');
+ const Registries = require('point_of_sale.Registries');
+
+ const BillScreen = (ReceiptScreen) => {
+ class BillScreen extends ReceiptScreen {
+ confirm() {
+ this.props.resolve({ confirmed: true, payload: null });
+ this.trigger('close-temp-screen');
+ }
+ }
+ BillScreen.template = 'BillScreen';
+ return BillScreen;
+ };
+
+ Registries.Component.addByExtending(BillScreen, ReceiptScreen);
+
+ return BillScreen;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js
new file mode 100644
index 00000000000..43b16d93c3e
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditBar.js
@@ -0,0 +1,19 @@
+odoo.define('pos_restaurant.EditBar', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+ const { useState } = owl.hooks;
+
+ class EditBar extends PosComponent {
+ constructor() {
+ super(...arguments);
+ this.state = useState({ isColorPicker: false })
+ }
+ }
+ EditBar.template = 'EditBar';
+
+ Registries.Component.add(EditBar);
+
+ return EditBar;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js
new file mode 100644
index 00000000000..4aeb781d712
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/EditableTable.js
@@ -0,0 +1,60 @@
+odoo.define('pos_restaurant.EditableTable', function(require) {
+ 'use strict';
+
+ const { onPatched, onMounted } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class EditableTable extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('resize-end', this._onResizeEnd);
+ useListener('drag-end', this._onDragEnd);
+ onPatched(this._setElementStyle.bind(this));
+ onMounted(this._setElementStyle.bind(this));
+ }
+ _setElementStyle() {
+ const table = this.props.table;
+ function unit(val) {
+ return `${val}px`;
+ }
+ const style = {
+ width: unit(table.width),
+ height: unit(table.height),
+ 'line-height': unit(table.height),
+ top: unit(table.position_v),
+ left: unit(table.position_h),
+ 'border-radius': table.shape === 'round' ? unit(1000) : '3px',
+ };
+ if (table.color) {
+ style.background = table.color;
+ }
+ if (table.height >= 150 && table.width >= 150) {
+ style['font-size'] = '32px';
+ }
+ Object.assign(this.el.style, style);
+ }
+ _onResizeEnd(event) {
+ const { size, loc } = event.detail;
+ const table = this.props.table;
+ table.width = size.width;
+ table.height = size.height;
+ table.position_v = loc.top;
+ table.position_h = loc.left;
+ this.trigger('save-table', this.props.table);
+ }
+ _onDragEnd(event) {
+ const { loc } = event.detail;
+ const table = this.props.table;
+ table.position_v = loc.top;
+ table.position_h = loc.left;
+ this.trigger('save-table', this.props.table);
+ }
+ }
+ EditableTable.template = 'EditableTable';
+
+ Registries.Component.add(EditableTable);
+
+ return EditableTable;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js
new file mode 100644
index 00000000000..aaf929ed0fe
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/FloorScreen.js
@@ -0,0 +1,296 @@
+odoo.define('pos_restaurant.FloorScreen', function (require) {
+ 'use strict';
+
+ const { debounce } = owl.utils;
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useState, useRef } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class FloorScreen extends PosComponent {
+ /**
+ * @param {Object} props
+ * @param {Object} props.floor
+ */
+ constructor() {
+ super(...arguments);
+ this._setTableColor = debounce(this._setTableColor, 70);
+ this._setFloorColor = debounce(this._setFloorColor, 70);
+ useListener('select-table', this._onSelectTable);
+ useListener('deselect-table', this._onDeselectTable);
+ useListener('save-table', this._onSaveTable);
+ useListener('create-table', this._createTable);
+ useListener('duplicate-table', this._duplicateTable);
+ useListener('rename-table', this._renameTable);
+ useListener('change-seats-num', this._changeSeatsNum);
+ useListener('change-shape', this._changeShape);
+ useListener('set-table-color', this._setTableColor);
+ useListener('set-floor-color', this._setFloorColor);
+ useListener('delete-table', this._deleteTable);
+ const floor = this.props.floor ? this.props.floor : this.env.pos.floors[0];
+ this.state = useState({
+ selectedFloorId: floor.id,
+ selectedTableId: null,
+ isEditMode: false,
+ floorBackground: floor.background_color,
+ });
+ this.floorMapRef = useRef('floor-map-ref');
+ }
+ patched() {
+ this.floorMapRef.el.style.background = this.state.floorBackground;
+ }
+ mounted() {
+ this.env.pos.set_table(null);
+ this.floorMapRef.el.style.background = this.state.floorBackground;
+ // call _tableLongpolling once then set interval of 5sec.
+ this._tableLongpolling();
+ this.tableLongpolling = setInterval(this._tableLongpolling.bind(this), 5000);
+ }
+ willUnmount() {
+ clearInterval(this.tableLongpolling);
+ }
+ get activeFloor() {
+ return this.env.pos.floors_by_id[this.state.selectedFloorId];
+ }
+ get activeTables() {
+ return this.activeFloor.tables;
+ }
+ get isFloorEmpty() {
+ return this.activeTables.length === 0;
+ }
+ get selectedTable() {
+ return this.state.selectedTableId !== null
+ ? this.env.pos.tables_by_id[this.state.selectedTableId]
+ : false;
+ }
+ selectFloor(floor) {
+ this.state.selectedFloorId = floor.id;
+ this.state.floorBackground = this.activeFloor.background_color;
+ this.state.isEditMode = false;
+ this.state.selectedTableId = null;
+ }
+ toggleEditMode() {
+ this.state.isEditMode = !this.state.isEditMode;
+ this.state.selectedTableId = null;
+ }
+ async _createTable() {
+ const newTable = await this._createTableHelper();
+ if (newTable) {
+ this.state.selectedTableId = newTable.id;
+ }
+ }
+ async _duplicateTable() {
+ if (!this.selectedTable) return;
+ const newTable = await this._createTableHelper(this.selectedTable);
+ if (newTable) {
+ this.state.selectedTableId = newTable.id;
+ }
+ }
+ async _changeSeatsNum() {
+ if (!this.selectedTable) return;
+ const { confirmed, payload: inputNumber } = await this.showPopup('NumberPopup', {
+ startingValue: this.selectedTable.seats,
+ cheap: true,
+ title: this.env._t('Number of Seats ?'),
+ });
+ if (!confirmed) return;
+ const newSeatsNum = parseInt(inputNumber, 10) || this.selectedTable.seats;
+ if (newSeatsNum !== this.selectedTable.seats) {
+ this.selectedTable.seats = newSeatsNum;
+ await this._save(this.selectedTable);
+ }
+ }
+ async _changeShape() {
+ if (!this.selectedTable) return;
+ this.selectedTable.shape = this.selectedTable.shape === 'square' ? 'round' : 'square';
+ this.render();
+ await this._save(this.selectedTable);
+ }
+ async _renameTable() {
+ if (!this.selectedTable) return;
+ const { confirmed, payload: newName } = await this.showPopup('TextInputPopup', {
+ startingValue: this.selectedTable.name,
+ title: this.env._t('Table Name ?'),
+ });
+ if (!confirmed) return;
+ if (newName !== this.selectedTable.name) {
+ this.selectedTable.name = newName;
+ await this._save(this.selectedTable);
+ }
+ }
+ async _setTableColor({ detail: color }) {
+ this.selectedTable.color = color;
+ this.render();
+ await this._save(this.selectedTable);
+ }
+ async _setFloorColor({ detail: color }) {
+ this.state.floorBackground = color;
+ this.activeFloor.background_color = color;
+ try {
+ await this.rpc({
+ model: 'restaurant.floor',
+ method: 'write',
+ args: [[this.activeFloor.id], { background_color: color }],
+ });
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to change background color'),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ async _deleteTable() {
+ if (!this.selectedTable) return;
+ const { confirmed } = await this.showPopup('ConfirmPopup', {
+ title: this.env._t('Are you sure ?'),
+ body: this.env._t('Removing a table cannot be undone'),
+ });
+ if (!confirmed) return;
+ try {
+ await this.rpc({
+ model: 'restaurant.table',
+ method: 'create_from_ui',
+ args: [{ active: false, id: this.state.selectedTableId }],
+ });
+ this.activeFloor.tables = this.activeTables.filter(
+ (table) => table.id !== this.state.selectedTableId
+ );
+ this.state.selectedTableId = null;
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to delete table'),
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ async _onSelectTable(event) {
+ const table = event.detail;
+ if (this.state.isEditMode) {
+ this.state.selectedTableId = table.id;
+ } else {
+ await this.env.pos.set_table(table);
+ }
+ }
+ _onDeselectTable() {
+ this.state.selectedTableId = null;
+ }
+ async _createTableHelper(copyTable) {
+ let newTable;
+ if (copyTable) {
+ newTable = Object.assign({}, copyTable);
+ newTable.position_h += 10;
+ newTable.position_v += 10;
+ } else {
+ newTable = {
+ position_v: 100,
+ position_h: 100,
+ width: 75,
+ height: 75,
+ shape: 'square',
+ seats: 1,
+ };
+ }
+ newTable.name = this._getNewTableName(newTable.name);
+ delete newTable.id;
+ newTable.floor_id = [this.activeFloor.id, ''];
+ newTable.floor = this.activeFloor;
+ try {
+ await this._save(newTable);
+ this.activeTables.push(newTable);
+ return newTable;
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('ErrorPopup', {
+ title: this.env._t('Offline'),
+ body: this.env._t('Unable to create table because you are offline.'),
+ });
+ return;
+ } else {
+ throw error;
+ }
+ }
+ }
+ _getNewTableName(name) {
+ if (name) {
+ const num = Number((name.match(/\d+/g) || [])[0] || 0);
+ const str = name.replace(/\d+/g, '');
+ const n = { num: num, str: str };
+ n.num += 1;
+ this._lastName = n;
+ } else if (this._lastName) {
+ this._lastName.num += 1;
+ } else {
+ this._lastName = { num: 1, str: 'T' };
+ }
+ return '' + this._lastName.str + this._lastName.num;
+ }
+ async _save(table) {
+ const fields = this.env.pos.models.find((model) => model.model === 'restaurant.table')
+ .fields;
+ const serializeTable = {};
+ for (let field of fields) {
+ if (typeof table[field] !== 'undefined') {
+ serializeTable[field] = table[field];
+ }
+ }
+ serializeTable.id = table.id;
+ const tableId = await this.rpc({
+ model: 'restaurant.table',
+ method: 'create_from_ui',
+ args: [serializeTable],
+ });
+ table.id = tableId;
+ this.env.pos.tables_by_id[tableId] = table;
+ }
+ async _onSaveTable(event) {
+ const table = event.detail;
+ await this._save(table);
+ }
+ async _tableLongpolling() {
+ if (this.state.isEditMode) {
+ return;
+ }
+ try {
+ const result = await this.rpc({
+ model: 'pos.config',
+ method: 'get_tables_order_count',
+ args: [this.env.pos.config.id],
+ });
+ result.forEach((table) => {
+ const table_obj = this.env.pos.tables_by_id[table.id];
+ const unsynced_orders = this.env.pos
+ .get_table_orders(table_obj)
+ .filter(
+ (o) =>
+ o.server_id === undefined &&
+ (o.orderlines.length !== 0 || o.paymentlines.length !== 0)
+ ).length;
+ table_obj.order_count = table.orders + unsynced_orders;
+ });
+ this.render();
+ } catch (error) {
+ if (error.message.code < 0) {
+ await this.showPopup('OfflineErrorPopup', {
+ title: 'Offline',
+ body: 'Unable to get orders count',
+ });
+ } else {
+ throw error;
+ }
+ }
+ }
+ }
+ FloorScreen.template = 'FloorScreen';
+
+ Registries.Component.add(FloorScreen);
+
+ return FloorScreen;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js
new file mode 100644
index 00000000000..48dfdad71e8
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/FloorScreen/TableWidget.js
@@ -0,0 +1,73 @@
+odoo.define('pos_restaurant.TableWidget', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class TableWidget extends PosComponent {
+ mounted() {
+ const table = this.props.table;
+ function unit(val) {
+ return `${val}px`;
+ }
+ const style = {
+ width: unit(table.width),
+ height: unit(table.height),
+ 'line-height': unit(table.height),
+ top: unit(table.position_v),
+ left: unit(table.position_h),
+ 'border-radius': table.shape === 'round' ? unit(1000) : '3px',
+ };
+ if (table.color) {
+ style.background = table.color;
+ }
+ if (table.height >= 150 && table.width >= 150) {
+ style['font-size'] = '32px';
+ }
+ Object.assign(this.el.style, style);
+
+ const tableCover = this.el.querySelector('.table-cover');
+ Object.assign(tableCover.style, { height: `${Math.ceil(this.fill * 100)}%` });
+ }
+ get fill() {
+ const customerCount = this.env.pos.get_customer_count(this.props.table);
+ return Math.min(1, Math.max(0, customerCount / this.props.table.seats));
+ }
+ get orderCount() {
+ const table = this.props.table;
+ return table.order_count !== undefined
+ ? table.order_count
+ : this.env.pos
+ .get_table_orders(table)
+ .filter(o => o.orderlines.length !== 0 || o.paymentlines.length !== 0).length;
+ }
+ get orderCountClass() {
+ const notifications = this._getNotifications();
+ return {
+ 'order-count': true,
+ 'notify-printing': notifications.printing,
+ 'notify-skipped': notifications.skipped,
+ };
+ }
+ _getNotifications() {
+ const orders = this.env.pos.get_table_orders(this.props.table);
+
+ let hasChangesCount = 0;
+ let hasSkippedCount = 0;
+ for (let i = 0; i < orders.length; i++) {
+ if (orders[i].hasChangesToPrint()) {
+ hasChangesCount++;
+ } else if (orders[i].hasSkippedChanges()) {
+ hasSkippedCount++;
+ }
+ }
+
+ return hasChangesCount ? { printing: true } : hasSkippedCount ? { skipped: true } : {};
+ }
+ }
+ TableWidget.template = 'TableWidget';
+
+ Registries.Component.add(TableWidget);
+
+ return TableWidget;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js
new file mode 100644
index 00000000000..58681810cb0
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.js
@@ -0,0 +1,42 @@
+odoo.define('pos_restaurant.OrderlineNoteButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class OrderlineNoteButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ get selectedOrderline() {
+ return this.env.pos.get_order().get_selected_orderline();
+ }
+ async onClick() {
+ if (!this.selectedOrderline) return;
+
+ const { confirmed, payload: inputNote } = await this.showPopup('TextAreaPopup', {
+ startingValue: this.selectedOrderline.get_note(),
+ title: this.env._t('Add Note'),
+ });
+
+ if (confirmed) {
+ this.selectedOrderline.set_note(inputNote);
+ }
+ }
+ }
+ OrderlineNoteButton.template = 'OrderlineNoteButton';
+
+ ProductScreen.addControlButton({
+ component: OrderlineNoteButton,
+ condition: function() {
+ return this.env.pos.config.module_pos_restaurant;
+ },
+ });
+
+ Registries.Component.add(OrderlineNoteButton);
+
+ return OrderlineNoteButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js
new file mode 100644
index 00000000000..7563d9fc6ea
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/PrintBillButton.js
@@ -0,0 +1,38 @@
+odoo.define('pos_restaurant.PrintBillButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class PrintBillButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ async onClick() {
+ const order = this.env.pos.get_order();
+ if (order.get_orderlines().length > 0) {
+ await this.showTempScreen('BillScreen');
+ } else {
+ await this.showPopup('ErrorPopup', {
+ title: this.env._t('Nothing to Print'),
+ body: this.env._t('There are no order lines'),
+ });
+ }
+ }
+ }
+ PrintBillButton.template = 'PrintBillButton';
+
+ ProductScreen.addControlButton({
+ component: PrintBillButton,
+ condition: function() {
+ return this.env.pos.config.iface_printbill;
+ },
+ });
+
+ Registries.Component.add(PrintBillButton);
+
+ return PrintBillButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js
new file mode 100644
index 00000000000..58ace925080
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SplitBillButton.js
@@ -0,0 +1,33 @@
+odoo.define('pos_restaurant.SplitBillButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class SplitBillButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ async onClick() {
+ const order = this.env.pos.get_order();
+ if (order.get_orderlines().length > 0) {
+ this.showScreen('SplitBillScreen');
+ }
+ }
+ }
+ SplitBillButton.template = 'SplitBillButton';
+
+ ProductScreen.addControlButton({
+ component: SplitBillButton,
+ condition: function() {
+ return this.env.pos.config.iface_splitbill;
+ },
+ });
+
+ Registries.Component.add(SplitBillButton);
+
+ return SplitBillButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js
new file mode 100644
index 00000000000..948410e5d08
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/SubmitOrderButton.js
@@ -0,0 +1,70 @@
+odoo.define('pos_restaurant.SubmitOrderButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ /**
+ * IMPROVEMENT: Perhaps this class is quite complicated for its worth.
+ * This is because it needs to listen to changes to the current order.
+ * Also, the current order changes when the selectedOrder in pos is changed.
+ * After setting new current order, we update the listeners.
+ */
+ class SubmitOrderButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ this._currentOrder = this.env.pos.get_order();
+ this._currentOrder.orderlines.on('change', this.render, this);
+ this.env.pos.on('change:selectedOrder', this._updateCurrentOrder, this);
+ }
+ willUnmount() {
+ this._currentOrder.orderlines.off('change', null, this);
+ this.env.pos.off('change:selectedOrder', null, this);
+ }
+ async onClick() {
+ const order = this.env.pos.get_order();
+ if (order.hasChangesToPrint()) {
+ const isPrintSuccessful = await order.printChanges();
+ if (isPrintSuccessful) {
+ order.saveChanges();
+ } else {
+ await this.showPopup('ErrorPopup', {
+ title: 'Printing failed',
+ body: 'Failed in printing the changes in the order',
+ });
+ }
+ }
+ }
+ get addedClasses() {
+ if (!this._currentOrder) return {};
+ const changes = this._currentOrder.hasChangesToPrint();
+ const skipped = changes ? false : this._currentOrder.hasSkippedChanges();
+ return {
+ highlight: changes,
+ altlight: skipped,
+ };
+ }
+ _updateCurrentOrder(pos, newSelectedOrder) {
+ this._currentOrder.orderlines.off('change', null, this);
+ if (newSelectedOrder) {
+ this._currentOrder = newSelectedOrder;
+ this._currentOrder.orderlines.on('change', this.render, this);
+ }
+ }
+ }
+ SubmitOrderButton.template = 'SubmitOrderButton';
+
+ ProductScreen.addControlButton({
+ component: SubmitOrderButton,
+ condition: function() {
+ return this.env.pos.printers.length;
+ },
+ });
+
+ Registries.Component.add(SubmitOrderButton);
+
+ return SubmitOrderButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js
new file mode 100644
index 00000000000..e015496d47a
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js
@@ -0,0 +1,44 @@
+odoo.define('pos_restaurant.TableGuestsButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class TableGuestsButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get nGuests() {
+ return this.currentOrder ? this.currentOrder.get_customer_count() : 0;
+ }
+ async onClick() {
+ const { confirmed, payload: inputNumber } = await this.showPopup('NumberPopup', {
+ startingValue: this.nGuests,
+ cheap: true,
+ title: this.env._t('Guests ?'),
+ });
+
+ if (confirmed) {
+ this.env.pos.get_order().set_customer_count(parseInt(inputNumber, 10) || 1);
+ }
+ }
+ }
+ TableGuestsButton.template = 'TableGuestsButton';
+
+ ProductScreen.addControlButton({
+ component: TableGuestsButton,
+ condition: function() {
+ return this.env.pos.config.module_pos_restaurant;
+ },
+ });
+
+ Registries.Component.add(TableGuestsButton);
+
+ return TableGuestsButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js
new file mode 100644
index 00000000000..ed72e2a7119
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TransferOrderButton.js
@@ -0,0 +1,30 @@
+odoo.define('pos_restaurant.TransferOrderButton', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const ProductScreen = require('point_of_sale.ProductScreen');
+ const { useListener } = require('web.custom_hooks');
+ const Registries = require('point_of_sale.Registries');
+
+ class TransferOrderButton extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ onClick() {
+ this.env.pos.transfer_order_to_different_table();
+ }
+ }
+ TransferOrderButton.template = 'TransferOrderButton';
+
+ ProductScreen.addControlButton({
+ component: TransferOrderButton,
+ condition: function() {
+ return this.env.pos.config.iface_floorplan;
+ },
+ });
+
+ Registries.Component.add(TransferOrderButton);
+
+ return TransferOrderButton;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/Orderline.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/Orderline.js
new file mode 100644
index 00000000000..2204405cd2d
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/Orderline.js
@@ -0,0 +1,46 @@
+odoo.define('pos_restaurant.Orderline', function(require) {
+ 'use strict';
+
+ const Orderline = require('point_of_sale.Orderline');
+ const Registries = require('point_of_sale.Registries');
+
+ const PosResOrderline = Orderline =>
+ class extends Orderline {
+ /**
+ * @override
+ */
+ get addedClasses() {
+ const res = super.addedClasses;
+ Object.assign(res, {
+ dirty: this.props.line.mp_dirty,
+ skip: this.props.line.mp_skip,
+ });
+ return res;
+ }
+ /**
+ * @override
+ * if doubleclick, change mp_dirty to mp_skip
+ *
+ * IMPROVEMENT: Instead of handling both double click and click in single
+ * method, perhaps we can separate double click from single click.
+ */
+ selectLine() {
+ const line = this.props.line; // the orderline
+ if (this.env.pos.get_order().selected_orderline !== line) {
+ this.mp_dbclk_time = new Date().getTime();
+ } else if (!this.mp_dbclk_time) {
+ this.mp_dbclk_time = new Date().getTime();
+ } else if (this.mp_dbclk_time + 500 > new Date().getTime()) {
+ line.set_skip(!line.mp_skip);
+ this.mp_dbclk_time = 0;
+ } else {
+ this.mp_dbclk_time = new Date().getTime();
+ }
+ super.selectLine();
+ }
+ };
+
+ Registries.Component.extend(Orderline, PosResOrderline);
+
+ return Orderline;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js
new file mode 100644
index 00000000000..689c3bf40ca
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js
@@ -0,0 +1,28 @@
+odoo.define('pos_restaurant.ReceiptScreen', function(require) {
+ 'use strict';
+
+ const ReceiptScreen = require('point_of_sale.ReceiptScreen');
+ const Registries = require('point_of_sale.Registries');
+
+ const PosResReceiptScreen = ReceiptScreen =>
+ class extends ReceiptScreen {
+ /**
+ * @override
+ */
+ get nextScreen() {
+ if (
+ this.env.pos.config.module_pos_restaurant &&
+ this.env.pos.config.iface_floorplan
+ ) {
+ const table = this.env.pos.table;
+ return { name: 'FloorScreen', props: { floor: table ? table.floor : null } };
+ } else {
+ return super.nextScreen;
+ }
+ }
+ };
+
+ Registries.Component.extend(ReceiptScreen, PosResReceiptScreen);
+
+ return ReceiptScreen;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js
new file mode 100644
index 00000000000..1c5613b3166
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitBillScreen.js
@@ -0,0 +1,164 @@
+odoo.define('pos_restaurant.SplitBillScreen', function(require) {
+ 'use strict';
+
+ const PosComponent = require('point_of_sale.PosComponent');
+ const { useState } = owl.hooks;
+ const { useListener } = require('web.custom_hooks');
+ const models = require('point_of_sale.models');
+ const Registries = require('point_of_sale.Registries');
+
+ class SplitBillScreen extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click-line', this.onClickLine);
+ this.splitlines = useState(this._initSplitLines(this.env.pos.get_order()));
+ this.newOrderLines = {};
+ this.newOrder = new models.Order(
+ {},
+ {
+ pos: this.env.pos,
+ temporary: true,
+ }
+ );
+ this._isFinal = false;
+ }
+ mounted() {
+ this.env.pos.on('change:selectedOrder', this._resetState, this);
+ }
+ willUnmount() {
+ this.env.pos.off('change:selectedOrder', null, this);
+ }
+ get currentOrder() {
+ return this.env.pos.get_order();
+ }
+ get orderlines() {
+ return this.currentOrder.get_orderlines();
+ }
+ onClickLine(event) {
+ const line = event.detail;
+ this._splitQuantity(line);
+ this._updateNewOrder(line);
+ }
+ back() {
+ this.showScreen('ProductScreen');
+ }
+ proceed() {
+ if (_.isEmpty(this.splitlines))
+ // Splitlines is empty
+ return;
+
+ this._isFinal = true;
+ delete this.newOrder.temporary;
+
+ if (this._isFullPayOrder()) {
+ this.showScreen('PaymentScreen');
+ } else {
+ this._setQuantityOnCurrentOrder();
+
+ this.newOrder.set_screen_data('screen', { name: 'PaymentScreen' });
+
+ // for the kitchen printer we assume that everything
+ // has already been sent to the kitchen before splitting
+ // the bill. So we save all changes both for the old
+ // order and for the new one. This is not entirely correct
+ // but avoids flooding the kitchen with unnecessary orders.
+ // Not sure what to do in this case.
+
+ if (this.newOrder.saveChanges) {
+ this.currentOrder.saveChanges();
+ this.newOrder.saveChanges();
+ }
+
+ this.newOrder.set_customer_count(1);
+ const newCustomerCount = this.currentOrder.get_customer_count() - 1;
+ this.currentOrder.set_customer_count(newCustomerCount || 1);
+ this.currentOrder.set_screen_data('screen', { name: 'ProductScreen' });
+
+ this.env.pos.get('orders').add(this.newOrder);
+ this.env.pos.set('selectedOrder', this.newOrder);
+ }
+ }
+ /**
+ * @param {models.Order} order
+ * @returns {Object<{ quantity: number }>} splitlines
+ */
+ _initSplitLines(order) {
+ const splitlines = {};
+ for (let line of order.get_orderlines()) {
+ splitlines[line.id] = { quantity: 0 };
+ }
+ return splitlines;
+ }
+ _splitQuantity(line) {
+ const split = this.splitlines[line.id];
+ if (!line.get_unit().is_pos_groupable) {
+ if (split.quantity !== line.get_quantity()) {
+ split.quantity = line.get_quantity();
+ } else {
+ split.quantity = 0;
+ }
+ } else {
+ if (split.quantity < line.get_quantity()) {
+ split.quantity += line.get_unit().is_pos_groupable
+ ? 1
+ : line.get_unit().rounding;
+ if (split.quantity > line.get_quantity()) {
+ split.quantity = line.get_quantity();
+ }
+ } else {
+ split.quantity = 0;
+ }
+ }
+ }
+ _updateNewOrder(line) {
+ const split = this.splitlines[line.id];
+ let orderline = this.newOrderLines[line.id];
+ if (split.quantity) {
+ if (!orderline) {
+ orderline = line.clone();
+ this.newOrder.add_orderline(orderline);
+ this.newOrderLines[line.id] = orderline;
+ }
+ orderline.set_quantity(split.quantity, 'do not recompute unit price');
+ } else if (orderline) {
+ this.newOrder.remove_orderline(orderline);
+ this.newOrderLines[line.id] = null;
+ }
+ }
+ _isFullPayOrder() {
+ return _.every(this.currentOrder.get_orderlines(), orderLine => {
+ var split = this.splitlines[orderLine.id];
+ return split && split.quantity === orderLine.get_quantity();
+ });
+ }
+ _setQuantityOnCurrentOrder() {
+ for (var id in this.splitlines) {
+ var split = this.splitlines[id];
+ var line = this.currentOrder.get_orderline(parseInt(id));
+ line.set_quantity(
+ line.get_quantity() - split.quantity,
+ 'do not recompute unit price'
+ );
+ if (Math.abs(line.get_quantity()) < 0.00001) {
+ this.currentOrder.remove_orderline(line);
+ }
+ }
+ }
+ _resetState() {
+ if (this._isFinal) return;
+
+ for (let id in this.splitlines) {
+ delete this.splitlines[id];
+ }
+ for (let line of this.currentOrder.get_orderlines()) {
+ this.splitlines[line.id] = { quantity: 0 };
+ }
+ this.newOrder.orderlines.reset();
+ }
+ }
+ SplitBillScreen.template = 'SplitBillScreen';
+
+ Registries.Component.add(SplitBillScreen);
+
+ return SplitBillScreen;
+});
diff --git a/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitOrderline.js b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitOrderline.js
new file mode 100644
index 00000000000..51c0b9296f8
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/Screens/SplitBillScreen/SplitOrderline.js
@@ -0,0 +1,25 @@
+odoo.define('pos_restaurant.SplitOrderline', function(require) {
+ 'use strict';
+
+ const { useListener } = require('web.custom_hooks');
+ const PosComponent = require('point_of_sale.PosComponent');
+ const Registries = require('point_of_sale.Registries');
+
+ class SplitOrderline extends PosComponent {
+ constructor() {
+ super(...arguments);
+ useListener('click', this.onClick);
+ }
+ get isSelected() {
+ return this.props.split.quantity !== 0;
+ }
+ onClick() {
+ this.trigger('click-line', this.props.line);
+ }
+ }
+ SplitOrderline.template = 'SplitOrderline';
+
+ Registries.Component.add(SplitOrderline);
+
+ return SplitOrderline;
+});
diff --git a/addons/pos_restaurant/static/src/js/floors.js b/addons/pos_restaurant/static/src/js/floors.js
index 0a4f711a868..1276fea9f8b 100644
--- a/addons/pos_restaurant/static/src/js/floors.js
+++ b/addons/pos_restaurant/static/src/js/floors.js
@@ -1,17 +1,7 @@
odoo.define('pos_restaurant.floors', function (require) {
"use strict";
-var PosBaseWidget = require('point_of_sale.BaseWidget');
-var chrome = require('point_of_sale.chrome');
-var gui = require('point_of_sale.gui');
var models = require('point_of_sale.models');
-var screens = require('point_of_sale.screens');
-var core = require('web.core');
-var rpc = require('web.rpc');
-var session = require('web.session');
-
-var QWeb = core.qweb;
-var _t = core._t;
// At POS Startup, load the floors, and add them to the pos model
models.load_models({
@@ -52,707 +42,6 @@ models.load_models({
},
});
-// The Table GUI element, should always be a child of the FloorScreenWidget
-var TableWidget = PosBaseWidget.extend({
- template: 'TableWidget',
- init: function(parent, options){
- this._super(parent, options);
- this.table = options.table;
- this.selected = false;
- this.moved = false;
- this.dragpos = {x:0, y:0};
- },
- // computes the absolute position of a DOM mouse event, used
- // when resizing tables
- event_position: function(event){
- if(event.touches && event.touches[0]){
- return {x: event.touches[0].screenX, y: event.touches[0].screenY};
- }else{
- return {x: event.screenX, y: event.screenY};
- }
- },
- // when a table is clicked, go to the table's orders
- // but if we're editing, we select/deselect it.
- click_handler: function(){
- var self = this;
- var floorplan = this.getParent();
- if (floorplan.editing) {
- setTimeout(function(){ // in a setTimeout to debounce with drag&drop start
- if (!self.dragging) {
- if (self.moved) {
- self.moved = false;
- } else if (!self.selected) {
- self.getParent().select_table(self);
- } else {
- self.getParent().deselect_tables();
- }
- }
- },50);
- } else {
- floorplan.pos.set_table(this.table);
- }
- },
- // drag and drop for moving the table, at drag start
- dragstart_handler: function(event, ui){
- this.dragging = true;
- },
- // drag and drop for moving the table, at drag end
- dragend_handler: function(event, ui){
- this.dragging = false;
- this.moved = true;
- this.table.position_h = ui.position.left - this.table.width/2;
- this.table.position_v = ui.position.top - this.table.height/2;
- this.$el.css(this.table_style());
- },
- // drag and dropping the resizing handles
- handle_dragmove_handler: function(event, ui) {
- this.moved = true;
- this.table.width = ui.size.width;
- this.table.height = ui.size.height;
- this.table.position_h = ui.position.left - ui.originalSize.width/2;
- this.table.position_v = ui.position.top - ui.originalSize.height/2;
- this.$el.css(this.table_style());
- },
- set_table_color: function(color){
- this.table.color = _.escape(color);
- this.$el.css({'background': this.table.color});
- },
- set_table_name: function(name){
- if (name) {
- this.table.name = name;
- this.renderElement();
- }
- },
- set_table_seats: function(seats){
- if (seats) {
- this.table.seats = Number(seats);
- this.renderElement();
- }
- },
- // The table's positioning is handled via css absolute positioning,
- // which is handled here.
- table_style: function(){
- var table = this.table;
- function unit(val){ return '' + val + 'px'; }
- var style = {
- 'width': unit(table.width),
- 'height': unit(table.height),
- 'line-height': unit(table.height),
- 'margin-left': unit(-table.width/2),
- 'margin-top': unit(-table.height/2),
- 'top': unit(table.position_v + table.height/2),
- 'left': unit(table.position_h + table.width/2),
- 'border-radius': table.shape === 'round' ?
- unit(Math.max(table.width,table.height)/2) : '3px',
- };
- if (table.color) {
- style.background = table.color;
- }
- if (table.height >= 150 && table.width >= 150) {
- style['font-size'] = '32px';
- }
-
- return style;
- },
- // convert the style dictionary to a ; separated string for inclusion in templates
- table_style_str: function(){
- var style = this.table_style();
- var str = "";
- var s;
- for (s in style) {
- str += s + ":" + style[s] + "; ";
- }
- return str;
- },
- // select the table (should be called via the floorplan)
- select: function() {
- this.selected = true;
- this.renderElement();
-
- this.$el.resizable({
- handles: 'all',
- resize: this.handle_dragmove_handler.bind(this),
- });
-
- this.$el.draggable({
- stop: this.dragend_handler.bind(this),
- });
- },
- // deselect the table (should be called via the floorplan)
- deselect: function() {
- this.selected = false;
- this.renderElement();
- this.save_changes();
- },
- // sends the table's modification to the server
- save_changes: function(){
- var self = this;
- var fields = _.find(this.pos.models,function(model){ return model.model === 'restaurant.table'; }).fields;
-
- // we need a serializable copy of the table, containing only the fields defined on the server
- var serializable_table = {};
- for (var i = 0; i < fields.length; i++) {
- if (typeof this.table[fields[i]] !== 'undefined') {
- serializable_table[fields[i]] = this.table[fields[i]];
- }
- }
- // and the id ...
- serializable_table.id = this.table.id;
-
- rpc.query({
- model: 'restaurant.table',
- method: 'create_from_ui',
- args: [serializable_table],
- })
- .then(function (table_id){
- rpc.query({
- model: 'restaurant.table',
- method: 'search_read',
- args: [[['id', '=', table_id]], fields],
- limit: 1,
- })
- .then(function (result){
- var table = result[0];
- for (var field in table) {
- self.table[field] = table[field];
- }
- self.pos.tables_by_id[table.id] = self.table;
- // If selected, render with drag and resize event handlers.
- if (!self.selected) {
- self.renderElement();
- } else {
- self.select();
- }
- });
- }, function(type,err) {
- self.gui.show_sync_error_popup();
- });
- },
- // destroy the table. We do not really destroy it, we set it
- // to inactive so that it doesn't show up anymore, but it still
- // available on the database for the orders that depend on it.
- trash: function(){
- var self = this;
- rpc.query({
- model: 'restaurant.table',
- method: 'create_from_ui',
- args: [{'active':false,'id':this.table.id}],
- })
- .then(function (table_id){
- // Removing all references from the table and the table_widget in in the UI ...
- for (var i = 0; i < self.pos.floors.length; i++) {
- var floor = self.pos.floors[i];
- for (var j = 0; j < floor.tables.length; j++) {
- if (floor.tables[j].id === table_id) {
- floor.tables.splice(j,1);
- break;
- }
- }
- }
- var floorplan = self.getParent();
- for (var i = 0; i < floorplan.table_widgets.length; i++) {
- if (floorplan.table_widgets[i] === self) {
- floorplan.table_widgets.splice(i,1);
- }
- }
- if (floorplan.selected_table === self) {
- floorplan.selected_table = null;
- }
- floorplan.update_toolbar();
- self.destroy();
- }, function(type, err) {
- self.gui.show_sync_error_popup();
- });
- },
- get_notifications: function(){ //FIXME : Make this faster
- var orders = this.pos.get_table_orders(this.table);
- var notifications = {};
- for (var i = 0; i < orders.length; i++) {
- if (orders[i].hasChangesToPrint()) {
- notifications.printing = true;
- break;
- } else if (orders[i].hasSkippedChanges()) {
- notifications.skipped = true;
- }
- }
- return notifications;
- },
- update_click_handlers: function(editing){
- var self = this;
- this.$el.off('mouseup touchend touchcancel click dragend');
-
- if (editing) {
- this.$el.on('mouseup touchend touchcancel', function(event){ self.click_handler(event,$(this)); });
- } else {
- this.$el.on('click dragend', function(event){ self.click_handler(event,$(this)); });
- }
- },
- renderElement: function(){
- var self = this;
- this.order_count = this.table.order_count !== undefined ?
- this.table.order_count :
- this.pos.get_table_orders(this.table)
- .filter(o => o.orderlines.length !== 0 || o.paymentlines.length !==0).length;
- this.customer_count = this.pos.get_customer_count(this.table);
- this.fill = Math.min(1,Math.max(0,this.customer_count / this.table.seats));
- this.notifications = this.get_notifications();
- this._super();
-
- this.update_click_handlers();
- },
-});
-
-// The screen that allows you to select the floor, see and select the table,
-// as well as edit them.
-var FloorScreenWidget = screens.ScreenWidget.extend({
- template: 'FloorScreenWidget',
-
- // Ignore products, discounts, and client barcodes
- barcode_product_action: function(code){},
- barcode_discount_action: function(code){},
- barcode_client_action: function(code){},
-
- init: function(parent, options) {
- this._super(parent, options);
- this.floor = this.pos.floors[0];
- this.table_widgets = [];
- this.selected_table = null;
- this.editing = false;
- },
- _table_longpolling: function(){
- if (this.editing) {
- return;
- }
- var self = this;
- rpc.query({
- model: 'pos.config',
- method: 'get_tables_order_count',
- args: [this.pos.config.id],
- })
- .then(function (result){
- result.forEach(function(table){
- var table_obj = self.pos.tables_by_id[table.id];
- var unsynced_orders = self.pos.get_table_orders(table_obj)
- .filter(o => o.server_id === undefined &&
- (o.orderlines.length !== 0 || o.paymentlines.length !== 0)).length
- table_obj.order_count = table.orders + unsynced_orders;
- });
- self.table_widgets.forEach(
- function(tw){
- tw.renderElement();
- });
- }, function(type,err) {
- self.table_widgets.forEach(
- function(tw){
- tw.table.order_count = self.pos.get_table_orders(tw.table)
- .filter(o => o.orderlines.length !== 0 || o.paymentlines.length !== 0).length
- tw.renderElement();
- });
- });
- },
- hide: function(){
- this._super();
- if (this.editing) {
- this.toggle_editing();
- }
- this.chrome.widget.order_selector.show();
- clearInterval(this.table_longpolling);
- },
- show: function(){
- this._super();
- this.chrome.widget.order_selector.hide();
- for (var i = 0; i < this.table_widgets.length; i++) {
- this.table_widgets[i].renderElement();
- }
- this.check_empty_floor();
-
- this._table_longpolling();
- this.table_longpolling = setInterval(this._table_longpolling.bind(this), 5000);
- },
- click_floor_button: function(event,$el){
- var floor = this.pos.floors_by_id[$el.data('id')];
- if (floor !== this.floor) {
- if (this.editing) {
- this.toggle_editing();
- }
- this.floor = floor;
- this.selected_table = null;
- this.renderElement();
- this.check_empty_floor();
- }
- },
- background_image_url: function(floor) {
- return '/web/image?model=restaurant.floor&id='+floor.id+'&field=background_image';
- },
- get_floor_style: function() {
- var style = "";
- if (this.floor.background_image) {
- style += "background-image: url(" + this.background_image_url(this.floor) + "); ";
- }
- if (this.floor.background_color) {
- style += "background-color: " + _.escape(this.floor.background_color) + ";";
- }
- return style;
- },
- set_background_color: function(background) {
- var self = this;
- this.floor.background_color = background;
- rpc.query({
- model: 'restaurant.floor',
- method: 'write',
- args: [[this.floor.id], {'background_color': background}],
- })
- .guardedCatch(function (){
- self.gui.show_sync_error_popup();
- });
- this.$('.floor-map').css({"background-color": _.escape(background)});
- },
- deselect_tables: function(){
- for (var i = 0; i < this.table_widgets.length; i++) {
- var table = this.table_widgets[i];
- if (table.selected) {
- table.deselect();
- }
- }
- this.selected_table = null;
- this.update_toolbar();
- },
- select_table: function(table_widget){
- if (!table_widget.selected) {
- this.deselect_tables();
- table_widget.select();
- this.selected_table = table_widget;
- this.update_toolbar();
- }
- },
- tool_shape_action: function(){
- if (this.selected_table) {
- var table = this.selected_table.table;
- if (table.shape === 'square') {
- table.shape = 'round';
- } else {
- table.shape = 'square';
- }
- this.selected_table.renderElement();
- this.update_toolbar();
- }
- },
- tool_colorpicker_open: function(){
- this.$('.color-picker').addClass('oe_hidden');
- if (this.selected_table) {
- this.$('.color-picker.fg-picker').removeClass('oe_hidden');
- } else {
- this.$('.color-picker.bg-picker').removeClass('oe_hidden');
- }
- },
- tool_colorpicker_pick: function(event,$el){
- if (this.selected_table) {
- this.selected_table.set_table_color($el[0].style['background-color']);
- } else {
- this.set_background_color($el[0].style['background-color']);
- }
- },
- tool_colorpicker_close: function(){
- this.$('.color-picker').addClass('oe_hidden');
- },
- tool_rename_table: function(){
- var self = this;
- if (this.selected_table) {
- this.gui.show_popup('textinput',{
- 'title':_t('Table Name ?'),
- 'value': this.selected_table.table.name,
- 'confirm': function(value) {
- self.selected_table.set_table_name(value);
- },
- });
- }
- },
- tool_change_seats: function(){
- var self = this;
- if (this.selected_table) {
- this.gui.show_popup('number',{
- 'title':_t('Number of Seats ?'),
- 'cheap': true,
- 'value': this.selected_table.table.seats,
- 'confirm': function(value) {
- self.selected_table.set_table_seats(value);
- },
- });
- }
- },
- tool_duplicate_table: function(){
- if (this.selected_table) {
- var tw = this.create_table(this.selected_table.table);
- tw.table.position_h += 10;
- tw.table.position_v += 10;
- tw.save_changes();
- this.select_table(tw);
- }
- },
- tool_new_table: function(){
- var tw = this.create_table({
- 'position_v': 100,
- 'position_h': 100,
- 'width': 75,
- 'height': 75,
- 'shape': 'square',
- 'seats': 1,
- });
- tw.save_changes();
- this.select_table(tw);
- this.check_empty_floor();
- },
- new_table_name: function(name){
- if (name) {
- var num = Number((name.match(/\d+/g) || [])[0] || 0);
- var str = (name.replace(/\d+/g,''));
- var n = {num: num, str:str};
- n.num += 1;
- this.last_name = n;
- } else if (this.last_name) {
- this.last_name.num += 1;
- } else {
- this.last_name = {num: 1, str:'T'};
- }
- return '' + this.last_name.str + this.last_name.num;
- },
- create_table: function(params) {
- var table = {};
- for (var p in params) {
- table[p] = params[p];
- }
-
- table.name = this.new_table_name(params.name);
-
- delete table.id;
- table.floor_id = [this.floor.id,''];
- table.floor = this.floor;
-
- this.floor.tables.push(table);
- var tw = new TableWidget(this,{table: table});
- tw.appendTo('.floor-map .tables');
- this.table_widgets.push(tw);
- return tw;
- },
- tool_trash_table: function(){
- var self = this;
- if (this.selected_table) {
- this.gui.show_popup('confirm',{
- 'title': _t('Are you sure ?'),
- 'comment':_t('Removing a table cannot be undone'),
- 'confirm': function(){
- self.selected_table.trash();
- },
- });
- }
- },
- toggle_editing: function(){
- this.editing = !this.editing;
- this.update_toolbar();
- this.update_table_click_handlers();
-
- if (!this.editing) {
- this.deselect_tables();
- }
- },
- update_table_click_handlers: function(){
- for (var i = 0; i < this.table_widgets.length; ++i) {
- if (this.editing) {
- this.table_widgets[i].update_click_handlers("editing");
- } else {
- this.table_widgets[i].update_click_handlers();
- }
- }
- },
- check_empty_floor: function(){
- if (!this.floor.tables.length) {
- if (!this.editing) {
- this.toggle_editing();
- }
- this.$('.empty-floor').removeClass('oe_hidden');
- } else {
- this.$('.empty-floor').addClass('oe_hidden');
- }
- },
- update_toolbar: function(){
-
- if (this.editing) {
- this.$('.edit-bar').removeClass('oe_hidden');
- this.$('.edit-button.editing').addClass('active');
- } else {
- this.$('.edit-bar').addClass('oe_hidden');
- this.$('.edit-button.editing').removeClass('active');
- }
-
- if (this.selected_table) {
- this.$('.needs-selection').removeClass('disabled');
- var table = this.selected_table.table;
- if (table.shape === 'square') {
- this.$('.button-option.square').addClass('oe_hidden');
- this.$('.button-option.round').removeClass('oe_hidden');
- } else {
- this.$('.button-option.square').removeClass('oe_hidden');
- this.$('.button-option.round').addClass('oe_hidden');
- }
- } else {
- this.$('.needs-selection').addClass('disabled');
- }
- this.tool_colorpicker_close();
- },
- renderElement: function(){
- var self = this;
-
- // cleanup table widgets from previous renders
- for (var i = 0; i < this.table_widgets.length; i++) {
- this.table_widgets[i].destroy();
- }
-
- this.table_widgets = [];
-
- this._super();
-
- for (var i = 0; i < this.floor.tables.length; i++) {
- var tw = new TableWidget(this,{
- table: this.floor.tables[i],
- });
- tw.appendTo(this.$('.floor-map .tables'));
- this.table_widgets.push(tw);
- }
-
- $('body').on('keyup', function (event) {
- if (event.which === $.ui.keyCode.ESCAPE) {
- if(self.editing) {
- self.toggle_editing();
- }
- }
- });
-
- this.$('.floor-selector .button').click(function(event){
- self.click_floor_button(event,$(this));
- });
-
- this.$('.edit-button.shape').click(function(){
- self.tool_shape_action();
- });
-
- this.$('.edit-button.color').click(function(){
- self.tool_colorpicker_open();
- });
-
- this.$('.edit-button.dup-table').click(function(){
- self.tool_duplicate_table();
- });
-
- this.$('.edit-button.new-table').click(function(){
- self.tool_new_table();
- });
-
- this.$('.edit-button.rename').click(function(){
- self.tool_rename_table();
- });
-
- this.$('.edit-button.seats').click(function(){
- self.tool_change_seats();
- });
-
- this.$('.edit-button.trash').click(function(){
- self.tool_trash_table();
- });
-
- this.$('.color-picker .close-picker').click(function(event){
- self.tool_colorpicker_close();
- event.stopPropagation();
- });
-
- this.$('.color-picker .color').click(function(event){
- self.tool_colorpicker_pick(event,$(this));
- event.stopPropagation();
- });
-
- this.$('.edit-button.editing').click(function(){
- self.toggle_editing();
- });
-
- this.$('.floor-map,.floor-map .tables').click(function(event){
- if (event.target === self.$('.floor-map')[0] ||
- event.target === self.$('.floor-map .tables')[0]) {
- self.deselect_tables();
- }
- });
-
- this.$('.color-picker .close-picker').click(function(event){
- self.tool_colorpicker_close();
- event.stopPropagation();
- });
-
- this.update_toolbar();
-
- },
-});
-
-screens.ProductScreenWidget.include({
- show: function () {
- var self = this;
- this._super();
- if (this.pos.config.iface_floorplan) {
- this.$el.bind("mousemove mousedown touchstart click scroll keypress", function () {
- self.set_idle_timer();
- });
- this.set_idle_timer();
- }
- },
- hide: function () {
- this._super();
- if (this.pos.config.iface_floorplan) {
- this.$el.unbind("mousemove mousedown touchstart click scroll keypress");
- clearTimeout(this.idle_timer);
- }
- },
- /**
- * Set a timeout to go back to the floorplan and clear the previous timeout
- *
- * @param {number} timeout, optional timeout in miliseconds, default one minute.
- */
- set_idle_timer: function(timeout=60000) {
- var self = this;
- clearTimeout(this.idle_timer);
- this.idle_timer = setTimeout(function () {
- self.pos.set_table(null);
- }, timeout);
- },
-});
-
-gui.define_screen({
- 'name': 'floors',
- 'widget': FloorScreenWidget,
- 'condition': function(){
- return this.pos.config.iface_floorplan;
- },
-});
-
-gui.Gui.include({
- show_sync_error_popup: function() {
- if (this.show_sync_errors) {
- this.show_popup('error-sync',{
- 'title':_t('Changes could not be saved'),
- 'body': _t('You must be connected to the internet to save your changes.\n\n' +
- 'Changes made to previously synced orders will get lost at the next sync.\n' +
- 'Orders that where not synced before will be synced next time you open and close the same table.'),
- });
- }
- },
-});
-
-// Add the FloorScreen to the GUI, and set it as the default screen
-chrome.Chrome.include({
- build_widgets: function(){
- this._super();
- if (this.pos.config.iface_floorplan) {
- this.gui.set_startup_screen('floors');
- }
- },
-});
-
// New orders are now associated with the current table, if any.
var _super_order = models.Order.prototype;
models.Order = models.Order.extend({
@@ -795,37 +84,6 @@ models.Order = models.Order.extend({
},
});
-// We need to modify the OrderSelector to hide itself when we're on
-// the floor plan
-chrome.OrderSelectorWidget.include({
- floor_button_click_handler: function(){
- this.pos.set_table(null);
- },
- hide: function(){
- this.$el.addClass('oe_invisible');
- },
- show: function(){
- this.$el.removeClass('oe_invisible');
- },
- renderElement: function(){
- var self = this;
- this._super();
- if (this.pos.config.iface_floorplan) {
- if (this.pos.get_order()) {
- if (this.pos.table && this.pos.table.floor) {
- this.$('.orders').prepend(QWeb.render('BackToFloorButton',{table: this.pos.table, floor:this.pos.table.floor}));
- this.$('.floor-button').click(function(){
- self.floor_button_click_handler();
- });
- }
- this.$el.removeClass('oe_invisible');
- } else {
- this.$el.addClass('oe_invisible');
- }
- }
- },
-});
-
// We need to change the way the regular UI sees the orders, it
// needs to only see the orders associated with the current table,
// and when an order is validated, it needs to go back to the floor map.
@@ -834,8 +92,8 @@ chrome.OrderSelectorWidget.include({
// if there is none.
var _super_posmodel = models.PosModel.prototype;
models.PosModel = models.PosModel.extend({
- after_load_server_data: function() {
- var res = _super_posmodel.after_load_server_data.call(this);
+ after_load_server_data: async function() {
+ var res = await _super_posmodel.after_load_server_data.call(this);
if (this.config.iface_floorplan) {
this.table = null;
}
@@ -854,11 +112,12 @@ models.PosModel = models.PosModel.extend({
remove_from_server_and_set_sync_state: function(ids_to_remove){
var self = this;
this.set_synch('connecting', ids_to_remove.length);
- self._remove_from_server(ids_to_remove)
+ return self._remove_from_server(ids_to_remove)
.then(function(server_ids) {
self.set_synch('connected');
}).catch(function(reason){
self.set_synch('error');
+ throw reason;
});
},
@@ -871,13 +130,12 @@ models.PosModel = models.PosModel.extend({
*/
_get_from_server: function (table_id, options) {
options = options || {};
- var self = this;
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500;
- return rpc.query({
+ return this.rpc({
model: 'pos.order',
method: 'get_table_draft_orders',
args: [table_id],
- kwargs: {context: session.user_context},
+ kwargs: {context: this.session.user_context},
}, {
timeout: timeout,
shadow: false,
@@ -947,8 +205,6 @@ models.PosModel = models.PosModel.extend({
var self = this;
var ids_to_remove = this.db.get_ids_to_remove_from_server();
- clearInterval(this.table_longpolling);
-
this.set_synch('connecting', 1);
this._get_from_server(table.id).then(function (server_orders) {
var orders = self.get_order_list();
@@ -1085,9 +341,8 @@ models.PosModel = models.PosModel.extend({
}
if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
this.set_order(order_list[index] || order_list[order_list.length -1]);
- }else{
- // back to the floor plan
- this.set_table(null);
+ } else if (order_list.length === 0) {
+ this.set_order(null);
}
} else {
_super_posmodel.on_removed_order.apply(this,arguments);
@@ -1097,68 +352,4 @@ models.PosModel = models.PosModel.extend({
});
-var TableGuestsButton = screens.ActionButtonWidget.extend({
- template: 'TableGuestsButton',
- guests: function() {
- if (this.pos.get_order()) {
- return this.pos.get_order().customer_count;
- } else {
- return 0;
- }
- },
- button_click: function() {
- var self = this;
- this.gui.show_popup('number', {
- 'title': _t('Guests ?'),
- 'cheap': true,
- 'value': this.pos.get_order().customer_count,
- 'confirm': function(value) {
- value = Math.max(1,Number(value));
- self.pos.get_order().set_customer_count(value);
- self.renderElement();
- },
- });
- },
-});
-
-screens.OrderWidget.include({
- update_summary: function(){
- this._super();
- if (this.getParent().action_buttons &&
- this.getParent().action_buttons.guests) {
- this.getParent().action_buttons.guests.renderElement();
- }
- },
-});
-
-screens.define_action_button({
- 'name': 'guests',
- 'widget': TableGuestsButton,
- 'condition': function(){
- return this.pos.config.iface_floorplan;
- },
-});
-
-var TransferOrderButton = screens.ActionButtonWidget.extend({
- template: 'TransferOrderButton',
- button_click: function() {
- this.pos.transfer_order_to_different_table();
- },
-});
-
-screens.define_action_button({
- 'name': 'transfer',
- 'widget': TransferOrderButton,
- 'condition': function(){
- return this.pos.config.iface_floorplan;
- },
-});
-
-return {
- TableGuestsButton: TableGuestsButton,
- TransferOrderButton:TransferOrderButton,
- TableWidget: TableWidget,
- FloorScreenWidget: FloorScreenWidget,
-};
-
});
diff --git a/addons/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js
index d284917b940..709de43ef7b 100644
--- a/addons/pos_restaurant/static/src/js/multiprint.js
+++ b/addons/pos_restaurant/static/src/js/multiprint.js
@@ -2,7 +2,6 @@ odoo.define('pos_restaurant.multiprint', function (require) {
"use strict";
var models = require('point_of_sale.models');
-var screens = require('point_of_sale.screens');
var core = require('web.core');
var Printer = require('point_of_sale.Printer').Printer;
@@ -125,36 +124,6 @@ models.Orderline = models.Orderline.extend({
},
});
-screens.OrderWidget.include({
- render_orderline: function(orderline) {
- var node = this._super(orderline);
- if (this.pos.config.iface_printers) {
- if (orderline.mp_skip) {
- node.classList.add('skip');
- } else if (orderline.mp_dirty) {
- node.classList.add('dirty');
- }
- }
- return node;
- },
- click_line: function(line, event) {
- if (!this.pos.config.iface_printers) {
- this._super(line, event);
- } else if (this.pos.get_order().selected_orderline !== line) {
- this.mp_dbclk_time = (new Date()).getTime();
- } else if (!this.mp_dbclk_time) {
- this.mp_dbclk_time = (new Date()).getTime();
- } else if (this.mp_dbclk_time + 500 > (new Date()).getTime()) {
- line.set_skip(!line.mp_skip);
- this.mp_dbclk_time = 0;
- } else {
- this.mp_dbclk_time = (new Date()).getTime();
- }
-
- this._super(line, event);
- },
-});
-
var _super_order = models.Order.prototype;
models.Order = models.Order.extend({
build_line_resume: function(){
@@ -299,13 +268,18 @@ models.Order = models.Order.extend({
},
printChanges: async function(){
var printers = this.pos.printers;
+ let isPrintSuccessful = true;
for(var i = 0; i < printers.length; i++){
var changes = this.computeChanges(printers[i].config.product_categories_ids);
if ( changes['new'].length > 0 || changes['cancelled'].length > 0){
var receipt = QWeb.render('OrderChangeReceipt',{changes:changes, widget:this});
- await printers[i].print_receipt(receipt);
+ const result = await printers[i].print_receipt(receipt);
+ if (!result.successful) {
+ isPrintSuccessful = false;
+ }
}
}
+ return isPrintSuccessful;
},
hasChangesToPrint: function(){
var printers = this.pos.printers;
@@ -337,41 +311,5 @@ models.Order = models.Order.extend({
},
});
-var SubmitOrderButton = screens.ActionButtonWidget.extend({
- 'template': 'SubmitOrderButton',
- button_click: async function(){
- var order = this.pos.get_order();
- if(order.hasChangesToPrint()){
- await order.printChanges();
- order.saveChanges();
- }
- },
-});
-
-screens.define_action_button({
- 'name': 'submit_order',
- 'widget': SubmitOrderButton,
- 'condition': function() {
- return this.pos.printers.length;
- },
-});
-
-screens.OrderWidget.include({
- update_summary: function(){
- this._super();
- var changes = this.pos.get_order().hasChangesToPrint();
- var skipped = changes ? false : this.pos.get_order().hasSkippedChanges();
- var buttons = this.getParent().action_buttons;
-
- if (buttons && buttons.submit_order) {
- buttons.submit_order.highlight(changes);
- buttons.submit_order.altlight(skipped);
- }
- },
-});
-
-return {
- SubmitOrderButton: SubmitOrderButton,
-}
});
diff --git a/addons/pos_restaurant/static/src/js/notes.js b/addons/pos_restaurant/static/src/js/notes.js
index a000b9fd871..c2ed95ddd26 100644
--- a/addons/pos_restaurant/static/src/js/notes.js
+++ b/addons/pos_restaurant/static/src/js/notes.js
@@ -2,14 +2,8 @@ odoo.define('pos_restaurant.notes', function (require) {
"use strict";
var models = require('point_of_sale.models');
-var screens = require('point_of_sale.screens');
-var core = require('web.core');
-
-var QWeb = core.qweb;
-var _t = core._t;
var _super_orderline = models.Orderline.prototype;
-
models.Orderline = models.Orderline.extend({
initialize: function(attr, options) {
_super_orderline.initialize.call(this,attr,options);
@@ -45,30 +39,4 @@ models.Orderline = models.Orderline.extend({
},
});
-var OrderlineNoteButton = screens.ActionButtonWidget.extend({
- template: 'OrderlineNoteButton',
- button_click: function(){
- var line = this.pos.get_order().get_selected_orderline();
- if (line) {
- this.gui.show_popup('textarea',{
- title: _t('Add Note'),
- value: line.get_note(),
- confirm: function(note) {
- line.set_note(note);
- },
- });
- }
- },
-});
-
-screens.define_action_button({
- 'name': 'orderline_note',
- 'widget': OrderlineNoteButton,
- 'condition': function(){
- return this.pos.config.iface_orderline_notes;
- },
-});
-return {
- OrderlineNoteButton: OrderlineNoteButton,
-}
});
diff --git a/addons/pos_restaurant/static/src/js/printbill.js b/addons/pos_restaurant/static/src/js/printbill.js
deleted file mode 100644
index 02e7c3bd36c..00000000000
--- a/addons/pos_restaurant/static/src/js/printbill.js
+++ /dev/null
@@ -1,57 +0,0 @@
-odoo.define('pos_restaurant.printbill', function (require) {
-"use strict";
-
-var core = require('web.core');
-var screens = require('point_of_sale.screens');
-var gui = require('point_of_sale.gui');
-var _t = core._t;
-var QWeb = core.qweb;
-
-var BillScreenWidget = screens.ReceiptScreenWidget.extend({
- template: 'BillScreenWidget',
- click_next: function(){
- this.gui.show_screen('products');
- },
- click_back: function(){
- this.gui.show_screen('products');
- },
- get_receipt_render_env: function(){
- var render_env = this._super();
- render_env.receipt.bill = true;
- return render_env;
- },
- render_receipt: function(){
- this._super();
- this.$('.receipt-change').remove();
- },
-});
-
-gui.define_screen({name:'bill', widget: BillScreenWidget});
-
-var PrintBillButton = screens.ActionButtonWidget.extend({
- template: 'PrintBillButton',
- button_click: function(){
- var order = this.pos.get('selectedOrder');
- if(order.get_orderlines().length > 0) {
- this.gui.show_screen('bill');
- } else {
- this.gui.show_popup('error', {
- 'title': _t('Nothing to Print'),
- 'body': _t('There are no order lines'),
- });
- }
- },
-});
-
-screens.define_action_button({
- 'name': 'print_bill',
- 'widget': PrintBillButton,
- 'condition': function(){
- return this.pos.config.iface_printbill;
- },
-});
-return {
- BillScreenWidget: BillScreenWidget,
- PrintBillButton: PrintBillButton,
-};
-});
diff --git a/addons/pos_restaurant/static/src/js/splitbill.js b/addons/pos_restaurant/static/src/js/splitbill.js
deleted file mode 100644
index 12e11769de9..00000000000
--- a/addons/pos_restaurant/static/src/js/splitbill.js
+++ /dev/null
@@ -1,203 +0,0 @@
-odoo.define('pos_restaurant.splitbill', function (require) {
-"use strict";
-
-var gui = require('point_of_sale.gui');
-var models = require('point_of_sale.models');
-var screens = require('point_of_sale.screens');
-var core = require('web.core');
-
-var QWeb = core.qweb;
-
-var SplitbillScreenWidget = screens.ScreenWidget.extend({
- template: 'SplitbillScreenWidget',
-
- previous_screen: 'products',
-
- renderElement: function(){
- var self = this;
- var linewidget;
-
- this._super();
- var order = this.pos.get_order();
- if(!order){
- return;
- }
- var orderlines = order.get_orderlines();
- for(var i = 0; i < orderlines.length; i++){
- var line = orderlines[i];
- linewidget = $(QWeb.render('SplitOrderline',{
- widget:this,
- line:line,
- selected: false,
- quantity: 0,
- id: line.id,
- }));
- linewidget.data('id',line.id);
- this.$('.orderlines').append(linewidget);
- }
- this.$('.back').click(function(){
- self.gui.show_screen(self.previous_screen);
- });
- },
-
- split_quantity: function(split, line, splitlines) {
- if( !line.get_unit().is_pos_groupable ){
- if( split.quantity !== line.get_quantity()){
- split.quantity = line.get_quantity();
- }else{
- split.quantity = 0;
- }
- }else{
- if( split.quantity < line.get_quantity()){
- split.quantity += line.get_unit().is_pos_groupable ? 1 : line.get_unit().rounding;
- if(split.quantity > line.get_quantity()){
- split.quantity = line.get_quantity();
- }
- }else{
- split.quantity = 0;
- }
- }
- },
-
- set_line_on_order: function(neworder, split, line) {
- if( split.quantity ){
- if ( !split.line ){
- split.line = line.clone();
- neworder.add_orderline(split.line);
- }
- split.line.set_quantity(split.quantity, 'do not recompute unit price');
- }else if( split.line ) {
- neworder.remove_orderline(split.line);
- split.line = null;
- }
- },
-
- lineselect: function($el,order,neworder,splitlines,line_id){
- var split = splitlines[line_id] || {'quantity': 0, line: null};
- var line = order.get_orderline(line_id);
-
- this.split_quantity(split, line, null);
-
- this.set_line_on_order(neworder, split, line);
-
- splitlines[line_id] = split;
- $el.replaceWith($(QWeb.render('SplitOrderline',{
- widget: this,
- line: line,
- selected: split.quantity !== 0,
- quantity: split.quantity,
- id: line_id,
- })));
- this.$('.order-info .subtotal').text(this.format_currency(neworder.get_subtotal()));
- },
-
- check_full_pay_order: function (order, splitlines) {
- return _.every(order.get_orderlines(), function(orderLine) {
- var split = splitlines[orderLine.id];
- return split && split.quantity === orderLine.get_quantity();
- });
- },
-
- set_quantity_on_order: function(splitlines, order) {
- for(var id in splitlines){
- var split = splitlines[id];
- var line = order.get_orderline(parseInt(id));
- line.set_quantity(line.get_quantity() - split.quantity, 'do not recompute unit price');
- if(Math.abs(line.get_quantity()) < 0.00001){
- order.remove_orderline(line);
- }
- delete splitlines[id];
- }
- },
-
- pay: function(order,neworder,splitlines){
- if(_.isEmpty(splitlines)) // Splitlines is empty
- return;
-
- delete neworder.temporary;
-
- if(this.check_full_pay_order(order, splitlines)){
- this.gui.show_screen('payment');
- }else{
- this.set_quantity_on_order(splitlines, order);
-
- neworder.set_screen_data('screen','payment');
-
- // for the kitchen printer we assume that everything
- // has already been sent to the kitchen before splitting
- // the bill. So we save all changes both for the old
- // order and for the new one. This is not entirely correct
- // but avoids flooding the kitchen with unnecessary orders.
- // Not sure what to do in this case.
-
- if ( neworder.saveChanges ) {
- order.saveChanges();
- neworder.saveChanges();
- }
-
- neworder.set_customer_count(1);
- order.set_customer_count(order.get_customer_count() - 1);
- order.set_screen_data('screen','products');
-
- this.pos.get('orders').add(neworder);
- this.pos.set('selectedOrder',neworder);
- }
- },
- show: function(){
- var self = this;
- this._super();
- this.renderElement();
-
- var order = this.pos.get_order();
- var neworder = new models.Order({},{
- pos: this.pos,
- temporary: true,
- });
- neworder.set('client',order.get('client'));
-
- var splitlines = {};
-
- this.$('.orderlines').on('click','.orderline',function(){
- var id = parseInt($(this).data('id'));
- var $el = $(this);
- self.lineselect($el,order,neworder,splitlines,id);
- });
-
- this.$('.paymentmethods .button').click(function(){
- self.pay(order,neworder,splitlines);
- });
- },
-});
-
-gui.define_screen({
- 'name': 'splitbill',
- 'widget': SplitbillScreenWidget,
- 'condition': function(){
- return this.pos.config.iface_splitbill;
- },
-});
-
-var SplitbillButton = screens.ActionButtonWidget.extend({
- template: 'SplitbillButton',
- button_click: function(){
- if(this.pos.get_order().get_orderlines().length > 0){
- this.gui.show_screen('splitbill');
- }
- },
-});
-
-screens.define_action_button({
- 'name': 'splitbill',
- 'widget': SplitbillButton,
- 'condition': function(){
- return this.pos.config.iface_splitbill;
- },
-});
-
-return {
- SplitbillButton: SplitbillButton,
- SplitbillScreenWidget: SplitbillScreenWidget,
-}
-
-});
-
diff --git a/addons/pos_restaurant/static/src/js/tours/pos_restaurant.js b/addons/pos_restaurant/static/src/js/tours/pos_restaurant.js
deleted file mode 100644
index 9e112e3d34c..00000000000
--- a/addons/pos_restaurant/static/src/js/tours/pos_restaurant.js
+++ /dev/null
@@ -1,240 +0,0 @@
-odoo.define('pos_reataurant.tour.synchronized_table_management', function (require) {
- "use strict";
-
- var Tour = require("web_tour.tour");
-
- function verify_order_total(total_str) {
- return [{
- content: 'order total contains ' + total_str,
- trigger: '.order .total .value:contains("' + total_str + '")',
- run: function () {}, // it's a check
- }];
- }
-
- function verify_orders_synced(order_count) {
- return [{
- content: "check synced",
- trigger: ".order-sequence",
- run: function() {
- var orders = $('.order-sequence');
- if (orders.length === order_count) {
- return
- } else {
- throw "sync failed";
- }
- },
- }];
- }
-
- function add_product_to_order(product_name) {
- return [{
- content: 'buy ' + product_name,
- trigger: '.product-list .product-name:contains("' + product_name + '")',
- }, {
- content: 'the ' + product_name + ' have been added to the order',
- trigger: '.order .product-name:contains("' + product_name + '")',
- run: function () {}, // it's a check
- }];
- }
-
- function generate_keypad_steps(amount_str, keypad_selector) {
- var i, steps = [], current_char;
- for (i = 0; i < amount_str.length; ++i) {
- current_char = amount_str[i];
- steps.push({
- content: 'press ' + current_char + ' on payment keypad',
- trigger: keypad_selector + ' .input-button:contains("' + current_char + '"):visible'
- });
- }
-
- return steps;
- }
-
- function generate_payment_screen_keypad_steps(amount_str) {
- return generate_keypad_steps(amount_str, '.payment-numpad');
- }
-
- function generate_product_screen_keypad_steps(amount_str) {
- return generate_keypad_steps(amount_str, '.numpad');
- }
-
- function goto_payment_screen_and_select_payment_method() {
- return [{
- content: "go to payment screen",
- trigger: '.button.pay',
- }, {
- content: "pay with cash",
- trigger: '.paymentmethod:contains("Cash")',
- }];
- }
-
- function open_table(table_id, order_count) {
- order_count = order_count || null;
- var steps = [{
- content: 'open table ' + table_id,
- trigger: '.label:contains(' + table_id +')',
- run: 'click',
- }];
- if (order_count !== null){
- steps = steps.concat(verify_orders_synced(order_count));
- }
- return steps;
- }
-
- function transfer_order_to_table(table_id, order_uid) {
- return [{
- content: 'Click transfer button',
- trigger: '.control-button:contains("Transfer")',
- run: 'click',
- }, {
- content: 'Transfer order to table ' + table_id,
- trigger: '.label:contains(' + table_id +')',
- run: 'click',
- }, {
- content: 'Check if order ' + order_uid + ' is open after transfer',
- trigger: '.order-button.selected .order-sequence:contains("' + order_uid + '")',
- run: function(){} // Check
- }];
- }
-
- function finish_order() {
- var steps = [{
- content: "validate the order",
- trigger: '.button.next:visible',
- }];
- steps = steps.concat([{
- content: "next order",
- trigger: '.button.next:visible',
- }]);
- return steps;
- }
-
- /* pos_restaurant_sync
- *
- * Run on new session.
- */
- var steps = [{
- content: 'waiting for loading to finish',
- trigger: 'body:has(.loader:hidden)',
- run: function () {},
- }]
-
-
- steps = steps.concat(open_table('T5'));
-
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat(add_product_to_order('Water'));
- steps = steps.concat(verify_order_total('4.40'));
- steps = steps.concat([{
- content: 'start new order',
- trigger: '.neworder-button',
- run: 'click',
- }]);
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat(add_product_to_order('Minute Maid'));
- steps = steps.concat(verify_order_total('4.40'));
- steps = steps.concat(goto_payment_screen_and_select_payment_method());
- steps = steps.concat(generate_payment_screen_keypad_steps('6.05'));
- steps = steps.concat(finish_order());
- steps = steps.concat(open_table('T5', 1));
- steps = steps.concat(verify_order_total('4.40'));
- steps = steps.concat([{
- content: 'start new order',
- trigger: '.neworder-button',
- run: 'click',
- }]);
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat(add_product_to_order('Minute Maid'));
- steps = steps.concat([{
- content: 'back to floor',
- trigger: '.floor-button',
- run: 'click',
- }]);
- steps = steps.concat(open_table('T5', 2));
- steps = steps.concat([{
- content: 'delete order',
- trigger: '.deleteorder-button',
- run: 'click',
- }, {
- content: 'confirm delete',
- trigger: '.button.confirm',
- run: 'click',
- }, {
- content: 'back to floor',
- trigger: '.floor-button',
- run: 'click',
- }]);
- steps = steps.concat(open_table('T5', 1));
-
- Tour.register('pos_restaurant_sync', { test: true, url: '/pos/web' }, steps);
-
-
- /* pos_restaurant_sync_second_login
- *
- * This tour should be run after the first tour is done.
- */
- var steps = [{
- content: 'waiting for loading to finish',
- trigger: 'body:has(.loader:hidden)',
- run: function () {},
- }];
- steps = steps.concat(open_table('T5', 1));
- steps = steps.concat(verify_order_total('4.40'));
-
- // Test transfering an order
- steps = steps.concat(transfer_order_to_table('T4', '002-0001'));
-
- // Test if products still get merged after transfering the order
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat({
- content: 'check the order-line for Coca-Cola has 2 Units',
- trigger: '.orderlines:has(.orderline .product-name:contains("Coca-Cola")) .info-list:contains("2.000")',
- run: function () {},
- })
- steps = steps.concat(generate_product_screen_keypad_steps('1'));
-
- steps = steps.concat(goto_payment_screen_and_select_payment_method());
- steps = steps.concat(generate_payment_screen_keypad_steps('4.4'));
- steps = steps.concat(finish_order());
- steps = steps.concat(open_table('T2'));
-
- // Test transfering an empty order
- steps = steps.concat(transfer_order_to_table('T4', '2'));
-
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat(verify_order_total('2.20'));
-
- // Take a synced order with products, remove the products
- // and check if the order is still available in the front-end
- steps = steps.concat([{
- content: 'back to floor',
- trigger: '.floor-button',
- run: 'click',
- }]);
- steps = steps.concat(open_table('T4', 1));
- steps = steps.concat([{
- content: 'click backspace to set quantity to 0',
- trigger: '.numpad-backspace',
- run: 'click',
- }, {
- content: 'click backspace to remove line',
- trigger: '.numpad-backspace',
- run: 'click',
- }]);
- steps = steps.concat([{
- content: 'back to floor',
- trigger: '.floor-button',
- run: 'click',
- }]);
- steps = steps.concat(open_table('T4', 1));
- steps = steps.concat(add_product_to_order('Coca-Cola'));
- steps = steps.concat(verify_order_total('2.20'));
- steps = steps.concat([{
- content: 'back to floor',
- trigger: '.floor-button',
- run: 'click',
- }]);
-
- Tour.register('pos_restaurant_sync_second_login', { test: true, url: '/pos/web' }, steps);
-
-});
diff --git a/addons/pos_restaurant/static/src/xml/Chrome.xml b/addons/pos_restaurant/static/src/xml/Chrome.xml
new file mode 100644
index 00000000000..ee29abe2e36
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Chrome.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="Chrome" t-inherit="point_of_sale.Chrome" t-inherit-mode="extension" owl="1">
+ <xpath expr="//OrderSelector" position="attributes">
+ <attribute name="t-if">mainScreen.name !== 'FloorScreen'</attribute>
+ </xpath>
+ <xpath expr="//OrderSelector" position="after">
+ <div t-else="" class="order-selector"></div>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml b/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml
new file mode 100644
index 00000000000..1f36d2763f3
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/ChromeWidgets/BackToFloorButton.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="BackToFloorButton" owl="1">
+ <span class="order-button floor-button">
+ <i class="fa fa-angle-double-left" role="img" aria-label="Back to floor" title="Back to floor" />
+ <span> </span>
+ <span t-esc="floor.name" />
+ <span> </span>
+ <span class="table-name">
+ <span>( </span>
+ <span t-esc="table.name" />
+ <span> )</span>
+ </span>
+ </span>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/ChromeWidgets/OrderSelector.xml b/addons/pos_restaurant/static/src/xml/ChromeWidgets/OrderSelector.xml
new file mode 100644
index 00000000000..824ff26bcee
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/ChromeWidgets/OrderSelector.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="OrderSelector" t-inherit="point_of_sale.OrderSelector" t-inherit-mode="extension" owl="1">
+ <xpath expr="//div[hasclass('order-selector')]//span[hasclass('orders')]//t" position="before">
+ <BackToFloorButton t-if="backToFloorButtonIsShown" table="currentTable" />
+ </xpath>
+ <xpath expr="//span[hasclass('order-sequence')]" position="replace">
+ <span class="order-sequence">
+ <t t-if="order.server_id and env.pos.pos_session.login_number != order.uid.split('-')[1]">
+ <t t-esc="order.uid.substring(order.uid.indexOf('-') +1)" />
+ </t>
+ <t t-else="">
+ <t t-esc="order.sequence_number" />
+ </t>
+ </span>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Resizeable.xml b/addons/pos_restaurant/static/src/xml/Resizeable.xml
new file mode 100644
index 00000000000..169c888e415
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Resizeable.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="Resizeable" owl="1">
+ <t t-slot="default"></t>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml
new file mode 100644
index 00000000000..0c904ece232
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/BillScreen.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="BillScreen" owl="1">
+ <div class="receipt-screen screen">
+ <div class="screen-content">
+ <div class="top-content">
+ <span class="button back" t-on-click="confirm">
+ <i class="fa fa-angle-double-left"></i>
+ <span> </span>
+ <span>Back</span>
+ </span>
+ <h1>Bill Printing</h1>
+ <span class="button next highlight" t-on-click="confirm">
+ <span>Ok</span>
+ <span> </span>
+ <i class="fa fa-angle-double-right"></i>
+ </span>
+ </div>
+ <div class="centered-content">
+ <div class="button print" t-on-click="printReceipt">
+ <i class="fa fa-print"></i>
+ <span> </span>
+ <span>Print</span>
+ </div>
+ <div class="pos-receipt-container">
+ <OrderReceipt order="currentOrder" isBill="true"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditBar.xml b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditBar.xml
new file mode 100644
index 00000000000..00e96d951b5
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditBar.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="EditBar" owl="1">
+ <div class="edit-bar">
+ <span class="edit-button" t-on-click.stop="trigger('create-table')">
+ <i class="fa fa-plus" role="img" aria-label="Add" title="Add"></i>
+ </span>
+ <span class="edit-button" t-att-class="{ disabled: !props.selectedTable }" t-on-click.stop="trigger('duplicate-table')">
+ <i class="fa fa-files-o" role="img" aria-label="Duplicate" title="Duplicate"></i>
+ </span>
+ <span class="edit-button" t-att-class="{ disabled: !props.selectedTable }" t-on-click.stop="trigger('rename-table')">
+ <i class="fa fa-font" role="img" aria-label="Rename" title="Rename"></i>
+ </span>
+ <span class="edit-button" t-att-class="{ disabled: !props.selectedTable }" t-on-click.stop="trigger('change-seats-num')">
+ <i class="fa fa-user" role="img" aria-label="Seats" title="Seats"></i>
+ </span>
+ <span class="edit-button" t-att-class="{ disabled: !props.selectedTable }" t-on-click.stop="trigger('change-shape')">
+ <span t-if="!props.selectedTable or props.selectedTable.shape == 'square'" class="button-option square">
+ <i class="fa fa-square-o" role="img" aria-label="Square Shape" title="Square Shape"></i>
+ </span>
+ <span t-else="" class="button-option round">
+ <i class="fa fa-circle-o" role="img" aria-label="Round Shape" title="Round Shape"></i>
+ </span>
+ </span>
+ <span class="edit-button" t-on-click.stop="state.isColorPicker = !state.isColorPicker">
+ <i class="fa fa-tint" role="img" aria-label="Tint" title="Tint"></i>
+ </span>
+ <div t-if="state.isColorPicker and props.selectedTable" class="color-picker fg-picker">
+ <div class="close-picker" title="Close" role="img" aria-label="Close" t-on-click.stop="state.isColorPicker = false">
+ <i class="fa fa-times" />
+ </div>
+ <span class="color tl" style="background-color:#EB6D6D" role="img" aria-label="Red" title="Red" t-on-click.stop="trigger('set-table-color', '#EB6D6D')" />
+ <span class="color" style="background-color:#35D374" role="img" aria-label="Green" title="Green" t-on-click.stop="trigger('set-table-color', '#35D374')" />
+ <span class="color tr" style="background-color:#6C6DEC" role="img" aria-label="Blue" title="Blue" t-on-click.stop="trigger('set-table-color', '#6C6DEC')" />
+ <span class="color" style="background-color:#EBBF6D" role="img" aria-label="Orange" title="Orange" t-on-click.stop="trigger('set-table-color', '#EBBF6D')" />
+ <span class="color" style="background-color:#EBEC6D" role="img" aria-label="Yellow" title="Yellow" t-on-click.stop="trigger('set-table-color', '#EBEC6D')" />
+ <span class="color" style="background-color:#AC6DAD" role="img" aria-label="Purple" title="Purple" t-on-click.stop="trigger('set-table-color', '#AC6DAD')" />
+ <span class="color bl" style="background-color:#6C6D6D" role="img" aria-label="Grey" title="Grey" t-on-click.stop="trigger('set-table-color', '#6C6D6D')" />
+ <span class="color" style="background-color:#ACADAD" role="img" aria-label="Light grey" title="Light grey" t-on-click.stop="trigger('set-table-color', '#ACADAD')" />
+ <span class="color br" style="background-color:#4ED2BE" role="img" aria-label="Turquoise" title="Turquoise" t-on-click.stop="trigger('set-table-color', '#4ED2BE')" />
+ </div>
+ <div t-if="state.isColorPicker and !props.selectedTable" class="color-picker bg-picker">
+ <div class="close-picker" title="Close" role="img" aria-label="Close" t-on-click.stop="state.isColorPicker = false">
+ <i class="fa fa-times" />
+ </div>
+ <span class="color tl" style="background-color:rgb(244, 149, 149)" role="img" aria-label="Red" title="Red" t-on-click.stop="trigger('set-floor-color', 'rgb(244, 149, 149)')" />
+ <span class="color" style="background-color:rgb(130, 233, 171)" role="img" aria-label="Green" title="Green" t-on-click.stop="trigger('set-floor-color', 'rgb(130, 233, 171)')" />
+ <span class="color tr" style="background-color:rgb(136, 137, 242)" role="img" aria-label="Blue" title="Blue" t-on-click.stop="trigger('set-floor-color', 'rgb(136, 137, 242)')" />
+ <span class="color" style="background-color:rgb(255, 214, 136)" role="img" aria-label="Orange" title="Orange" t-on-click.stop="trigger('set-floor-color', 'rgb(255, 214, 136)')" />
+ <span class="color" style="background-color:rgb(254, 255, 154)" role="img" aria-label="Yellow" title="Yellow" t-on-click.stop="trigger('set-floor-color', 'rgb(254, 255, 154)')" />
+ <span class="color" style="background-color:rgb(209, 171, 210)" role="img" aria-label="Purple" title="Purple" t-on-click.stop="trigger('set-floor-color', 'rgb(209, 171, 210)')" />
+ <span class="color bl" style="background-color:rgb(75, 75, 75)" role="img" aria-label="Grey" title="Grey" t-on-click.stop="trigger('set-floor-color', 'rgb(75, 75, 75)')" />
+ <span class="color" style="background-color:rgb(210, 210, 210)" role="img" aria-label="Light grey" title="Light grey" t-on-click.stop="trigger('set-floor-color', 'rgb(210, 210, 210)')" />
+ <span class="color br" style="background-color:rgb(127, 221, 236)" role="img" aria-label="Turquoise" title="Turquoise" t-on-click.stop="trigger('set-floor-color', 'rgb(127, 221, 236)')" />
+ </div>
+ <span class="edit-button trash" t-att-class="{ disabled: !props.selectedTable }" t-on-click.stop="trigger('delete-table')">
+ <i class="fa fa-trash" role="img" aria-label="Delete" title="Delete"></i>
+ </span>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditableTable.xml b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditableTable.xml
new file mode 100644
index 00000000000..9c3a6079a1e
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/EditableTable.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="EditableTable" owl="1">
+ <Draggable limitArea="'.floor-map'">
+ <Resizeable limitArea="'.floor-map'">
+ <div class="table selected" t-on-click.stop="">
+ <span class="label drag-handle">
+ <t t-esc="props.table.name" />
+ </span>
+ <span class="table-seats">
+ <t t-esc="props.table.seats" />
+ </span>
+ <t t-if="props.table.shape === 'round'">
+ <div class="table-handle top resize-handle-n"></div>
+ <div class="table-handle bottom resize-handle-s"></div>
+ <div class="table-handle left resize-handle-w"></div>
+ <div class="table-handle right resize-handle-e"></div>
+ </t>
+ <t t-if="props.table.shape === 'square'">
+ <span class='table-handle top right resize-handle-ne'></span>
+ <span class='table-handle top left resize-handle-nw'></span>
+ <span class='table-handle bottom right resize-handle-se'></span>
+ <span class='table-handle bottom left resize-handle-sw'></span>
+ </t>
+ </div>
+ </Resizeable>
+ </Draggable>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/FloorScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/FloorScreen.xml
new file mode 100644
index 00000000000..0f3dc40680a
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/FloorScreen.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="FloorScreen" owl="1">
+ <div class="floor-screen screen">
+ <div class="screen-content-flexbox">
+ <t t-if="env.pos.floors.length > 1">
+ <div class="floor-selector">
+ <t t-foreach="env.pos.floors" t-as="floor" t-key="floor.id">
+ <span class="button button-floor" t-att-class="{ active: floor.id === state.selectedFloorId }" t-on-click="selectFloor(floor)">
+ <t t-esc="floor.name" />
+ </span>
+ </t>
+ </div>
+ </t>
+ <div class="floor-map" t-on-click="trigger('deselect-table')" t-ref="floor-map-ref">
+ <div t-if="isFloorEmpty" class="empty-floor">
+ <span>This floor has no tables yet, use the </span>
+ <i class="fa fa-plus" role="img" aria-label="Add button" title="Add button"></i>
+ <span> button in the editing toolbar to create new tables.</span>
+ </div>
+ <div t-else="" class="tables">
+ <t t-foreach="activeTables" t-as="table" t-key="table.id">
+ <TableWidget t-if="table.id !== state.selectedTableId" table="table" />
+ <EditableTable t-else="" table="table" />
+ </t>
+ </div>
+ <span t-if="env.pos.user.role == 'manager'" class="edit-button editing" t-att-class="{ active: state.isEditMode }" t-on-click.stop="toggleEditMode">
+ <i class="fa fa-pencil" role="img" aria-label="Edit" title="Edit"></i>
+ </span>
+ <EditBar t-if="state.isEditMode" selectedTable="selectedTable" />
+ </div>
+ </div>
+ </div>
+ </t>
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/TableWidget.xml b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/TableWidget.xml
new file mode 100644
index 00000000000..32b9bc94047
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/FloorScreen/TableWidget.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="TableWidget" owl="1">
+ <div t-if="!props.isSelected" class="table" t-on-click.stop="trigger('select-table', props.table)">
+ <span class="table-cover" t-att-class="{ full: fill >= 1 }"></span>
+ <span t-if="orderCount" t-att-class="orderCountClass">
+ <t t-esc="orderCount" />
+ </span>
+ <span class="label">
+ <t t-esc="props.table.name" />
+ </span>
+ <span class="table-seats">
+ <t t-esc="props.table.seats" />
+ </span>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.xml
new file mode 100644
index 00000000000..407103a5ab0
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/OrderlineNoteButton.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="OrderlineNoteButton" owl="1">
+ <div class="control-button">
+ <i class="fa fa-tag" />
+ <span> </span>
+ <span>Note</span>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/PrintBillButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/PrintBillButton.xml
new file mode 100644
index 00000000000..12a4e397edd
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/PrintBillButton.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="PrintBillButton" owl="1">
+ <span class="control-button order-printbill">
+ <i class="fa fa-print"></i>
+ <span> </span>
+ <span>Bill</span>
+ </span>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SplitBillButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SplitBillButton.xml
new file mode 100644
index 00000000000..e8efb306466
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SplitBillButton.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SplitBillButton" owl="1">
+ <span class="control-button order-split">
+ <i class="fa fa-files-o"></i>
+ <span> </span>
+ <span>Split</span>
+ </span>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SubmitOrderButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SubmitOrderButton.xml
new file mode 100644
index 00000000000..e0229af81fc
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/SubmitOrderButton.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SubmitOrderButton" owl="1">
+ <span class="control-button" t-att-class="addedClasses">
+ <i class="fa fa-cutlery"></i>
+ <span> </span>
+ <span>Order</span>
+ </span>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml
new file mode 100644
index 00000000000..5a33b17ec32
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="TableGuestsButton" owl="1">
+ <div class="control-button">
+ <span class="control-button-number">
+ <t t-esc="nGuests" />
+ </span>
+ <span> </span>
+ <span>Guests</span>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TransferOrderButton.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TransferOrderButton.xml
new file mode 100644
index 00000000000..6eb0c975573
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TransferOrderButton.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="TransferOrderButton" owl="1">
+ <div class="control-button">
+ <i class="fa fa-arrow-right" />
+ <span> </span>
+ <span>Transfer</span>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/Orderline.xml b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/Orderline.xml
new file mode 100644
index 00000000000..a5c96dedad9
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/Orderline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="Orderline" t-inherit="point_of_sale.Orderline" t-inherit-mode="extension" owl="1">
+ <xpath expr="//ul[hasclass('info-list')]" position="inside">
+ <t t-if="props.line.get_note()">
+ <li class="info orderline-note">
+ <i class="fa fa-tag" role="img" aria-label="Note" title="Note"/>
+ <t t-esc="props.line.get_note()" />
+ </li>
+ </t>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml b/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml
new file mode 100644
index 00000000000..8d195f6cca2
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml
@@ -0,0 +1,15 @@
+<?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" owl="1">
+ <xpath expr="//div[hasclass('pos-receipt-order-data')]" position="inside">
+ <t t-if="props.isBill">
+ <div>PRO FORMA</div>
+ </t>
+ </xpath>
+ <xpath expr="//div[hasclass('receipt-change')]" position="attributes">
+ <attribute name="t-if">!props.isBill</attribute>
+ </xpath>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitBillScreen.xml b/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitBillScreen.xml
new file mode 100644
index 00000000000..f09ecdcdc1d
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitBillScreen.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SplitBillScreen" owl="1">
+ <div class="splitbill-screen screen">
+ <div class="screen-content">
+ <div class="top-content">
+ <span class="button back" t-on-click="back">
+ <i class="fa fa-angle-double-left"></i>
+ <span> </span>
+ <span>Back</span>
+ </span>
+ <h1>Bill Splitting</h1>
+ </div>
+ <div class="left-content touch-scrollable scrollable-y">
+ <div class="order">
+ <ul class="orderlines">
+ <t t-foreach="orderlines" t-as="line" t-key="line.cid">
+ <SplitOrderline line="line" split="splitlines[line.id]" />
+ </t>
+ </ul>
+ </div>
+ </div>
+ <div class="right-content touch-scrollable scrollable-y">
+ <div class="order-info">
+ <span class="subtotal">
+ <t t-esc="env.pos.format_currency(newOrder.get_subtotal())" />
+ </span>
+ </div>
+ <div class="paymentmethods">
+ <div class="button payment" t-on-click="proceed">
+ <i class="fa fa-chevron-right" />
+ <span> </span>
+ <span>Payment</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitOrderline.xml b/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitOrderline.xml
new file mode 100644
index 00000000000..fd71374fa98
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/Screens/SplitBillScreen/SplitOrderline.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SplitOrderline" owl="1">
+ <li class="orderline" t-att-class="{ selected: isSelected, partially: props.split.quantity !== props.line.get_quantity() }">
+ <span class="product-name">
+ <t t-esc="props.line.get_product().display_name" />
+ </span>
+ <span class="price">
+ <t t-esc="env.pos.format_currency(props.line.get_display_price())" />
+ </span>
+ <ul class="info-list">
+ <t t-if="props.line.get_quantity_str() !== '1'">
+ <li class="info">
+ <t t-if="isSelected and props.line.get_unit().is_pos_groupable">
+ <em class="big">
+ <t t-esc="props.split.quantity" />
+ </em>
+ /
+ <t t-esc="props.line.get_quantity_str()" />
+ </t>
+ <t t-if="!(isSelected and props.line.get_unit().is_pos_groupable)">
+ <em>
+ <t t-esc="props.line.get_quantity_str()" />
+ </em>
+ </t>
+ <t t-esc="props.line.get_unit().name" />
+ at
+ <t t-esc="env.pos.format_currency(props.line.get_unit_price())" />
+ /
+ <t t-esc="props.line.get_unit().name" />
+ </li>
+ </t>
+ <t t-if="props.line.get_discount_str() !== '0'">
+ <li class="info">
+ <span>With a </span>
+ <em>
+ <t t-esc="props.line.get_discount_str()" />
+ <span>%</span>
+ </em>
+ <span> discount</span>
+ </li>
+ </t>
+ </ul>
+ </li>
+ </t>
+
+</templates>
diff --git a/addons/pos_restaurant/static/src/xml/floors.xml b/addons/pos_restaurant/static/src/xml/floors.xml
deleted file mode 100644
index b3019a915d4..00000000000
--- a/addons/pos_restaurant/static/src/xml/floors.xml
+++ /dev/null
@@ -1,181 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-extend="OrderReceipt">
- <t t-jquery='.cashier' t-operation='append'>
- <t t-if='receipt.table'>
- at table <t t-esc='receipt.table' />
- </t>
- <t t-if='receipt.table &amp;&amp; receipt.customer_count'>
- <div>Guests: <t t-esc='receipt.customer_count' /></div>
- </t>
- </t>
- </t>
-
- <t t-extend="BillReceipt">
- <t t-jquery='.cashier' t-operation='append'>
- <t t-if='receipt.table'>
- at table <t t-esc='receipt.table' />
- </t>
- <t t-if='receipt.customer_count'>
- <div>Guests: <t t-esc='receipt.customer_count' /></div>
- </t>
- </t>
- </t>
-
- <t t-extend="OrderSelectorWidget">
- <t t-jquery=".order-sequence" t-operation="replace">
- <span class="order-sequence">
- <t t-if="order.server_id &amp;&amp; widget.pos.pos_session.login_number != order.uid.split('-')[1]">
- <t t-esc="order.uid.substring(order.uid.indexOf('-') +1)"/>
- </t>
- <t t-else="">
- <t t-esc="order.sequence_number"/>
- </t>
- </span>
- </t>
- </t>
-
- <t t-name="TableGuestsButton">
- <div class='control-button'>
- <span class='control-button-number'>
- <t t-esc="widget.guests()" />
- </span>
- Guests
- </div>
- </t>
-
- <t t-name="TransferOrderButton">
- <div class='control-button'>
- <i class='fa fa-arrow-right' /> Transfer
- </div>
- </t>
-
- <t t-name="TableWidget">
- <t t-if='!widget.selected'>
- <div class='table' t-att-style='widget.table_style_str()'>
- <span
- t-if="widget.table.shape"
- t-att-class='"table-cover " + (widget.fill >= 1 ? "full" : "")'
- t-att-style='"height: " + Math.ceil(widget.fill * 100) + "%;"'
- ></span>
- <t t-if='widget.order_count'>
- <span t-att-class='"order-count " + (widget.notifications.printing ? "notify-printing":"") + (widget.notifications.skipped ? "notify-skipped" : "")'><t t-esc='widget.order_count'/></span>
- </t>
- <span class='label'>
- <t t-esc='widget.table.name' />
- </span>
- <span class="table-seats"><t t-esc="widget.table.seats" /></span>
- </div>
- </t>
- <t t-if='widget.selected'>
- <div class='table selected' t-att-style='widget.table_style_str()'>
- <span class='label'>
- <t t-esc='widget.table.name' />
- </span>
- <span class="table-seats"><t t-esc="widget.table.seats" /></span>
- <t t-if="widget.table.shape === 'round'">
- <span class='table-handle top ui-resizable-n'></span>
- <span class='table-handle bottom ui-resizable-s'></span>
- <span class='table-handle left ui-resizable-w'></span>
- <span class='table-handle right ui-resizable-e'></span>
- </t>
- <t t-if="widget.table.shape === 'square'">
- <span class='table-handle top right ui-resizable-ne'></span>
- <span class='table-handle top left ui-resizable-nw'></span>
- <span class='table-handle bottom right ui-resizable-se'></span>
- <span class='table-handle bottom left ui-resizable-sw'></span>
- </t>
- </div>
- </t>
- </t>
-
- <t t-name="BackToFloorButton">
- <span class="order-button floor-button">
- <i class='fa fa-angle-double-left' role="img" aria-label="Back to floor" title="Back to floor"/>
- <t t-esc="floor.name"/>
- <span class='table-name'>
- ( <t t-esc="table.name" /> )
- </span>
- </span>
- </t>
-
- <t t-name="FloorScreenWidget">
- <div class='floor-screen screen'>
- <div class='screen-content-flexbox'>
- <t t-if='widget.pos.floors.length > 1'>
- <div class='floor-selector'>
- <t t-foreach="widget.pos.floors" t-as="floor">
- <t t-if="floor.id === widget.floor.id">
- <span class='button button-floor active' t-att-data-id="floor.id"><t t-esc="floor.name" /></span>
- </t>
- <t t-if="floor.id !== widget.floor.id">
- <span class='button button-floor' t-att-data-id="floor.id"><t t-esc="floor.name" /></span>
- </t>
- </t>
- </div>
- </t>
- <div class='floor-map' t-att-style='widget.get_floor_style()' >
- <div class='empty-floor oe_hidden'>
- This floor has no tables yet, use the <i class="fa fa-plus" role="img" aria-label="Add button" title="Add button"></i> button in the editing toolbar to create new tables.
- </div>
- <div class='tables'></div>
- <span t-if="widget.pos.user.role == 'manager'" class='edit-button editing'><i class='fa fa-pencil' role="img" aria-label="Edit" title="Edit"></i></span>
- <div class='edit-bar oe_hidden'>
- <span class='edit-button new-table'>
- <i class='fa fa-plus' role="img" aria-label="Add" title="Add"></i>
- </span>
- <span class='edit-button dup-table needs-selection'>
- <i class='fa fa-files-o' role="img" aria-label="Duplicate" title="Duplicate"></i>
- </span>
- <span class='edit-button rename needs-selection'>
- <i class='fa fa-font' role="img" aria-label="Rename" title="Rename"></i>
- </span>
- <span class='edit-button seats needs-selection'>
- <i class='fa fa-user' role="img" aria-label="Seats" title="Seats"></i>
- </span>
- <span class='edit-button shape needs-selection'>
- <span class='button-option square'><i class='fa fa-square-o' role="img" aria-label="Square Shape" title="Square Shape"></i></span>
- <span class='button-option round oe_hidden'><i class='fa fa-circle-o' role="img" aria-label="Round Shape" title="Round Shape"></i></span>
- </span>
- <span class='edit-button color'>
- <i class='fa fa-tint' role="img" aria-label="Tint" title="Tint"></i>
- <div class='color-picker fg-picker oe_hidden'>
- <div class='close-picker' title="Close" role="img" aria-label="Close">
- <i class='fa fa-times' />
- </div>
- <span class='color tl' style='background-color:#EB6D6D' role="img" aria-label="Red" title="Red"/>
- <span class='color' style='background-color:#35D374' role="img" aria-label="Green" title="Green"/>
- <span class='color tr' style='background-color:#6C6DEC' role="img" aria-label="Blue" title="Blue"/>
- <span class='color' style='background-color:#EBBF6D' role="img" aria-label="Orange" title="Orange"/>
- <span class='color' style='background-color:#EBEC6D' role="img" aria-label="Yellow" title="Yellow"/>
- <span class='color' style='background-color:#AC6DAD' role="img" aria-label="Purple" title="Purple"/>
- <span class='color bl' style='background-color:#6C6D6D' role="img" aria-label="Grey" title="Grey"/>
- <span class='color' style='background-color:#ACADAD' role="img" aria-label="Light grey" title="Light grey"/>
- <span class='color br' style='background-color:#4ED2BE' role="img" aria-label="Turquoise" title="Turquoise"/>
- </div>
- <div class='color-picker bg-picker oe_hidden'>
- <div class='close-picker' title="Close" role="img" aria-label="Close">
- <i class='fa fa-times' />
- </div>
- <span class='color tl' style='background-color:rgb(244, 149, 149)' role="img" aria-label="Red" title="Red"/>
- <span class='color' style='background-color:rgb(130, 233, 171)' role="img" aria-label="Green" title="Green"/>
- <span class='color tr' style='background-color:rgb(136, 137, 242)' role="img" aria-label="Blue" title="Blue"/>
- <span class='color' style='background-color:rgb(255, 214, 136)' role="img" aria-label="Orange" title="Orange"/>
- <span class='color' style='background-color:rgb(254, 255, 154)' role="img" aria-label="Yellow" title="Yellow"/>
- <span class='color' style='background-color:rgb(209, 171, 210)' role="img" aria-label="Purple" title="Purple"/>
- <span class='color bl' style='background-color:rgb(75, 75, 75)' role="img" aria-label="Grey" title="Grey"/>
- <span class='color' style='background-color:rgb(210, 210, 210)' role="img" aria-label="Light grey" title="Light grey"/>
- <span class='color br' style='background-color:rgb(127, 221, 236)' role="img" aria-label="Turquoise" title="Turquoise"/>
- </div>
- </span>
- <span class='edit-button trash needs-selection'>
- <i class='fa fa-trash' role="img" aria-label="Delete" title="Delete"></i>
- </span>
- </div>
-
- </div>
- </div>
- </div>
- </t>
-</templates>
diff --git a/addons/pos_restaurant/static/src/xml/multiprint.xml b/addons/pos_restaurant/static/src/xml/multiprint.xml
index e43e63a765b..8a56c283c4c 100644
--- a/addons/pos_restaurant/static/src/xml/multiprint.xml
+++ b/addons/pos_restaurant/static/src/xml/multiprint.xml
@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
- <t t-name="SubmitOrderButton">
- <span class="control-button order-submit">
- <i class="fa fa-cutlery"></i>
- Order
- </span>
- </t>
-
<t t-name="NameWrapped">
<t t-foreach="change.name_wrapped.slice(1)" t-as="wrapped_line">
<div>
diff --git a/addons/pos_restaurant/static/src/xml/notes.xml b/addons/pos_restaurant/static/src/xml/notes.xml
deleted file mode 100644
index 63bd9d8a1c3..00000000000
--- a/addons/pos_restaurant/static/src/xml/notes.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-extend="Orderline">
- <t t-jquery=".info-list" t-operation="append">
- <t t-if="line.get_note()">
- <li class="info orderline-note">
- <i class='fa fa-tag' role="img" aria-label="Note" title="Note"/><t t-esc="line.get_note()" />
- </li>
- </t>
- </t>
- </t>
-
- <t t-name="OrderlineNoteButton">
- <div class='control-button'>
- <i class='fa fa-tag' /> Note
- </div>
- </t>
-
-</templates>
diff --git a/addons/pos_restaurant/static/src/xml/printbill.xml b/addons/pos_restaurant/static/src/xml/printbill.xml
deleted file mode 100644
index 47fe78fd44f..00000000000
--- a/addons/pos_restaurant/static/src/xml/printbill.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-name="BillScreenWidget">
- <div class='receipt-screen screen'>
- <div class='screen-content'>
- <div class='top-content'>
- <span class='button back'>
- <i class='fa fa-angle-double-left'></i>
- Back
- </span>
- <h1>Bill Printing</h1>
- <span class='button next'>
- Ok
- <i class='fa fa-angle-double-right'></i>
- </span>
- </div>
- <div class="centered-content">
- <div class="button print">
- <i class='fa fa-print'></i> Print
- </div>
- <div class="pos-receipt-container">
- </div>
- </div>
- </div>
- </div>
- </t>
-
- <t t-name="PrintBillButton">
- <span class="control-button order-printbill">
- <i class="fa fa-print"></i>
- Bill
- </span>
- </t>
-
- <t t-extend="OrderReceipt">
- <t t-jquery='.pos-receipt-order-data' t-operation='append'>
- <t t-if='receipt.bill === true'>
- <div>PRO FORMA</div>
- </t>
- </t>
- </t>
-
-</templates>
diff --git a/addons/pos_restaurant/static/src/xml/splitbill.xml b/addons/pos_restaurant/static/src/xml/splitbill.xml
deleted file mode 100644
index afca4071427..00000000000
--- a/addons/pos_restaurant/static/src/xml/splitbill.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-name="SplitbillButton">
- <span class="control-button order-split">
- <i class="fa fa-files-o"></i>
- Split
- </span>
- </t>
-
- <t t-name="SplitOrderline">
-
- <li t-attf-class="orderline #{ selected ? 'selected' : ''} #{ quantity !== line.get_quantity() ? 'partially' : '' }"
- t-att-data-id="id">
- <span class="product-name">
- <t t-esc="line.get_product().display_name"/>
- </span>
- <span class="price">
- <t t-esc="widget.format_currency(line.get_display_price())"/>
- </span>
- <ul class="info-list">
- <t t-if="line.get_quantity_str() !== '1'">
- <li class="info">
- <t t-if='selected and line.get_unit().is_pos_groupable'>
- <em class='big'>
- <t t-esc='quantity' />
- </em>
- /
- <t t-esc="line.get_quantity_str()" />
- </t>
- <t t-if='!(selected and line.get_unit().is_pos_groupable)'>
- <em>
- <t t-esc="line.get_quantity_str()" />
- </em>
- </t>
- <t t-esc="line.get_unit().name" />
- at
- <t t-esc="widget.format_currency(line.get_unit_price())" />
- /
- <t t-esc="line.get_unit().name" />
- </li>
- </t>
- <t t-if="line.get_discount_str() !== '0'">
- <li class="info">
- With a
- <em>
- <t t-esc="line.get_discount_str()" />%
- </em>
- discount
- </li>
- </t>
- </ul>
- </li>
- </t>
-
- <t t-name="SplitbillScreenWidget">
- <div class='splitbill-screen screen'>
- <div class='screen-content'>
- <div class='top-content'>
- <span class='button back'>
- <i class='fa fa-angle-double-left'></i>
- Back
- </span>
- <h1>Bill Splitting</h1>
- </div>
- <div class='left-content touch-scrollable scrollable-y'>
- <div class='order'>
- <ul class='orderlines'>
- </ul>
- </div>
- </div>
- <div class='right-content touch-scrollable scrollable-y'>
- <div class='order-info'>
- <span class='subtotal'><t t-esc='widget.format_currency(0.0)'/></span>
- </div>
- <div class='paymentmethods'>
- <div class='button payment'>
- <i class='fa fa-chevron-right' /> Payment
- </div>
- </div>
- </div>
- </div>
- </div>
- </t>
-
-</templates>