From b4339b49a0f22a072e9052a45ff4bb23af5fa7af Mon Sep 17 00:00:00 2001 From: "David Monnom (moda)" Date: Tue, 13 Jun 2023 13:14:33 +0000 Subject: [PATCH] [IMP] pos_restaurant: online table booking Adds the possibility to book a table online from the website. The booking is then visible in the POS. When a new table is created in the PoS, a linked resource is created in the appointment application, which allows the user to add these resources to appointment_types, authorising visitors to reserve tables online. In pos_config, you need to choose an appointment_type via which the appointments will be retrieved A websocket has been integrated to retrieve new bookings instantly from the PoS, which are displayed directly on the tables. closes odoo/odoo#124850 Related: odoo/enterprise#42489 Signed-off-by: Joseph Caburnay (jcb) --- addons/point_of_sale/models/pos_session.py | 16 ++- .../static/src/app/bus/pos_bus_service.js | 3 +- addons/pos_restaurant/models/pos_config.py | 1 + addons/pos_restaurant/models/pos_order.py | 7 +- .../pos_restaurant/models/pos_restaurant.py | 20 +--- .../models/res_config_settings.py | 1 + .../src/app/floor_screen/editable_table.xml | 3 - .../src/app/floor_screen/floor_screen.js | 39 +++++-- .../src/app/floor_screen/floor_screen.scss | 81 +++++++------- .../static/src/app/floor_screen/table.js | 105 ++++++++++++------ .../static/src/app/floor_screen/table.xml | 20 ++-- .../static/src/app/navbar/navbar.js | 4 +- .../src/app/product_screen/order/order.js | 44 ++++++++ .../src/app/product_screen/order/order.xml | 8 ++ .../ControlButtons/TableGuestsButton.js | 8 +- .../static/src/js/Screens/TicketScreen.js | 16 ++- .../ControlButtons/TableGuestsButton.xml | 2 +- .../tours/helpers/FloorScreenTourMethods.js | 12 +- .../views/res_config_settings_views.xml | 8 ++ 19 files changed, 262 insertions(+), 136 deletions(-) create mode 100644 addons/pos_restaurant/static/src/app/product_screen/order/order.js create mode 100644 addons/pos_restaurant/static/src/app/product_screen/order/order.xml diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index e20cf1285cc..6db8ed7a5b9 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - +import secrets from collections import defaultdict from datetime import timedelta from itertools import groupby @@ -33,6 +33,7 @@ class PosSession(models.Model): required=True, index=True) name = fields.Char(string='Session ID', required=True, readonly=True, default='/') + access_token = fields.Char('Security Token', copy=False) user_id = fields.Many2one( 'res.users', string='Opened By', required=True, @@ -1757,13 +1758,24 @@ class PosSession(models.Model): return taxes + def _ensure_access_token(self): + # Code taken from addons/portal/models/portal_mixin.py + if not self.access_token: + # we use a `write` to force the cache clearing otherwise `return self.access_token` will return False + self.sudo().write({'access_token': secrets.token_hex(16)}) + return self.access_token + + def _get_bus_channel_name(self): + return f'pos_session-{self.id}-{self._ensure_access_token()}' + def _loader_params_pos_session(self): + self._ensure_access_token() return { 'search_params': { 'domain': [('id', '=', self.id)], 'fields': [ 'id', 'name', 'user_id', 'config_id', 'start_at', 'stop_at', 'sequence_number', - 'payment_method_ids', 'state', 'update_stock_at_closing', 'cash_register_balance_start' + 'payment_method_ids', 'state', 'update_stock_at_closing', 'cash_register_balance_start', 'access_token' ], }, } diff --git a/addons/point_of_sale/static/src/app/bus/pos_bus_service.js b/addons/point_of_sale/static/src/app/bus/pos_bus_service.js index 0f193dea67a..96117de305f 100644 --- a/addons/point_of_sale/static/src/app/bus/pos_bus_service.js +++ b/addons/point_of_sale/static/src/app/bus/pos_bus_service.js @@ -13,8 +13,7 @@ export class PosBus { this.pos = pos; this.orm = orm; - // initialize bus_service and listen on pos_config-`id` channel - bus_service.addChannel(`pos_config-${pos.config.id}`); + bus_service.addChannel(`pos_session-${pos.pos_session.id}-${pos.pos_session.access_token}`); bus_service.addEventListener("notification", ({ detail }) => { for (const message of detail) { this.dispatch(message); diff --git a/addons/pos_restaurant/models/pos_config.py b/addons/pos_restaurant/models/pos_config.py index a9a47d70170..6c7d39cc277 100644 --- a/addons/pos_restaurant/models/pos_config.py +++ b/addons/pos_restaurant/models/pos_config.py @@ -15,6 +15,7 @@ class PosConfig(models.Model): floor_ids = fields.Many2many('restaurant.floor', string='Restaurant Floors', help='The restaurant floors served by this point of sale.') set_tip_after_payment = fields.Boolean('Set Tip After Payment', help="Adjust the amount authorized by payment terminals to add a tip after the customers left or at the end of the day.") module_pos_restaurant = fields.Boolean(default=True) + module_pos_restaurant_appointment = fields.Boolean("Table Booking") def get_tables_order_count_and_printing_changes(self): self.ensure_one() diff --git a/addons/pos_restaurant/models/pos_order.py b/addons/pos_restaurant/models/pos_order.py index 52c29c32bf2..372acd8af2f 100644 --- a/addons/pos_restaurant/models/pos_order.py +++ b/addons/pos_restaurant/models/pos_order.py @@ -71,8 +71,11 @@ class PosOrder(models.Model): def send_table_count_notification(self, table_ids): messages = [] for config in self.env['pos.config'].search([('floor_ids', 'in', table_ids.floor_id.ids)]): - order_count = config.get_tables_order_count_and_printing_changes() - messages.append((f'pos_config-{config.id}', 'TABLE_ORDER_COUNT', order_count)) + config_cur_session = config.current_session_id + + if config_cur_session: + order_count = config.get_tables_order_count_and_printing_changes() + messages.append((config_cur_session._get_bus_channel_name(), 'TABLE_ORDER_COUNT', order_count)) self.env['bus.bus']._sendmany(messages) @api.model diff --git a/addons/pos_restaurant/models/pos_restaurant.py b/addons/pos_restaurant/models/pos_restaurant.py index 7fdcd699c31..d80d678aee5 100644 --- a/addons/pos_restaurant/models/pos_restaurant.py +++ b/addons/pos_restaurant/models/pos_restaurant.py @@ -85,31 +85,13 @@ class RestaurantTable(models.Model): width = fields.Float('Width', default=50, help="The table's width in pixels") height = fields.Float('Height', default=50, help="The table's height in pixels") seats = fields.Integer('Seats', default=1, help="The default number of customer served at this table.") - color = fields.Char('Color', help="The table's color, expressed as a valid 'background' CSS property value") + color = fields.Char('Color', help="The table's color, expressed as a valid 'background' CSS property value", default="#35D374") active = fields.Boolean('Active', default=True, help='If false, the table is deactivated and will not be available in the point of sale') def are_orders_still_in_draft(self): draft_orders = self.env['pos.order'].search([('table_id', '=', self.id), ('state', '=', 'draft')]) return len(draft_orders) > 0 - @api.model - def create_from_ui(self, table): - """ create or modify a table from the point of sale UI. - table contains the table's fields. If it contains an - id, it will modify the existing table. It then - returns the id of the table. - """ - if table.get('floor_id'): - table['floor_id'] = table['floor_id'][0] - - sanitized_table = dict([(key, val) for key, val in table.items() if key in self._fields and val is not None]) - table_id = sanitized_table.pop('id', False) - if table_id: - self.browse(table_id).write(sanitized_table) - else: - table_id = self.create(sanitized_table).id - return table_id - @api.ondelete(at_uninstall=False) def _unlink_except_active_pos_session(self): confs = self.mapped('floor_id.pos_config_ids').filtered(lambda c: c.module_pos_restaurant) diff --git a/addons/pos_restaurant/models/res_config_settings.py b/addons/pos_restaurant/models/res_config_settings.py index 1fd35f799a2..13948d0e0b6 100644 --- a/addons/pos_restaurant/models/res_config_settings.py +++ b/addons/pos_restaurant/models/res_config_settings.py @@ -11,6 +11,7 @@ class ResConfigSettings(models.TransientModel): pos_iface_printbill = fields.Boolean(compute='_compute_pos_module_pos_restaurant', store=True, readonly=False) pos_iface_splitbill = fields.Boolean(compute='_compute_pos_module_pos_restaurant', store=True, readonly=False) pos_set_tip_after_payment = fields.Boolean(compute='_compute_pos_set_tip_after_payment', store=True, readonly=False) + pos_module_pos_restaurant_appointment = fields.Boolean(related="pos_config_id.module_pos_restaurant_appointment", readonly=False) @api.depends('pos_module_pos_restaurant', 'pos_config_id') def _compute_pos_module_pos_restaurant(self): diff --git a/addons/pos_restaurant/static/src/app/floor_screen/editable_table.xml b/addons/pos_restaurant/static/src/app/floor_screen/editable_table.xml index 08fc5c0985e..09356ed0844 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/editable_table.xml +++ b/addons/pos_restaurant/static/src/app/floor_screen/editable_table.xml @@ -6,9 +6,6 @@ - - - diff --git a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js index a656f24fcf1..e8181d735c3 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js +++ b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.js @@ -44,7 +44,8 @@ export class FloorScreen extends Component { isColorPicker: false, }); const ui = useState(useService("ui")); - this.pos.floorPlanStyle = ui.isSmall ? "kanban" : "default"; + const mode = localStorage.getItem("floorPlanStyle"); + this.pos.floorPlanStyle = ui.isSmall || mode == "kanban" ? "kanban" : "default"; this.floorMapRef = useRef("floor-map-ref"); this.addFloorRef = useRef("add-floor-ref"); this.map = useRef("map"); @@ -268,11 +269,26 @@ export class FloorScreen extends Component { return firstNum.toString(); } async _save(table) { - const tableCopy = { ...table }; - delete tableCopy.floor; - const tableId = await this.orm.call("restaurant.table", "create_from_ui", [tableCopy]); - table.id = tableId; - this.pos.tables_by_id[tableId] = table; + const tableCopy = { + floor_id: table.floor.id, + color: table.color, + height: table.height, + name: table.name, + position_h: table.position_h, + position_v: table.position_v, + seats: table.seats, + shape: table.shape, + width: table.width, + }; + + if (table.id) { + await this.orm.write("restaurant.table", [table.id], tableCopy); + } else { + const tableId = await this.orm.create("restaurant.table", [tableCopy]); + + table.id = tableId[0]; + this.pos.tables_by_id[tableId] = table; + } } async _renameFloor(floorId, newName) { await this.orm.call("restaurant.floor", "rename_floor", [floorId, newName]); @@ -359,7 +375,9 @@ export class FloorScreen extends Component { } } async onSaveTable(table) { - await this._save(table); + if (this.pos.tables_by_id[table.id]) { + await this._save(table); + } } async addFloor() { const { confirmed, payload: newName } = await this.popup.add(TextInputPopup, { @@ -381,6 +399,9 @@ export class FloorScreen extends Component { } async createTable() { const newTable = await this._createTableHelper(); + newTable.skip_changes = 0; + newTable.changes_count = 0; + newTable.order_count = 0; if (newTable) { this.state.selectedTableIds = []; this.state.selectedTableIds.push(newTable.id); @@ -564,9 +585,7 @@ export class FloorScreen extends Component { } } this.pos.tables_by_id[id].active = false; - await this.orm.call("restaurant.table", "create_from_ui", [ - { active: false, id: id }, - ]); + this.orm.write("restaurant.table", [id], { active: false }); this.activeFloor.tables = this.activeTables.filter((table) => table.id !== id); delete this.pos.tables_by_id[id]; } else { diff --git a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.scss b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.scss index 06c75e23180..9791862e1c4 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.scss +++ b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.scss @@ -150,40 +150,43 @@ // Should probably be moved to table.scss but currently the selectors are too generic .table { + display: flex; + flex-direction: column; + justify-content: center; position: absolute; text-align: center; - font-size: 18px; color: white; background: rgb(53, 211, 116); border-radius: 3px; cursor: pointer; box-shadow: 0px 3px rgba(0,0,0,0.07); transition: background-color 300ms ease-in-out; - overflow: hidden; + box-sizing: border-box; &.selected { outline: solid rgba(255,255,255,0.3); cursor: move; z-index: 50; + overflow: hidden; + } - .table-seats { - margin-left: -12px; - } + .infos { + display: flex; + gap: 1rem; + align-items: center; + justify-content: center; } .order-count { - position: absolute; - top: 0; - left: 50%; background: black; - width: 20px; - margin-top: 1px; - margin-left: -10px; - height: 20px; - line-height: 20px; - border-radius: 10px; - font-size: 16px; + border-radius: 999px; z-index: 10; + width: 16px; + height: 16px; + line-height: 16px; + font-size: 16px; + position: absolute; + padding: 3px; &.notify-printing { background: red; @@ -195,37 +198,32 @@ } } - .table-cover { - display: block; - position: absolute; - left: 0; right: 0; bottom: 0; - border-radius: 0px 0px 3px 3px; - background: rgba(0,0,0,0.2); - - &.full { - border-radius: 3px 3px 3px 3px; - } - } - .table-seats { - position: absolute; display: inline-block; - bottom: 0; - left: 50%; - height: 20px; - line-height: 20px; - font-size: 16px; border-radius: 5px; - margin-left: -16px; - margin-bottom: 4px; - background: black; + background: rgba(0,0,0,0.2); color: white; - opacity: 0.2; z-index: 3; - padding: 0 4px; - } - .table-seats-presence { - opacity: 1; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto; + padding: 0 10px; + position: absolute; + bottom: -5px; + left: 50%; + transform: translate(-50%, -50%); + + .cover { + position: absolute; + left: 0; + top: 0; + height: 100%; + background: black; + opacity: 0.4; + z-index: -1; + } } .table-handle { @@ -253,7 +251,6 @@ max-height: 100%; overflow: hidden; position: relative; - bottom: 5px; } .edit-button.editing { diff --git a/addons/pos_restaurant/static/src/app/floor_screen/table.js b/addons/pos_restaurant/static/src/app/floor_screen/table.js index 92fb2b383d3..45a456b609f 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/table.js +++ b/addons/pos_restaurant/static/src/app/floor_screen/table.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/store/pos_hook"; export class Table extends Component { @@ -25,26 +25,64 @@ export class Table extends Component { setup() { this.pos = usePos(); + this.state = useState({ + containerHeight: 0, + containerWidth: 0, + }); + } + get fontSize() { + const size = this.state.containerHeight / 3; + return size > 20 ? 20 : size; + } + get badgeStyle() { + if (this.props.table.shape !== "round") { + return `top: -6px; right: -6px;`; + } + + const tableHeight = this.state.containerHeight; + const tableWidth = this.state.containerWidth; + const radius = Math.min(tableWidth, tableHeight) / 2; + + let left = 0; + let bottom = 0; + + if (tableHeight > tableWidth) { + left = radius; + bottom = radius + (tableHeight - tableWidth); + } else { + bottom = radius; + left = radius + (tableWidth - tableHeight); + } + + bottom += 0.7 * radius - 8; + left += 0.7 * radius - 8; + + return `bottom: ${bottom}px; left: ${left}px;`; } get style() { const table = this.props.table; - + let style = ""; let background = table.color ? table.color : "rgb(53, 211, 116)"; let textColor = "white"; - let border = "auto"; - let boxShadow = "0px 3px rgba(0,0,0,0.07)"; + if (!this.isOccupied()) { - background = "transparent"; + background = "#00000020"; const rgb = table.floor.background_color .substring(4, table.floor.background_color.length - 1) .replace(/ /g, "") .split(","); textColor = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255 > 0.5 ? "black" : "white"; - border = "3px solid " + table.color; - boxShadow = "none"; } + style += ` + border: 3px solid ${table.color}; + border-radius: ${table.shape === "round" ? 1000 : 3}px; + background: ${background}; + box-shadow: 0px 3px rgba(0,0,0,0.07); + padding: ${table.shape === "round" ? "4px 10px" : "4px 8px"}; + color: ${textColor};`; + if (this.pos.floorPlanStyle == "kanban") { const floor = table.floor; const index = floor.tables.indexOf(table); @@ -54,37 +92,35 @@ export class Table extends Component { const position_h = widthTable * (index % nbrHorizontal) + 5 + (index % nbrHorizontal) * 10; const position_v = - widthTable * Math.floor(index / nbrHorizontal) + - 5 + + (widthTable + 25) * Math.floor(index / nbrHorizontal) + + 10 + Math.floor(index / nbrHorizontal) * 10; - return ` + this.state.containerHeight = widthTable; + this.state.containerWidth = widthTable; + + style += ` width: ${widthTable}px; height: ${widthTable}px; - line-height: ${widthTable}px; top: ${position_v}px; left: ${position_h}px; - border: ${border}; - border-radius: ${table.shape === "round" ? 1000 : 3}px; - background: ${background}; - box-shadow: ${boxShadow}; - font-size: ${widthTable >= 150 ? 32 : 16}px; - color: ${textColor}; `; } else { - return ` + this.state.containerHeight = table.height; + this.state.containerWidth = table.width; + + style += ` width: ${table.width}px; height: ${table.height}px; - line-height: ${table.height}px; top: ${table.position_v}px; left: ${table.position_h}px; - border: ${border}; - border-radius: ${table.shape === "round" ? 1000 : 3}px; - background: ${background}; - box-shadow: ${boxShadow}; - font-size: ${table.height >= 150 && table.width >= 150 ? 32 : 16}px; - color: ${textColor}; `; } + + style += ` + font-size: ${this.fontSize}px; + line-height: ${this.fontSize}px;`; + + return style; } get fill() { const customerCount = this.pos.getCustomerCount(this.props.table.id); @@ -110,18 +146,16 @@ export class Table extends Component { return !Number.isNaN(result) ? result : 0; } get orderCountClass() { - const countClass = { "order-count": true }; - if (this.pos.orderPreparationCategories.size) { - const notifications = this._getNotifications(); - countClass["notify-printing"] = notifications.printing; - countClass["notify-skipped"] = notifications.skipped; - } + const notifications = this._getNotifications(); + const countClass = { + "order-count": true, + "notify-printing": notifications.printing, + "notify-skipped": notifications.skipped, + }; return countClass; } get customerCountDisplay() { - return `${this.pos.getCustomerCount(this.props.table.id)}/${ - this.props.table.seats - }`; + return `${this.pos.getCustomerCount(this.props.table.id)}/${this.props.table.seats}`; } _getNotifications() { const table = this.props.table; @@ -133,8 +167,7 @@ export class Table extends Component { } isOccupied() { return ( - this.pos.getCustomerCount(this.props.table.id) > 0 || - this.props.table.order_count > 0 + this.pos.getCustomerCount(this.props.table.id) > 0 || this.props.table.order_count > 0 ); } } diff --git a/addons/pos_restaurant/static/src/app/floor_screen/table.xml b/addons/pos_restaurant/static/src/app/floor_screen/table.xml index 5e63a046402..5c9ad417313 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/table.xml +++ b/addons/pos_restaurant/static/src/app/floor_screen/table.xml @@ -1,19 +1,19 @@ -
- - - - - - - - +
+ + + + + + +
+ +
- diff --git a/addons/pos_restaurant/static/src/app/navbar/navbar.js b/addons/pos_restaurant/static/src/app/navbar/navbar.js index ed3ca604966..f2902eb17a0 100644 --- a/addons/pos_restaurant/static/src/app/navbar/navbar.js +++ b/addons/pos_restaurant/static/src/app/navbar/navbar.js @@ -18,7 +18,9 @@ patch(Navbar.prototype, "pos_restaurant.Navbar", { return this._super() || (this.pos.config.module_pos_restaurant && !this.pos.table); }, onSwitchButtonClick() { - this.pos.floorPlanStyle = this.pos.floorPlanStyle == "kanban" ? "default" : "kanban"; + const mode = this.pos.floorPlanStyle == "kanban" ? "default" : "kanban"; + localStorage.setItem("floorPlanStyle", mode); + this.pos.floorPlanStyle = mode; }, toggleEditMode() { this.pos.toggleEditMode(); diff --git a/addons/pos_restaurant/static/src/app/product_screen/order/order.js b/addons/pos_restaurant/static/src/app/product_screen/order/order.js new file mode 100644 index 00000000000..72045bd34fc --- /dev/null +++ b/addons/pos_restaurant/static/src/app/product_screen/order/order.js @@ -0,0 +1,44 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { OrderWidget } from "@point_of_sale/app/screens/product_screen/order/order"; +import { AlertPopup } from "@point_of_sale/app/utils/alert_popup/alert_popup"; +import { _t } from "@web/core/l10n/translation"; + +patch(OrderWidget.prototype, "pos_restaurant.OrderWidget", { + releaseTable() { + const currentTable = this.pos.table; + const orderOnTable = this.pos.orders.filter( + (o) => o.tableId === currentTable.id && o.finalized === false + ); + const orderOnTableWithOrderline = orderOnTable.find((o) => o.orderlines.length > 0); + + if (orderOnTableWithOrderline) { + this.popup.add(AlertPopup, { + title: _t("Open orders"), + body: _t( + "There are open orders on this table. Please close them before releasing the table." + ), + }); + + return; + } + + for (const order of orderOnTable) { + this.pos.removeOrder(order); + } + + this.pos.showScreen("FloorScreen"); + }, + get showReleaseBtn() { + if (!this.pos.config.module_pos_restaurant) { + return false; + } + + const currentTable = this.pos.table; + const noEmptyOrderOnTable = this.pos.orders.filter( + (o) => o.tableId === currentTable.id && o.finalized === false && o.orderlines.length > 0 + ); + return noEmptyOrderOnTable.length ? false : true; + }, +}); diff --git a/addons/pos_restaurant/static/src/app/product_screen/order/order.xml b/addons/pos_restaurant/static/src/app/product_screen/order/order.xml new file mode 100644 index 00000000000..51886bd73c5 --- /dev/null +++ b/addons/pos_restaurant/static/src/app/product_screen/order/order.xml @@ -0,0 +1,8 @@ + + + + + + + + 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 index b7ec6918641..77287371e25 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js @@ -30,7 +30,7 @@ export class TableGuestsButton extends Component { }); if (confirmed) { - const guestCount = parseInt(inputNumber, 10) || 1; + const guestCount = parseInt(inputNumber, 10) || 0; // Set the maximum number possible for an integer const max_capacity = 2 ** 31 - 1; if (guestCount > max_capacity) { @@ -43,6 +43,12 @@ export class TableGuestsButton extends Component { }); return; } + + if (guestCount == 0 && this.currentOrder.orderlines.length === 0) { + this.pos.removeOrder(this.currentOrder); + this.pos.showScreen("FloorScreen"); + } + this.currentOrder.setCustomerCount(guestCount); } } diff --git a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js index 8d5ac27076a..d77737bc21d 100644 --- a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js @@ -16,8 +16,16 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { }); }, getTable(order) { - if (order.getTable()) { - return `${order.getTable().floor.name} (${order.getTable().name})`; + const table = order.getTable(); + if (table) { + let floorAndTable = ""; + + if (this.pos.floors && this.pos.floors.length > 1) { + floorAndTable = `${table.floor.name}/`; + } + + floorAndTable += table.name; + return floorAndTable; } }, //@override @@ -118,9 +126,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { async onDoRefund() { const order = this.getSelectedSyncedOrder(); if (this.pos.config.module_pos_restaurant && order && !this.pos.table) { - this.pos.setTable( - order.table ? order.table : Object.values(this.pos.tables_by_id)[0] - ); + this.pos.setTable(order.table ? order.table : Object.values(this.pos.tables_by_id)[0]); } this._super(...arguments); }, 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 index dcf41ea658f..02a465be892 100644 --- a/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml +++ b/addons/pos_restaurant/static/src/xml/Screens/ProductScreen/ControlButtons/TableGuestsButton.xml @@ -7,7 +7,7 @@
- Guests + Dine-in Guests
diff --git a/addons/pos_restaurant/static/tests/tours/helpers/FloorScreenTourMethods.js b/addons/pos_restaurant/static/tests/tours/helpers/FloorScreenTourMethods.js index 7a93f004d84..f1b932892cb 100644 --- a/addons/pos_restaurant/static/tests/tours/helpers/FloorScreenTourMethods.js +++ b/addons/pos_restaurant/static/tests/tours/helpers/FloorScreenTourMethods.js @@ -131,17 +131,25 @@ class Check { } tableSeatIs(table, val) { return [ + { + content: `Unselect table`, + trigger: `.floor-map`, + }, { content: `number of seats in table '${table}' is '${val}'`, - trigger: `.floor-map .table .label:contains("${table}") ~ .table-seats:contains("${val}")`, + trigger: `.floor-map .table .infos:has(.label:contains("${table}")) ~ .table-seats:contains("${val}")`, run: function () {}, }, + { + content: `click table '${table}'`, + trigger: `.floor-map .table .label:contains("${table}")`, + }, ]; } orderCountSyncedInTableIs(table, count) { return [ { - trigger: `.floor-map .table .order-count:contains("${count}") ~ .label:contains("${table}")`, + trigger: `.floor-map .table .label:contains("${table}") ~ .order-count:contains("${count}")`, run: function () {}, }, ]; diff --git a/addons/pos_restaurant/views/res_config_settings_views.xml b/addons/pos_restaurant/views/res_config_settings_views.xml index 8994ec0e24c..603fe31b39f 100644 --- a/addons/pos_restaurant/views/res_config_settings_views.xml +++ b/addons/pos_restaurant/views/res_config_settings_views.xml @@ -28,6 +28,14 @@ + + +
+
+ Save this page and come back here to set up the feature. +
+
+