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 @@ -