From d0f89b347891c48789d083b2f955f88e0e862e27 Mon Sep 17 00:00:00 2001 From: "Jacky (trj)" Date: Thu, 4 Aug 2022 10:10:00 +0000 Subject: [PATCH] [IMP] point_of_sale,pos_*: back to basics Overall improvement and simplification (along with some minor re-alignment) for the point of sale. POS Dashboard: - added a new check for companies that hasn't installed any chart of account, this gives them a better understanding of the real problem. - added a new `menuitem` in the configuration tab that list all the pos configs, this gives the user a way to access the sub-config of a pos (and indirectly give access to its whole settings) Opening/closing control: - the `MoneyDetailsPopup` is now opened through the `showPopup` method, we're no longer using the css to show/hide. - the details of the money (if it exists) is now given to the `MoneyDetailsPopup` component, this allows to have a record data usable of the details and the user doesn't have to start from scratch if he just wants to modify one thing - opening the `MoneyDetailsPopup` no longer resets the cash input, that way the user can just quickly check if everything is correct again without having to recount everything and this won't reset the outcome Customer display: - added "Powered by" before the Odoo logo in order to accentuate that it's from the software used is from Odoo Product screen: - visual improvement of the `EditListInput` component which is mostly used by the lot/serial number, it gives more information to the user and better UX - "Cash out" is now automatically selected upon opening the `CashMovePopup`. No more minus sign (-) in the input amount. This should give better intuitiveness and one less click to do - no more clear search button shown if the search input is empty - removed the hover and clickable effect on `CashierName` component in the normal point_of_sale since nothing happens when we click on it - increased the width of the click zone with a label in the `ProductConfiguratorPopup` which makes it easier to select a radio input - the `PrintBillButton`, `SplitBillButton`, `RewardButton` and `ResetProgramsButton` are now disabled when nothing happens - the `textarea` of the `TextAreaPopup` component has been fixed and is no longer resizable Partner list screen: - fixed phone, mobile and mail icons the contact area - increased details button size Floor screen: - when idle, no longer redirect to the floor screen if there's an open error popup. An error popup is opened for a specific reason, we shouldn't redirect while having this open or automatically close it. Other kinds of popup will be closed since those are issued by the user that went away. task-2916197 closes odoo/odoo#97803 Signed-off-by: Masereel Pierre --- addons/point_of_sale/__manifest__.py | 1 + addons/point_of_sale/models/pos_config.py | 7 + .../src/css/customer_facing_display.css | 7 + .../static/src/css/popups/common.css | 13 +- .../src/css/popups/money_details_popup.css | 4 + .../static/src/css/popups/text_area_popup.css | 16 ++ addons/point_of_sale/static/src/js/Chrome.js | 2 +- .../src/js/ChromeWidgets/CashierName.js | 3 + .../static/src/js/Popups/CashMovePopup.js | 48 +---- .../static/src/js/Popups/CashOpeningPopup.js | 29 ++- .../static/src/js/Popups/ClosePosPopup.js | 36 ++-- .../static/src/js/Popups/EditListInput.js | 12 +- .../static/src/js/Popups/EditListPopup.js | 20 +- .../static/src/js/Popups/MoneyDetailsPopup.js | 31 +-- .../static/src/js/Popups/NumberPopup.js | 4 +- .../static/src/js/Popups/TextAreaPopup.js | 4 +- .../static/src/js/Popups/TextInputPopup.js | 4 +- .../js/Screens/ProductScreen/OrderWidget.js | 1 + .../js/Screens/ProductScreen/ProductItem.js | 20 -- .../js/Screens/ProductScreen/ProductScreen.js | 1 + .../ProductsWidgetControlPanel.js | 18 +- addons/point_of_sale/static/src/scss/pos.scss | 77 ++++++-- .../src/xml/ChromeWidgets/CashierName.xml | 2 +- .../CustomerFacingDisplayOrder.xml | 4 +- .../static/src/xml/Popups/CashMovePopup.xml | 56 +++--- .../src/xml/Popups/CashOpeningPopup.xml | 42 ++-- .../static/src/xml/Popups/ClosePosPopup.xml | 182 +++++++++--------- .../static/src/xml/Popups/EditListInput.xml | 6 +- .../static/src/xml/Popups/EditListPopup.xml | 37 ++-- .../src/xml/Popups/MoneyDetailsPopup.xml | 6 +- .../static/src/xml/Popups/NumberPopup.xml | 6 +- .../xml/Popups/ProductConfiguratorPopup.xml | 19 +- .../static/src/xml/Popups/TextAreaPopup.xml | 32 ++- .../static/src/xml/Popups/TextInputPopup.xml | 10 +- .../Screens/PartnerListScreen/PartnerLine.xml | 8 +- .../ProductsWidgetControlPanel.xml | 4 +- .../helpers/ProductConfiguratorTourMethods.js | 2 +- .../point_of_sale/views/pos_config_view.xml | 19 ++ addons/pos_hr/static/src/js/CashierName.js | 8 + .../js/ControlButtons/ResetProgramsButton.js | 6 +- .../src/js/ControlButtons/RewardButton.js | 10 +- addons/pos_loyalty/static/src/js/Loyalty.js | 5 + .../ControlButtons/ResetProgramsButton.xml | 2 +- .../src/xml/ControlButtons/RewardButton.xml | 2 +- addons/pos_restaurant/static/src/js/Chrome.js | 28 +-- .../src/js/Popups/PosPopupController.js | 25 +++ .../ControlButtons/PrintBillButton.js | 14 +- .../ControlButtons/SplitBillButton.js | 9 +- .../ControlButtons/PrintBillButton.xml | 2 +- .../ControlButtons/SplitBillButton.xml | 2 +- 50 files changed, 489 insertions(+), 417 deletions(-) create mode 100644 addons/point_of_sale/static/src/css/popups/text_area_popup.css create mode 100644 addons/pos_restaurant/static/src/js/Popups/PosPopupController.js diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index d866642fcd0..89ef744e40a 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -116,6 +116,7 @@ 'point_of_sale/static/src/css/popups/cash_opening_popup.css', 'point_of_sale/static/src/css/popups/closing_pos_popup.css', 'point_of_sale/static/src/css/popups/money_details_popup.css', + 'point_of_sale/static/src/css/popups/text_area_popup.css', 'web/static/src/legacy/scss/fontawesome_overridden.scss', # Here includes the lib and POS UI assets. diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 0c4d8f69c01..e96f3d124f3 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -337,6 +337,12 @@ class PosConfig(models.Model): if any(pricelist.company_id.id not in [False, config.company_id.id] for pricelist in config.available_pricelist_ids): raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale.")) + def _check_company_has_template(self): + self.ensure_one() + if not self.company_has_template: + raise ValidationError(_("No chart of account configured, go to the \"configuration / settings\" menu, and " + "install one from the Invoicing tab.")) + def name_get(self): result = [] for config in self: @@ -472,6 +478,7 @@ class PosConfig(models.Model): } def _check_before_creating_new_session(self): + self._check_company_has_template() self._check_pricelists() self._check_company_journal() self._check_company_invoice_journal() diff --git a/addons/point_of_sale/static/src/css/customer_facing_display.css b/addons/point_of_sale/static/src/css/customer_facing_display.css index 95c1b1c54ac..1e059e60561 100644 --- a/addons/point_of_sale/static/src/css/customer_facing_display.css +++ b/addons/point_of_sale/static/src/css/customer_facing_display.css @@ -267,6 +267,13 @@ body .pos-customer_facing_display .pos-payment_info .pos-payment_info_details .p background-repeat: no-repeat; height: 1.5vw; margin-top: 10%; + display: flex; + justify-content: end; + align-items: flex-end; +} +body .pos-customer_facing_display .pos-payment_info .pos-payment_info_details .pos-odoo_logo_container span { + margin-right: 5vw; + font-size: 1vw; } @media all and (orientation: portrait) { body .pos-customer_facing_display { diff --git a/addons/point_of_sale/static/src/css/popups/common.css b/addons/point_of_sale/static/src/css/popups/common.css index 9d206dd5ba6..1b418797eec 100644 --- a/addons/point_of_sale/static/src/css/popups/common.css +++ b/addons/point_of_sale/static/src/css/popups/common.css @@ -1,6 +1,6 @@ /* Input style used in cash control popups */ .pos .popup .pos-input { - text-align: center; + text-align: right; font-size: 18px; color: #555555; background: none; @@ -19,3 +19,14 @@ border-color: blue; font-weight: bold; } + +.pos .popup .footer-flex { + display: flex; + justify-content: space-between; + padding: 0 10px; + box-sizing: border-box; +} + +.pos .popup .footer-flex .button { + margin-right: 0; +} diff --git a/addons/point_of_sale/static/src/css/popups/money_details_popup.css b/addons/point_of_sale/static/src/css/popups/money_details_popup.css index 706ff2ffbca..ef31f0a60ca 100644 --- a/addons/point_of_sale/static/src/css/popups/money_details_popup.css +++ b/addons/point_of_sale/static/src/css/popups/money_details_popup.css @@ -34,3 +34,7 @@ .pos .money-details .total-section { font-weight: bold; } + +.pos .money-details .footer .button { + margin-right: unset; +} diff --git a/addons/point_of_sale/static/src/css/popups/text_area_popup.css b/addons/point_of_sale/static/src/css/popups/text_area_popup.css new file mode 100644 index 00000000000..0fa24109316 --- /dev/null +++ b/addons/point_of_sale/static/src/css/popups/text_area_popup.css @@ -0,0 +1,16 @@ +.popup-textarea textarea { + resize: none; + height: 110px; + width: 450px; +} + +.popup-textarea footer { + display: flex; + justify-content: space-between; + padding: 0 10px; + box-sizing: border-box; +} + +.pos .popup.popup-textarea footer .button { + margin-right: 0; +} diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index ff7d3056997..ad85ffd7499 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -243,7 +243,7 @@ odoo.define('point_of_sale.Chrome', function(require) { openCashControl() { if (this.shouldShowCashControl()) { - this.showPopup('CashOpeningPopup'); + this.showPopup('CashOpeningPopup', { keepBehind: true }); } } diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js b/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js index 57aae62b04e..ba9489ae28b 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js @@ -15,6 +15,9 @@ odoo.define('point_of_sale.CashierName', function(require) { const id = user_id ? user_id : -1; return `/web/image/res.users/${id}/avatar_128`; } + get cssClass() { + return {'not-clickable': true}; + } } CashierName.template = 'CashierName'; diff --git a/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js b/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js index c428fe1ff29..407c37625b6 100644 --- a/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js +++ b/addons/point_of_sale/static/src/js/Popups/CashMovePopup.js @@ -3,67 +3,44 @@ odoo.define('point_of_sale.CashMovePopup', function (require) { const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); const Registries = require('point_of_sale.Registries'); - const { _t } = require('web.core'); const { parse } = require('web.field_utils'); - const { useRef, useState } = owl; + const { useRef, useState, onMounted } = owl; class CashMovePopup extends AbstractAwaitablePopup { setup() { super.setup(); this.state = useState({ - inputType: '', // '' | 'in' | 'out' + inputType: 'out', // '' | 'in' | 'out' inputAmount: '', inputReason: '', - inputHasError: false, + errorMessage: '', }); this.inputAmountRef = useRef('input-amount-ref'); + onMounted(() => this.inputAmountRef.el.focus()); } confirm() { try { parse.float(this.state.inputAmount); } catch { - this.state.inputHasError = true; - this.errorMessage = this.env._t('Invalid amount'); - return; - } - if (this.state.inputType == '') { - this.state.inputHasError = true; - this.errorMessage = this.env._t('Select either Cash In or Cash Out before confirming.'); - return; - } - if (this.state.inputType === 'out' && this.state.inputAmount > 0) { - this.state.inputHasError = true; - this.errorMessage = this.env._t('Insert a negative amount with the Cash Out option.'); - return; - } - if (this.state.inputType === 'in' && this.state.inputAmount < 0) { - this.state.inputHasError = true; - this.errorMessage = this.env._t('Insert a positive amount with the Cash In option.'); + this.state.errorMessage = this.env._t('Invalid amount'); return; } if (this.state.inputAmount < 0) { - this.state.inputAmount = this.state.inputAmount.substring(1); + this.state.errorMessage = this.env._t('Insert a positive amount'); + return; } return super.confirm(); } _onAmountKeypress(event) { - if (event.key === '-') { + if (['-', '+'].includes(event.key)) { event.preventDefault(); - this.state.inputAmount = this.state.inputType === 'out' ? this.state.inputAmount.substring(1) : `-${this.state.inputAmount}`; - this.state.inputType = this.state.inputType === 'out' ? 'in' : 'out'; } } onClickButton(type) { - let amount = this.state.inputAmount; - if (type === 'in') { - this.state.inputAmount = amount.charAt(0) === '-' ? amount.substring(1) : amount; - } else { - this.state.inputAmount = amount.charAt(0) === '-' ? amount : `-${amount}`; - } this.state.inputType = type; - this.state.inputHasError = false; - this.inputAmountRef.el && this.inputAmountRef.el.focus(); + this.state.errorMessage = ''; + this.inputAmountRef.el.focus(); } getPayload() { return { @@ -74,11 +51,6 @@ odoo.define('point_of_sale.CashMovePopup', function (require) { } } CashMovePopup.template = 'point_of_sale.CashMovePopup'; - CashMovePopup.defaultProps = { - cancelText: _t('Cancel'), - title: _t('Cash In/Out'), - }; - Registries.Component.add(CashMovePopup); return CashMovePopup; diff --git a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js index 07e4b7c6641..8b289b6d70e 100644 --- a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js @@ -10,10 +10,10 @@ odoo.define('point_of_sale.CashOpeningPopup', function(require) { setup() { super.setup(); this.manualInputCashCount = null; + this.moneyDetails = null; this.state = useState({ notes: "", openingCash: this.env.pos.pos_session.cash_register_balance_start || 0, - displayMoneyDetailsPopup: false, }); } //@override @@ -27,25 +27,22 @@ odoo.define('point_of_sale.CashOpeningPopup', function(require) { }); super.confirm(); } - openDetailsPopup() { - this.state.openingCash = 0; - this.state.notes = ""; - this.state.displayMoneyDetailsPopup = true; - } - closeDetailsPopup() { - this.state.displayMoneyDetailsPopup = false; - } - updateCashOpening({ total, moneyDetailsNotes }) { - this.state.openingCash = total; - if (moneyDetailsNotes) { - this.state.notes = moneyDetailsNotes; + async openDetailsPopup() { + const { confirmed, payload } = await this.showPopup('MoneyDetailsPopup', { + moneyDetails: this.moneyDetails, total: this.manualInputCashCount ? 0 : this.state.openingCash }); + if (confirmed) { + const { total, moneyDetails, moneyDetailsNotes } = payload; + this.state.openingCash = total; + if (moneyDetailsNotes) { + this.state.notes = moneyDetailsNotes; + } + this.manualInputCashCount = false; + this.moneyDetails = moneyDetails; } - this.manualInputCashCount = false; - this.closeDetailsPopup(); } handleInputChange() { this.manualInputCashCount = true; - this.state.notes = ""; + this.moneyDetails = null; } } diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js index af424d46040..c86aef2683f 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -50,14 +50,22 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { super.cancel(); } } - openDetailsPopup() { - this.state.payments[this.defaultCashDetails.id].counted = 0; - this.state.payments[this.defaultCashDetails.id].difference = -this.defaultCashDetails.amount; - this.state.notes = ""; - this.state.displayMoneyDetailsPopup = true; - } - closeDetailsPopup() { - this.state.displayMoneyDetailsPopup = false; + async openDetailsPopup() { + const { confirmed, payload } = await this.showPopup('MoneyDetailsPopup', { + moneyDetails: this.moneyDetails, + total: this.manualInputCashCount ? 0 : this.state.payments[this.defaultCashDetails.id].counted, + }); + if (confirmed) { + const { total, moneyDetailsNotes, moneyDetails } = payload; + this.state.payments[this.defaultCashDetails.id].counted = total; + this.state.payments[this.defaultCashDetails.id].difference = + this.env.pos.round_decimals_currency(this.state.payments[[this.defaultCashDetails.id]].counted - this.defaultCashDetails.amount); + if (moneyDetailsNotes) { + this.state.notes = moneyDetailsNotes; + } + this.manualInputCashCount = false; + this.moneyDetails = moneyDetails; + } } async downloadSalesReport() { await this.env.legacyActionManager.do_action('point_of_sale.sale_details_report', { @@ -70,6 +78,7 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { let expectedAmount; if (paymentId === this.defaultCashDetails.id) { this.manualInputCashCount = true; + this.moneyDetails = null; this.state.notes = ''; expectedAmount = this.defaultCashDetails.amount; } else { @@ -78,17 +87,6 @@ odoo.define('point_of_sale.ClosePosPopup', function(require) { this.state.payments[paymentId].difference = this.env.pos.round_decimals_currency(this.state.payments[paymentId].counted - expectedAmount); } - updateCountedCash({ total, moneyDetailsNotes, moneyDetails }) { - this.state.payments[this.defaultCashDetails.id].counted = total; - this.state.payments[this.defaultCashDetails.id].difference = - this.env.pos.round_decimals_currency(this.state.payments[[this.defaultCashDetails.id]].counted - this.defaultCashDetails.amount); - if (moneyDetailsNotes) { - this.state.notes = moneyDetailsNotes; - } - this.manualInputCashCount = false; - this.moneyDetails = moneyDetails; - this.closeDetailsPopup(); - } hasDifference() { return Object.entries(this.state.payments).find(pm => pm[1].difference != 0); } diff --git a/addons/point_of_sale/static/src/js/Popups/EditListInput.js b/addons/point_of_sale/static/src/js/Popups/EditListInput.js index 09b39f212c9..e4e7769685f 100644 --- a/addons/point_of_sale/static/src/js/Popups/EditListInput.js +++ b/addons/point_of_sale/static/src/js/Popups/EditListInput.js @@ -4,12 +4,22 @@ odoo.define('point_of_sale.EditListInput', function(require) { const PosComponent = require('point_of_sale.PosComponent'); const Registries = require('point_of_sale.Registries'); + /** + * props { + * createNewItem: callback, + * removeItem: callback, + * item: object, + * } + */ class EditListInput extends PosComponent { onKeyup(event) { if (event.key === "Enter" && event.target.value.trim() !== '') { - this.trigger('create-new-item'); + this.props.createNewItem(); } } + onInput(event) { + this.props.onInputChange(this.props.item._id, event.target.value); + } } EditListInput.template = 'EditListInput'; diff --git a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js index 3d5f563a330..e5fc46198fa 100644 --- a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js @@ -67,16 +67,18 @@ odoo.define('point_of_sale.EditListPopup', function(require) { // Put _id for each item. It will serve as unique identifier of each item. return array.map((item) => Object.assign({}, { _id: this._nextId() }, typeof item === 'object'? item: { 'text': item})); } - removeItem(event) { - const itemToRemove = event.detail; + _hasMoreThanOneItem() { + return this.state.array.length > 1; + } + removeItem(itemId) { this.state.array.splice( - this.state.array.findIndex(item => item._id == itemToRemove._id), + this.state.array.findIndex(item => item._id == itemId), 1 ); - // We keep a minimum of one empty item in the popup. - if (this.state.array.length === 0) { - this.state.array.push(this._emptyItem()); - } + } + onInputChange(itemId, text) { + const item = this.state.array.find(elem => elem._id === itemId); + item.text = text; } createNewItem() { if (this.props.isSingleItem) return; @@ -95,8 +97,8 @@ odoo.define('point_of_sale.EditListPopup', function(require) { } EditListPopup.template = 'EditListPopup'; EditListPopup.defaultProps = { - confirmText: _lt('Ok'), - cancelText: _lt('Cancel'), + confirmText: _lt('Add'), + cancelText: _lt('Discard'), array: [], isSingleItem: false, }; diff --git a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js index 26aba2664d5..9a7750d3ca0 100644 --- a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js @@ -1,27 +1,20 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) { 'use strict'; - const PosComponent = require('point_of_sale.PosComponent'); + const AbstractAwaitablePopup = require('point_of_sale.AbstractAwaitablePopup'); const Registries = require('point_of_sale.Registries'); const { useState } = owl; - /** - * Even if this component has a "confirm and cancel"-like buttons, this should not be an AbstractAwaitablePopup. - * We currently cannot show two popups at the same time, what we do is mount this component with its parent - * and hide it with some css. The confirm button will just trigger an event to the parent. - */ - class MoneyDetailsPopup extends PosComponent { + class MoneyDetailsPopup extends AbstractAwaitablePopup{ setup() { super.setup(); this.currency = this.env.pos.currency; this.state = useState({ - moneyDetails: Object.fromEntries(this.env.pos.bills.map(bill => ([bill.value, 0]))), - total: 0, + moneyDetails: this.props.moneyDetails ? {...this.props.moneyDetails} : + Object.fromEntries(this.env.pos.bills.map(bill => ([bill.value, 0]))), + total: this.props.total ? this.props.total : 0, }); - if (this.props.manualInputCashCount) { - this.reset(); - } } get firstHalfMoneyDetails() { const moneyDetailsKeys = Object.keys(this.state.moneyDetails).sort((a, b) => a - b); @@ -35,23 +28,15 @@ odoo.define('point_of_sale.MoneyDetailsPopup', function(require) { let total = Object.entries(this.state.moneyDetails).reduce((total, money) => total + money[0] * money[1], 0); this.state.total = this.env.pos.round_decimals_currency(total); } - confirm() { + //@override + async getPayload() { let moneyDetailsNotes = this.state.total ? 'Money details: \n' : null; this.env.pos.bills.forEach(bill => { if (this.state.moneyDetails[bill.value]) { moneyDetailsNotes += ` - ${this.state.moneyDetails[bill.value]} x ${this.env.pos.format_currency(bill.value)}\n`; } }) - const payload = { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails } }; - this.props.onConfirm(payload); - } - reset() { - for (let key in this.state.moneyDetails) { this.state.moneyDetails[key] = 0 } - this.state.total = 0; - } - discard() { - this.reset(); - this.props.onDiscard(); + return { total: this.state.total, moneyDetailsNotes, moneyDetails: { ...this.state.moneyDetails } }; } } diff --git a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js index d4e6a44626f..6575cd99ee8 100644 --- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js @@ -65,8 +65,8 @@ odoo.define('point_of_sale.NumberPopup', function(require) { } NumberPopup.template = 'NumberPopup'; NumberPopup.defaultProps = { - confirmText: _t('Ok'), - cancelText: _t('Cancel'), + confirmText: _t('Confirm'), + cancelText: _t('Discard'), title: _t('Confirm ?'), body: '', cheap: false, diff --git a/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js b/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js index 4c271288085..10e1efe376a 100644 --- a/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js @@ -30,8 +30,8 @@ odoo.define('point_of_sale.TextAreaPopup', function(require) { } TextAreaPopup.template = 'TextAreaPopup'; TextAreaPopup.defaultProps = { - confirmText: _lt('Ok'), - cancelText: _lt('Cancel'), + confirmText: _lt('Add'), + cancelText: _lt('Discard'), title: '', body: '', }; diff --git a/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js b/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js index f6c9d28f819..d7188c69f60 100644 --- a/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js @@ -24,8 +24,8 @@ odoo.define('point_of_sale.TextInputPopup', function(require) { } TextInputPopup.template = 'TextInputPopup'; TextInputPopup.defaultProps = { - confirmText: _lt('Ok'), - cancelText: _lt('Cancel'), + confirmText: _lt('Confirm'), + cancelText: _lt('Discard'), title: '', body: '', startingValue: '', diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js index 4b460cc752a..c6b241fc878 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderWidget.js @@ -46,6 +46,7 @@ odoo.define('point_of_sale.OrderWidget', function(require) { const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot); const { confirmed, payload } = await this.showPopup('EditListPopup', { title: this.env._t('Lot/Serial Number(s) Required'), + name: orderline.product.display_name, isSingleItem: isAllowOnlyOneLot, array: packLotLinesToEdit, }); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js index 765a15c03c2..17f9f2734b8 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductItem.js @@ -3,8 +3,6 @@ odoo.define('point_of_sale.ProductItem', function(require) { const PosComponent = require('point_of_sale.PosComponent'); const Registries = require('point_of_sale.Registries'); - const { ConnectionLostError, ConnectionAbortedError } = require('@web/core/network/rpc_service') - const { identifyError } = require('point_of_sale.utils'); class ProductItem extends PosComponent { /** @@ -42,24 +40,6 @@ odoo.define('point_of_sale.ProductItem', function(require) { return formattedUnitPrice; } } - async onProductInfoClick() { - try { - const info = await this.env.pos.getProductInfo(this.props.product, 1); - this.showPopup('ProductInfoPopup', { info: info , product: this.props.product }); - } catch (e) { - if (identifyError(e) instanceof ConnectionLostError||ConnectionAbortedError) { - this.showPopup('ErrorPopup', { - title: this.env._t('OfflineErrorPopup'), - body: this.env._t('Cannot access product information screen if offline.'), - }); - } else { - this.showPopup('ErrorPopup', { - title: this.env._t('Unknown error'), - body: this.env._t('An unknown error prevents us from loading product information.'), - }); - } - } - } } ProductItem.template = 'ProductItem'; diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js index 6afa8e13e58..1cba86074e5 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -98,6 +98,7 @@ odoo.define('point_of_sale.ProductScreen', function(require) { } const { confirmed, payload } = await this.showPopup('EditListPopup', { title: this.env._t('Lot/Serial Number(s) Required'), + name: product.display_name, isSingleItem: isAllowOnlyOneLot, array: packLotLinesToEdit, }); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js index e3fd4d2876c..6dd5293dd81 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidgetControlPanel.js @@ -7,13 +7,13 @@ odoo.define('point_of_sale.ProductsWidgetControlPanel', function(require) { const Registries = require('point_of_sale.Registries'); const { debounce } = require("@web/core/utils/timing"); - const { onMounted, onWillUnmount, useRef } = owl; + const { onMounted, onWillUnmount } = owl; class ProductsWidgetControlPanel extends PosComponent { setup() { super.setup(); - this.searchWordInput = useRef('search-word-input-product'); this.updateSearch = debounce(this.updateSearch, 100); + this.state = { searchInput: '' }; onMounted(() => { this.env.posbus.on('search-product-from-info-popup', this, this.searchProductFromInfo) @@ -26,32 +26,32 @@ odoo.define('point_of_sale.ProductsWidgetControlPanel', function(require) { }); } _clearSearch() { - this.searchWordInput.el.value = ''; + this.state.searchInput = ''; this.trigger('clear-search'); } get displayCategImages() { return Object.values(this.env.pos.db.category_by_id).some(categ => categ.has_image) && !this.env.isMobile; } updateSearch(event) { - this.trigger('update-search', event.target.value); + this.trigger('update-search', this.state.searchInput); if (event.key === 'Enter') { this._onPressEnterKey() } } async _onPressEnterKey() { - if (!this.searchWordInput.el.value) return; + if (!this.state.searchInput) return; if (!this.env.pos.isEveryProductLoaded) { const result = await this.loadProductFromDB(); this.showNotification( _.str.sprintf(this.env._t('%s product(s) found for "%s".'), result.length, - this.searchWordInput.el.value) + this.state.searchInput) , 3000); if (!result.length) this._clearSearch(); } } searchProductFromInfo(productName) { - this.searchWordInput.el.value = productName; + this.state.searchInput = productName; this.trigger('switch-category', 0); this.trigger('update-search', productName); } @@ -59,14 +59,14 @@ odoo.define('point_of_sale.ProductsWidgetControlPanel', function(require) { this.trigger('toggle-mobile-searchbar'); } async loadProductFromDB() { - if(!this.searchWordInput.el.value) + if(!this.state.searchInput) return; try { let ProductIds = await this.rpc({ model: 'product.product', method: 'search', - args: [['&', ['name', 'ilike', this.searchWordInput.el.value + "%"], ['available_in_pos', '=', true]]], + args: [['&', ['name', 'ilike', this.state.searchInput + "%"], ['available_in_pos', '=', true]]], context: this.env.session.user_context, }); if(ProductIds.length) { diff --git a/addons/point_of_sale/static/src/scss/pos.scss b/addons/point_of_sale/static/src/scss/pos.scss index 5bd1b999f22..8a1a22c343a 100644 --- a/addons/point_of_sale/static/src/scss/pos.scss +++ b/addons/point_of_sale/static/src/scss/pos.scss @@ -673,6 +673,7 @@ td { opacity: 0.5; cursor: default; color: inherit; + pointer-events: none; } /* ********* The actionpad (payment, set customer) ********* */ @@ -1906,12 +1907,12 @@ td { } .pos .partnerlist-screen .partner-list th, .pos .partnerlist-screen .partner-list td { - padding: 12px; + padding: 15px 8px; } .pos .partnerlist-screen .fa.fa.fa-phone, -.pos .partnerlist-screen .fa.fa-mobile, -.pos .partnerlist-screen .fa.fa-paper-plane-o { +.pos .partnerlist-screen .fa.fa-paper-plane-o, +.pos .partnerlist-screen .fa.fa-mobile{ margin-right: 4px; } @@ -1977,9 +1978,11 @@ td { width: 30px; opacity: .75; } +.pos .edit-partner-button-cell { + vertical-align: middle; +} .pos .edit-partner-button { - margin-top: 6px; - color: black; + height: 30px; font-weight: bold; } .pos .partner-list tr.partner-line.highlight{ @@ -2638,13 +2641,6 @@ td { color: $primary; } -.pos .popup-textarea textarea { - width: 100%; - box-sizing: border-box; -} -.pos .popup-textarea .popup-textarea-wrap { - padding: map-get($spacers, 2); -} .pos .popup .button.dont-show-again { width: 130px; } @@ -2698,13 +2694,35 @@ td { box-sizing: border-box; width: 80%; } -.pos .popup .list-lines{ - overflow: auto; - height: 250px; - margin: 10px; +.pos .edit-list-popup header { + display: flex; + flex-direction: column; } -.pos .popup .list-line-input { - margin: 3px; +.pos .edit-list-popup header .sub-title { + font-weight: normal; + margin-top: 5px +} +.pos .edit-list-popup main { + margin: 10px; + padding-left: 40px; + text-align: left; +} +.pos .edit-list-popup .list-lines { + overflow: auto; + display: flex; + flex-direction: column; + height: 250px; +} + +.pos .edit-list-popup .list-lines span { + font-size: 16px; + padding-right: 40px; + margin-left: 3px; +} + +.pos .edit-list-popup .list-line-input { + margin: 3px 10px 3px 3px; + width: 90% } .pos .popup-number .popup-input { @@ -2774,6 +2792,9 @@ td { { margin-bottom: 20px; } +.pos .popup-textinput main { + margin-bottom: 20px; +} .pos .popup-numpad .input-button, .pos .popup-numpad .mode-button { background: none; @@ -3010,6 +3031,20 @@ td { grid-column: 1; word-break: break-word; max-width: 300px; + position: relative +} + +.pos .product_configurator_attributes .configurator_radio .attribute-name-cell label { + position: absolute; + top: 0; + left: 0; + width: 80vw; + max-width: 600px; + height: 100%; +} + +.pos .product_configurator_attributes .configurator_radio .attribute-name-cell label:hover { + cursor: pointer; } .pos .product_configurator_attributes .configurator_radio .price-extra-cell { @@ -3019,6 +3054,7 @@ td { .pos .product_configurator_attributes .configurator_radio input[type='radio'] { box-shadow: none; margin-right: 0.5em; + cursor: pointer; } .pos .product_configurator_attributes .configurator_radio .price_extra { @@ -3032,7 +3068,8 @@ td { .pos .product_configurator_attributes .configurator_radio .custom_value { margin-left: 0.5em; height: 1.3em; - + position: relative; + z-index: 1; } /** Selector attribute **/ @@ -3727,7 +3764,7 @@ td { font-size: 17px; } -.pos .pos-topheader .status-buttons > div:hover { +.pos .pos-topheader .status-buttons > div:not(.not-clickable):hover { background: rgba(0, 0, 0, .08); } diff --git a/addons/point_of_sale/static/src/xml/ChromeWidgets/CashierName.xml b/addons/point_of_sale/static/src/xml/ChromeWidgets/CashierName.xml index 25864a52cde..33f45ac1476 100644 --- a/addons/point_of_sale/static/src/xml/ChromeWidgets/CashierName.xml +++ b/addons/point_of_sale/static/src/xml/ChromeWidgets/CashierName.xml @@ -2,7 +2,7 @@ -
+
diff --git a/addons/point_of_sale/static/src/xml/CustomerFacingDisplay/CustomerFacingDisplayOrder.xml b/addons/point_of_sale/static/src/xml/CustomerFacingDisplay/CustomerFacingDisplayOrder.xml index 84f7e85242b..b307b6c7648 100644 --- a/addons/point_of_sale/static/src/xml/CustomerFacingDisplay/CustomerFacingDisplayOrder.xml +++ b/addons/point_of_sale/static/src/xml/CustomerFacingDisplay/CustomerFacingDisplayOrder.xml @@ -41,7 +41,9 @@ -
+
+ powered by +
diff --git a/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml b/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml index 80765968fe4..0eb6c7c6e17 100644 --- a/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml +++ b/addons/point_of_sale/static/src/xml/Popups/CashMovePopup.xml @@ -2,36 +2,36 @@ - diff --git a/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml b/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml index 5003319956b..ee14ff9830a 100644 --- a/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml +++ b/addons/point_of_sale/static/src/xml/Popups/CashOpeningPopup.xml @@ -1,31 +1,25 @@ -