From 3236c1cb5025d2c2d446d11f67fda1ea51ceb992 Mon Sep 17 00:00:00 2001 From: "Theo VINCENT (thvi)" Date: Fri, 16 Jun 2023 14:45:23 +0000 Subject: [PATCH] [IMP] pos_restaurant, *: default settings and data *: point_of_sale, pos_self_order Changes: - Adds a default restaurant with some default usual settings (with tips after payment if the company is in the United States) but no floor in the Point of Sale app. Makes pos_restaurant module auto installed with point_of_sale. - Makes some restaurant settings enabled by default (only for restaurants). - Adds loadable default demonstration data for onboarding (only when no real product is available in POS), specific to a restaurant. The default demonstration data for onboarding of point_of_sale and pos_restaurant are loaded together (if pos_restaurant is installed) to make it so an user that clicks to add demo data will have demo data for the default point of sale and also for the default restaurant (and the current restaurant if it is different than the default one). Makes the load demonstration data button visible when there is no product with a positive price available, because without that condition the tips (in restaurant) and discount were preventing the display of the button. - Hides the Preparation Printers menu item if there is no POS config using the printers. - Renames some labels. Fixes: - Adds checks to always set set_tip_after_payment to False if a point of sale is no longer a restaurant or if the tips are disabled. This is needed because the code of pos_restaurant seems to be written considering that set_tip_after_payment is True only when the point of sale is a restaurant with the tips enabled (which wasn't guaranteed). Without that checks, some tests like ReceiptScreenTour fail. Another solution to make the tests successful without that checks would be to add !this.env.pos.config.module_pos_restaurant in pos_restaurant/static/src/js/Screens/PaymentScreen.js:16. But because other parts of the code only check the value of set_tip_after_payment, I think it is better to opt for the additionnal checks. - Fixes duplication of displayed product categories when loading demonstration data. - Fixes products before loading demonstration data that were kept and displayed after (discount for example). - Fixes categories and therefore communication with the preparation display after loading demo data in a restaurant. - Adds a check on the server side to prevent demonstration data loading when there is at least one valid product for POS. - Fixes order count display when "NaN" (in floors and tables view). - Fixes arguments of orm call for set_not_tip (model and method names were not given in the right order). closes odoo/odoo#117941 Task-id: 2484291 Related: odoo/enterprise#39450 Signed-off-by: Joseph Caburnay (jcb) --- addons/point_of_sale/models/pos_config.py | 12 ++ .../models/pos_payment_method.py | 2 +- addons/point_of_sale/models/pos_session.py | 22 +++- .../control_panel/control_panel.xml | 26 +++-- .../product_list/product_list.js | 38 ++++++- .../product_list/product_list.xml | 32 +++--- .../point_of_sale/static/src/app/store/db.js | 107 +++++++++++++++++- .../static/src/app/store/pos_store.js | 48 ++++++++ addons/point_of_sale/static/src/scss/pos.scss | 10 +- addons/pos_restaurant/__manifest__.py | 2 + .../data/pos_restaurant_data.xml | 19 ++++ .../data/pos_restaurant_onboarding.xml | 55 +++++++++ addons/pos_restaurant/models/pos_config.py | 25 +++- addons/pos_restaurant/models/pos_session.py | 12 +- .../static/src/app/floor_screen/edit_bar.xml | 4 +- .../src/app/floor_screen/floor_screen.js | 3 +- .../src/app/floor_screen/floor_screen.xml | 8 +- .../static/src/app/floor_screen/table.js | 9 +- .../static/src/js/Screens/TicketScreen.js | 2 +- addons/pos_restaurant/tests/test_frontend.py | 1 + .../views/res_config_settings_views.xml | 2 +- addons/pos_self_order/__manifest__.py | 1 + .../data/pos_restaurant_data.xml | 17 +++ addons/pos_self_order/models/pos_config.py | 2 +- .../models/res_config_settings.py | 2 +- 25 files changed, 402 insertions(+), 59 deletions(-) create mode 100644 addons/pos_restaurant/data/pos_restaurant_data.xml create mode 100644 addons/pos_restaurant/data/pos_restaurant_onboarding.xml create mode 100644 addons/pos_self_order/data/pos_restaurant_data.xml diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 7001fed9017..a5d6f18c91c 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -395,6 +395,7 @@ class PosConfig(models.Model): pos_configs = super().create(vals_list) pos_configs.sudo()._check_modules_to_install() pos_configs.sudo()._check_groups_implied() + pos_configs._update_preparation_printers_menuitem_visibility() # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install. return pos_configs @@ -406,16 +407,25 @@ class PosConfig(models.Model): else: raise UserError(_('The default tip product is missing. Please manually specify the tip product. (See Tips field.)')) + def _update_preparation_printers_menuitem_visibility(self): + prepa_printers_menuitem = self.sudo().env.ref('point_of_sale.menu_pos_preparation_printer', raise_if_not_found=False) + if prepa_printers_menuitem: + prepa_printers_menuitem.active = self.sudo().env['pos.config'].search_count([('is_order_printer', '=', True)], limit=1) > 0 + def write(self, vals): self._reset_default_on_vals(vals) if ('is_order_printer' in vals and not vals['is_order_printer']): vals['printer_ids'] = [fields.Command.clear()] + bypass_categories_forbidden_change = self.env.context.get('bypass_categories_forbidden_change', False) + opened_session = self.mapped('session_ids').filtered(lambda s: s.state != 'closed') if opened_session: forbidden_fields = [] for key in self._get_forbidden_change_fields(): if key in vals.keys(): + if bypass_categories_forbidden_change and key in ('limit_categories', 'iface_available_categ_ids'): + continue if key == 'use_pricelist' and vals[key]: continue if key == 'available_pricelist_ids': @@ -434,6 +444,8 @@ class PosConfig(models.Model): self.sudo()._set_fiscal_position() self.sudo()._check_modules_to_install() self.sudo()._check_groups_implied() + if 'is_order_printer' in vals: + self._update_preparation_printers_menuitem_visibility() return result def _get_forbidden_change_fields(self): diff --git a/addons/point_of_sale/models/pos_payment_method.py b/addons/point_of_sale/models/pos_payment_method.py index 6ac7c18d7fb..5679c0fe038 100644 --- a/addons/point_of_sale/models/pos_payment_method.py +++ b/addons/point_of_sale/models/pos_payment_method.py @@ -38,7 +38,7 @@ class PosPaymentMethod(models.Model): default=False, help='Forces to set a customer when using this payment method and splits the journal entries for each customer. It could slow down the closing process.') open_session_ids = fields.Many2many('pos.session', string='Pos Sessions', compute='_compute_open_session_ids', help='Open PoS sessions that are using this payment method.') - config_ids = fields.Many2many('pos.config', string='Point of Sale Configurations') + config_ids = fields.Many2many('pos.config', string='Point of Sale') company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) use_payment_terminal = fields.Selection(selection=lambda self: self._get_payment_terminal_selection(), string='Use a Payment Terminal', help='Record payments with a terminal on this journal.') # used to hide use_payment_terminal when no payment interfaces are installed diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index f4119017bcc..75d14c290b5 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -1563,8 +1563,8 @@ class PosSession(models.Model): def get_onboarding_data(self): return { - "categories": self._load_model('pos.category'), - "products": self._load_model('product.product'), + 'pos.category': self._load_model('pos.category'), + 'product.product': self._load_model('product.product'), } def _load_model(self, model): @@ -1636,6 +1636,7 @@ class PosSession(models.Model): loaded_data['attributes_by_ptal_id'] = self._get_attributes_by_ptal_id() loaded_data['base_url'] = self.get_base_url() + loaded_data['pos_has_valid_product'] = self._pos_has_valid_product() @api.model def _pos_ui_models_to_load(self): @@ -2094,10 +2095,21 @@ class PosSession(models.Model): self.message_post(body=body, author_id=partner_id) + def _pos_has_valid_product(self): + return self.env['product.product'].sudo().search_count(['&', ('available_in_pos', '=', True), ('list_price', '>', 0)], limit=1) > 0 + + def _load_onboarding_data(self): + convert.convert_file(self.env, 'point_of_sale', 'data/point_of_sale_onboarding.xml', None, mode='init', kind='data') + def load_product_frontend(self): - convert.convert_file(self.env, 'point_of_sale', 'data/point_of_sale_onboarding.xml', None, mode='init', - kind='data') - return self.get_onboarding_data() + allowed = not self._pos_has_valid_product() + if allowed: + self._load_onboarding_data() + + return { + 'models_data': self.get_onboarding_data(), + 'successful': allowed, + } class ProcurementGroup(models.Model): diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/control_panel/control_panel.xml b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/control_panel/control_panel.xml index ee739ea1da1..8e4c2a2c379 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/control_panel/control_panel.xml +++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/control_panel/control_panel.xml @@ -4,18 +4,20 @@
-
- - - - - - -
+ +
+ + + + + + +
+
diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.js b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.js index d6b4992b64d..25e4491daff 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.js +++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.js @@ -10,6 +10,7 @@ import { ProductsWidgetControlPanel } from "@point_of_sale/app/screens/product_s import { Component, useState } from "@odoo/owl"; import { sprintf } from "@web/core/utils/strings"; import { OfflineErrorPopup } from "@point_of_sale/app/errors/popups/offline_error_popup"; +import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; export class ProductsWidget extends Component { static components = { ProductItem, ProductsWidgetControlPanel }; @@ -30,8 +31,8 @@ export class ProductsWidget extends Component { this.notification = useService("pos_notification"); this.orm = useService("orm"); } - get hasProducts() { - return Object.keys(this.pos.db.product_by_id).length > 0; + get posHasValidProduct() { + return this.pos.posHasValidProduct(); } get selectedCategoryId() { return this.pos.selectedCategoryId; @@ -166,17 +167,42 @@ export class ProductsWidget extends Component { } } async loadDemoDataProducts() { - const { products, categories } = await this.orm.call( + const { models_data, successful } = await this.orm.call( "pos.session", "load_product_frontend", [this.pos.pos_session.id] ); - this.pos.db.add_categories(categories); - this.pos._loadProductProduct(products); + if (!successful) { + this.popup.add(ErrorPopup, { + title: this.env._t("Demo products are no longer available"), + body: this.env._t( + "A valid product already exists for Point of Sale. Therefore, demonstration products cannot be loaded." + ), + }); + // But the received models_data is still used to update the current session. + } + if (!models_data) { + this._showLoadDemoDataMissingDataError("models_data"); + return; + } + for (const dataName of ["pos.category", "product.product"]) { + if (!models_data[dataName]) { + this._showLoadDemoDataMissingDataError(dataName); + return; + } + } + this.pos.updateModelsData(models_data); + } + _showLoadDemoDataMissingDataError(missingData) { + console.error( + "Missing '", + missingData, + "' in pos.session:load_product_frontend server answer." + ); } createNewProducts() { - window.open("/web#action=point_of_sale.action_client_product_menu", "_blank"); + window.open("/web#action=point_of_sale.action_client_product_menu", "_self"); this.state.showReloadMessage = true; } } diff --git a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.xml b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.xml index e9eb102c249..c581cdfc9d5 100644 --- a/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.xml +++ b/addons/point_of_sale/static/src/app/screens/product_screen/product_list/product_list.xml @@ -13,26 +13,30 @@ hasNoCategories="hasNoCategories" loadProductFromServer.bind="onPressEnterKey" />
-
+
-

- No product found for "" in this category. -

-

- There are no products in this category. -

-
- - -
- Please reload this page once products have been created -
-
+ +

No products found for "" in this category.

+
+ + +

There are no products in this category.

+
+ +
+

No products here, but don't worry, we've got your back.

+

Play with our demo products or create your own products

+
+ Please reload this page once products have been created +
+
+
+
Search more
diff --git a/addons/point_of_sale/static/src/app/store/db.js b/addons/point_of_sale/static/src/app/store/db.js index 50f8a1f9a1d..6d7417d9c26 100644 --- a/addons/point_of_sale/static/src/app/store/db.js +++ b/addons/point_of_sale/static/src/app/store/db.js @@ -118,7 +118,10 @@ export class PosDB { if (!self.category_childs[parent_id]) { self.category_childs[parent_id] = []; } - self.category_childs[parent_id].push(cat.id); + + if (!self.category_childs[parent_id].includes(cat.id)) { + self.category_childs[parent_id].push(cat.id); + } }); function make_ancestors(cat_id, ancestors) { self.category_ancestors[cat_id] = ancestors; @@ -133,7 +136,43 @@ export class PosDB { } make_ancestors(this.root_category_id, []); } + /** + * Removes all categories specified by their id. + * @param {integer[]} categories_id + */ + remove_categories(categories_id) { + categories_id = categories_id.filter((cat_id) => cat_id !== this.root_category_id); + categories_id.forEach((cat_id) => { + if (cat_id === undefined || cat_id === null) { + return; + } + const cat = this.category_by_id[cat_id]; + if (!cat) { + return; + } + const parent_id = this.category_parent[cat.id]; + if (this.category_childs[parent_id]) { + this._remove_all_occurrences_from_array(this.category_childs[parent_id], cat.id); + if (this.category_childs[parent_id].length === 0) { + delete this.category_childs[parent_id]; + } + } + delete this.category_parent[cat.id]; + + delete this.category_by_id[cat_id]; + }); + const removed_categories_id = new Set(categories_id); + for (const cat_id_str in this.category_ancestors) { + if (removed_categories_id.has(Number(cat_id_str))) { + delete this.category_ancestors[cat_id_str]; + } else { + this.category_ancestors[cat_id_str] = this.category_ancestors[cat_id_str].filter( + (anc_id_str) => !removed_categories_id.has(Number(anc_id_str)) + ); + } + } + } /* loads a record store from the database. returns default if nothing is found */ load(store, deft) { if (CACHE[store] !== undefined) { @@ -220,6 +259,72 @@ export class PosDB { } } } + /** + * Removes all products specified by their id. + * @param {integer[]} products_id + */ + remove_products(products_id) { + if (!(products_id instanceof Array)) { + products_id = [products_id]; + } + products_id.forEach((product_id) => { + if (product_id === undefined || product_id === null) { + return; + } + const product = this.product_by_id[product_id]; + if (!product) { + return; + } + if (product.available_in_pos) { + const product_search_string = unaccent(this._product_search_string(product)); + const all_categ_ids = product.pos_categ_ids.length + ? product.pos_categ_ids + : [this.root_category_id]; + + for (const categ_id of all_categ_ids) { + this.remove_product_from_category(product.id, product_search_string, categ_id); + const categ_ancestors_id = this.get_category_ancestors_ids(categ_id) || []; + categ_ancestors_id.forEach((ancestor_id) => { + this.remove_product_from_category( + product.id, + product_search_string, + ancestor_id + ); + }); + } + } + delete this.product_by_id[product.id]; + if (product.barcode) { + delete this.product_by_barcode[product.barcode]; + } + }); + } + remove_product_from_category(product_id, product_search_string, categ_id) { + const stored_categories = this.product_by_category_id; + if (stored_categories[categ_id]) { + this._remove_all_occurrences_from_array(stored_categories[categ_id], product_id); + if (stored_categories[categ_id].length === 0) { + delete stored_categories[categ_id]; + } + } + + if (this.category_search_string[categ_id]) { + this.category_search_string[categ_id] = this.category_search_string[categ_id].replace( + product_search_string, + "" + ); + } + if (!this.category_search_string[categ_id]) { + delete this.category_search_string[categ_id]; + } + } + _remove_all_occurrences_from_array(array, element) { + for (let i = array.length - 1; i >= 0; i--) { + if (array[i] === element) { + array.splice(i, 1); + } + } + } add_packagings(product_packagings) { var self = this; Object.values(product_packagings || {}).map((product_packaging) => { diff --git a/addons/point_of_sale/static/src/app/store/pos_store.js b/addons/point_of_sale/static/src/app/store/pos_store.js index 6f000c71566..e484e4c7759 100644 --- a/addons/point_of_sale/static/src/app/store/pos_store.js +++ b/addons/point_of_sale/static/src/app/store/pos_store.js @@ -243,6 +243,7 @@ export class PosStore extends Reactive { this._loadPosPaymentMethod(); this.fiscal_positions = loadedData["account.fiscal.position"]; this.base_url = loadedData["base_url"]; + this.pos_has_valid_product = loadedData["pos_has_valid_product"]; await this._loadPictures(); await this._loadPosPrinters(loadedData["pos.printer"]); } @@ -393,6 +394,53 @@ export class PosStore extends Reactive { return this.addPartners(partners); } + async updateModelsData(models_data) { + const products = models_data["product.product"]; + const categories = models_data["pos.category"]; + + let removed_categories_id; + if (categories) { + const previous_categories_id = Object.values(this.db.category_by_id).map((c) => c.id); + const received_categories_id = new Set(categories.map((c) => c.id)); + this.db.add_categories(categories); + removed_categories_id = previous_categories_id.filter( + (p) => !received_categories_id.has(p) + ); + } + if (products) { + const previous_products_id = Object.values(this.db.product_by_id).map((p) => p.id); + const received_products_id = new Set(products.map((p) => p.id)); + this._loadProductProduct(products); + + const removed_products_id = previous_products_id.filter( + (p) => !received_products_id.has(p) + ); + this.db.remove_products(removed_products_id); + + if ( + Object.values(this.db.product_by_id).some( + (p) => p.available_in_pos && p.lst_price > 0 + ) + ) { + this.pos_has_valid_product = true; + } + } + + if (categories) { + this.db.remove_categories(removed_categories_id); + } + } + + /** + * @returns true if the POS app (not only this POS config) has at least one valid product. + */ + posHasValidProduct() { + return ( + this.pos_has_valid_product || + Object.values(this.db.product_by_id).some((p) => p.available_in_pos && p.lst_price > 0) + ); + } + setSelectedCategoryId(categoryId) { this.selectedCategoryId = categoryId; } diff --git a/addons/point_of_sale/static/src/scss/pos.scss b/addons/point_of_sale/static/src/scss/pos.scss index 3e0d0c3eb3f..38207782383 100644 --- a/addons/point_of_sale/static/src/scss/pos.scss +++ b/addons/point_of_sale/static/src/scss/pos.scss @@ -485,7 +485,7 @@ td { justify-content: center; flex-direction: column; border: none; - + h1 { font-size: 20px; margin: 0; @@ -889,7 +889,7 @@ td { color: white; border: solid 1px $primary; } - + } .pos .product-list-scroller{ @@ -2945,7 +2945,7 @@ td { font-size: 12px; font-weight: bold; margin: 5px; - + div { margin: 0; } @@ -3719,6 +3719,8 @@ td { padding: 10px 0; cursor: pointer; font-size: large; + color: #017e84; + font-weight: bold; } .info-load-product-message { @@ -3901,7 +3903,7 @@ td { .menu-item { padding: 10px; - + span, i { color: black; inline-size: 196px; diff --git a/addons/pos_restaurant/__manifest__.py b/addons/pos_restaurant/__manifest__.py index ad9a92b4b8e..fdff2235057 100644 --- a/addons/pos_restaurant/__manifest__.py +++ b/addons/pos_restaurant/__manifest__.py @@ -17,12 +17,14 @@ This module adds several features to the Point of Sale that are specific to rest """, 'depends': ['point_of_sale'], + 'auto_install': True, 'website': 'https://www.odoo.com/app/point-of-sale-restaurant', 'data': [ 'security/ir.model.access.csv', 'views/pos_order_views.xml', 'views/pos_restaurant_views.xml', 'views/res_config_settings_views.xml', + 'data/pos_restaurant_data.xml', ], 'demo': [ 'data/pos_restaurant_demo.xml', diff --git a/addons/pos_restaurant/data/pos_restaurant_data.xml b/addons/pos_restaurant/data/pos_restaurant_data.xml new file mode 100644 index 00000000000..a12752b99da --- /dev/null +++ b/addons/pos_restaurant/data/pos_restaurant_data.xml @@ -0,0 +1,19 @@ + + + + + Restaurant + True + True + False + True + False + False + False + + + + + + + diff --git a/addons/pos_restaurant/data/pos_restaurant_onboarding.xml b/addons/pos_restaurant/data/pos_restaurant_onboarding.xml new file mode 100644 index 00000000000..2bd34ff67a7 --- /dev/null +++ b/addons/pos_restaurant/data/pos_restaurant_onboarding.xml @@ -0,0 +1,55 @@ + + + + Drinks + + + + Food + + + + + Restaurant food + + + + + True + 7.0 + Margherita + 0.01 + + + + + + True + 7.0 + Cheese Burger + 0.01 + + + + + + + + True + 2.20 + Coca-Cola + 0.01 + + + + + + True + 2.20 + Water + 0.01 + + + + + diff --git a/addons/pos_restaurant/models/pos_config.py b/addons/pos_restaurant/models/pos_config.py index 2e61fee03d1..a9a47d70170 100644 --- a/addons/pos_restaurant/models/pos_config.py +++ b/addons/pos_restaurant/models/pos_config.py @@ -59,10 +59,33 @@ class PosConfig(models.Model): forbidden_keys.append('floor_ids') return forbidden_keys + def _set_tips_after_payment_if_country_custom(self): + self.ensure_one() + company = self.company_id or self.env.company or self.env['res.company']._get_main_company() + if company and company.country_id and company.country_id.code == 'US': + self.update({ + 'iface_tipproduct': True, + 'set_tip_after_payment': True, + }) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + is_restaurant = 'module_pos_restaurant' not in vals or vals['module_pos_restaurant'] + if is_restaurant and 'iface_splitbill' not in vals: + vals['iface_splitbill'] = True + if not is_restaurant or not vals.get('iface_tipproduct', False): + vals['set_tip_after_payment'] = False + return super(PosConfig, self).create(vals_list) + def write(self, vals): if ('module_pos_restaurant' in vals and vals['module_pos_restaurant'] is False): vals['floor_ids'] = [(5, 0, 0)] - return super(PosConfig, self).write(vals) + + if ('module_pos_restaurant' in vals and not vals['module_pos_restaurant']) or ('iface_tipproduct' in vals and not vals['iface_tipproduct']): + vals['set_tip_after_payment'] = False + + return super().write(vals) @api.model def add_cash_payment_method(self): diff --git a/addons/pos_restaurant/models/pos_session.py b/addons/pos_restaurant/models/pos_session.py index 87897eadcce..68dc2a4ab92 100644 --- a/addons/pos_restaurant/models/pos_session.py +++ b/addons/pos_restaurant/models/pos_session.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models +from odoo import models, Command +from odoo.tools import convert from itertools import groupby from odoo.osv.expression import AND @@ -52,3 +53,12 @@ class PosSession(models.Model): def get_pos_ui_restaurant_floor(self): return self._get_pos_ui_restaurant_floor(self._loader_params_restaurant_floor()) + + def _load_onboarding_data(self): + super()._load_onboarding_data() + convert.convert_file(self.env, 'pos_restaurant', 'data/pos_restaurant_onboarding.xml', None, mode='init', kind='data') + configs = self.config_id.filtered('module_pos_restaurant').union(self.env.ref('pos_restaurant.pos_config_main_restaurant', raise_if_not_found=False)) + configs.with_context(bypass_categories_forbidden_change=True).write({ + 'limit_categories': True, + 'iface_available_categ_ids': [Command.link(self.env.ref('pos_restaurant.onboarding_drinks_category').id), Command.link(self.env.ref('pos_restaurant.onboarding_food_category').id)] + }) diff --git a/addons/pos_restaurant/static/src/app/floor_screen/edit_bar.xml b/addons/pos_restaurant/static/src/app/floor_screen/edit_bar.xml index c1f296d5d71..1ffc030533f 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/edit_bar.xml +++ b/addons/pos_restaurant/static/src/app/floor_screen/edit_bar.xml @@ -6,7 +6,7 @@ - ADD + TABLE @@ -63,7 +63,7 @@
-
+
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 d3125225668..a656f24fcf1 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 @@ -363,7 +363,8 @@ export class FloorScreen extends Component { } async addFloor() { const { confirmed, payload: newName } = await this.popup.add(TextInputPopup, { - title: this.env._t("Floor Name"), + title: this.env._t("New Floor"), + placeholder: this.env._t("Floor name"), }); if (!confirmed) { return; diff --git a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.xml b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.xml index 41e4236dc51..38a9db8f913 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.xml +++ b/addons/pos_restaurant/static/src/app/floor_screen/floor_screen.xml @@ -3,10 +3,10 @@
- @@ -31,7 +31,7 @@ >
- No tables available,
ADD your first table
+ Oops! No tables available.
Add a new table to get started.
@@ -42,7 +42,7 @@
- No floors available,
ADD a floor to start
+ Oops! No floors available.
Add a new floor to get started.
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 06c4ea0fbb5..92fb2b383d3 100644 --- a/addons/pos_restaurant/static/src/app/floor_screen/table.js +++ b/addons/pos_restaurant/static/src/app/floor_screen/table.js @@ -99,12 +99,15 @@ export class Table extends Component { // do not count the orders that are already finalized !o.finalized ); + let result; if (table.changes_count > 0) { - return table.changes_count; + result = table.changes_count; } else if (table.skip_changes > 0) { - return table.skip_changes; + result = table.skip_changes; + } else { + result = table.order_count + unsynced_orders.length; } - return table.order_count + unsynced_orders.length; + return !Number.isNaN(result) ? result : 0; } get orderCountClass() { const countClass = { "order-count": true }; diff --git a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js index ffba45c1059..66dafc852fe 100644 --- a/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js +++ b/addons/pos_restaurant/static/src/js/Screens/TicketScreen.js @@ -117,7 +117,7 @@ patch(TicketScreen.prototype, "pos_restaurant.TicketScreen", { } }, async setNoTip(serverId) { - await this.orm.call("set_no_tip", "pos.order", [serverId]); + await this.orm.call("pos.order", "set_no_tip", [serverId]); }, _getOrderStates() { const result = this._super(...arguments); diff --git a/addons/pos_restaurant/tests/test_frontend.py b/addons/pos_restaurant/tests/test_frontend.py index 21dc7c52880..9a074e84829 100644 --- a/addons/pos_restaurant/tests/test_frontend.py +++ b/addons/pos_restaurant/tests/test_frontend.py @@ -49,6 +49,7 @@ class TestFrontend(odoo.tests.HttpCase): 'start_category': True, 'is_order_printer': True, 'printer_ids': [(4, printer.id)], + 'iface_tipproduct': False, }) main_floor = self.env['restaurant.floor'].create({ diff --git a/addons/pos_restaurant/views/res_config_settings_views.xml b/addons/pos_restaurant/views/res_config_settings_views.xml index b741f5f049d..8994ec0e24c 100644 --- a/addons/pos_restaurant/views/res_config_settings_views.xml +++ b/addons/pos_restaurant/views/res_config_settings_views.xml @@ -33,7 +33,7 @@
-
diff --git a/addons/pos_self_order/__manifest__.py b/addons/pos_self_order/__manifest__.py index ef24383ac54..7aaf2812e37 100644 --- a/addons/pos_self_order/__manifest__.py +++ b/addons/pos_self_order/__manifest__.py @@ -19,6 +19,7 @@ "data/restaurant_table_data.xml", "views/res_config_settings_views.xml", "views/point_of_sale_dashboard.xml", + "data/pos_restaurant_data.xml", ], "assets": { "pos_self_order.assets_self_order": [ diff --git a/addons/pos_self_order/data/pos_restaurant_data.xml b/addons/pos_self_order/data/pos_restaurant_data.xml new file mode 100644 index 00000000000..2460f4c862a --- /dev/null +++ b/addons/pos_self_order/data/pos_restaurant_data.xml @@ -0,0 +1,17 @@ + + + + + True + True + + + + + diff --git a/addons/pos_self_order/models/pos_config.py b/addons/pos_self_order/models/pos_config.py index ce936d34c9f..5e384e62f2a 100644 --- a/addons/pos_self_order/models/pos_config.py +++ b/addons/pos_self_order/models/pos_config.py @@ -36,7 +36,7 @@ class PosConfig(models.Model): self_order_pay_after = fields.Selection( [("each", "Each Order"), ("meal", "Meal")], string="Pay After:", - default="each", + default="meal", help="Choose when the customer will pay", required=True, ) diff --git a/addons/pos_self_order/models/res_config_settings.py b/addons/pos_self_order/models/res_config_settings.py index 806b683e8c4..f0e4b1d918d 100644 --- a/addons/pos_self_order/models/res_config_settings.py +++ b/addons/pos_self_order/models/res_config_settings.py @@ -16,7 +16,7 @@ class ResConfigSettings(models.TransientModel): pos_self_order_pay_after = fields.Selection( [("each", "Each Order"), ("meal", "Meal")], string="Pay After:", - default="each", + default="meal", help="Choose when the customer will pay", readonly=False, required=True,