From c7fbae7a343dd90f8449ed94530f1fa2e6070690 Mon Sep 17 00:00:00 2001 From: "David Monnom (moda)" Date: Wed, 8 Feb 2023 14:55:35 +0000 Subject: [PATCH] [REF] point_of_sale, pos_*: remove usage of useListener *:point_of_sale,pos_ayden,pos_discount,pos_hr,pos_loyalty,pos_mercury, pos_restaurant,pos_sale,pos_six Previously, the point of sale and related modules made heavy usage of `useListener`. This is because the pos was converted early to owl, and in owl 1, using events to communicate between components was encouraged. In owl 2, the decision was made to stop encouraging this way of communicating between components, because it encourages communication accross many layers of components, and is also hard to debug. In addition, in owl 2, because components can have 0 or more than node, including non HTMLElement nodes like text or comment nodes, owl removed the implementation of useListener, as useListener relies on the presence and unicity of a root HTMLElement per component on which we can attach event listeners. Another problem is that events rely on the fact that the components are in the DOM so that the event can propagate, which can lead to issues in some cases and in particular, in the PoS, if you load the point of sale and switch tabs, some things will be broken because we are triggering events, but there have not been any animation frame fired in the tab because it's not focussed, and so owl has not mounted the component yet. When migrating odoo to owl 2, we introduced a shim for this.el and useListener in LegacyComponent, with the hope to remove it as soon as possible. This commit removes all usage of useListener from the pos modules for those reasons. In some cases, useListener was incorrectly used intstead of t-on, and the event was triggered and handled by the same component, in other cases, a simple callback could be passed to a child component. In a few cases, a method had to be implemented on the store as we wanted this method to be available everywhere. closes odoo/odoo#112219 Related: odoo/enterprise#36888 Signed-off-by: Samuel Degueldre --- .../static/src/app/navbar/navbar.js | 4 +- .../static/src/app/number_buffer_service.js | 24 +- .../static/src/app/popup/popup_service.js | 7 +- .../point_of_sale/static/src/app/pos_store.js | 212 +++++++++++++++++- addons/point_of_sale/static/src/js/Chrome.js | 100 +-------- .../src/js/ChromeWidgets/CashierName.js | 4 +- .../CustomerFacingDisplayButton.js | 6 +- .../src/js/ChromeWidgets/HeaderButton.js | 4 +- .../src/js/ChromeWidgets/ProxyStatus.js | 6 +- .../src/js/ChromeWidgets/SaleDetailsButton.js | 5 +- .../src/js/ChromeWidgets/SyncNotification.js | 4 +- .../src/js/Misc/AbstractReceiptScreen.js | 6 +- .../static/src/js/Misc/CurrencyAmount.js | 4 +- .../src/js/Misc/IndependentToOrderScreen.js | 4 +- .../static/src/js/Misc/MobileOrderWidget.js | 4 +- .../static/src/js/Misc/SearchBar.js | 19 +- .../src/js/Popups/AbstractAwaitablePopup.js | 5 +- .../static/src/js/Popups/CashOpeningPopup.js | 4 +- .../static/src/js/Popups/ClosePosPopup.js | 85 ++----- .../static/src/js/Popups/DatePickerPopup.js | 20 +- .../static/src/js/Popups/EditListInput.js | 4 +- .../static/src/js/Popups/EditListPopup.js | 4 +- .../src/js/Popups/ErrorTracebackPopup.js | 6 +- .../static/src/js/Popups/MoneyDetailsPopup.js | 4 +- .../static/src/js/Popups/NumberPopup.js | 14 +- .../src/js/Popups/ProductConfiguratorPopup.js | 9 +- .../static/src/js/Popups/SelectionPopup.js | 3 +- .../static/src/js/Popups/TextAreaPopup.js | 3 +- .../static/src/js/Popups/TextInputPopup.js | 3 +- .../point_of_sale/static/src/js/PosContext.js | 3 +- .../PartnerListScreen/PartnerDetailsEdit.js | 8 +- .../Screens/PartnerListScreen/PartnerLine.js | 4 +- .../PartnerListScreen/PartnerListScreen.js | 15 +- .../PaymentScreen/PSNumpadInputButton.js | 9 +- .../js/Screens/PaymentScreen/PaymentScreen.js | 46 ++-- .../PaymentScreen/PaymentScreenNumpad.js | 4 +- .../PaymentScreenPaymentLines.js | 4 +- .../PaymentScreen/PaymentScreenStatus.js | 4 +- .../Screens/ProductScreen/ActionpadWidget.js | 11 +- .../Screens/ProductScreen/CategoryButton.js | 4 +- .../OrderlineCustomerNoteButton.js | 7 +- .../ControlButtons/ProductInfoButton.js | 9 +- .../ControlButtons/RefundButton.js | 8 +- .../ControlButtons/SetFiscalPositionButton.js | 9 +- .../ControlButtons/SetPricelistButton.js | 9 +- .../js/Screens/ProductScreen/NumpadWidget.js | 21 +- .../js/Screens/ProductScreen/OrderSummary.js | 4 +- .../js/Screens/ProductScreen/OrderWidget.js | 21 +- .../src/js/Screens/ProductScreen/Orderline.js | 8 +- .../js/Screens/ProductScreen/ProductItem.js | 10 +- .../js/Screens/ProductScreen/ProductScreen.js | 196 ++-------------- .../Screens/ProductScreen/ProductsWidget.js | 25 +-- .../ProductsWidgetControlPanel.js | 16 +- .../js/Screens/ReceiptScreen/OrderReceipt.js | 7 +- .../js/Screens/ReceiptScreen/ReceiptScreen.js | 2 + .../ReceiptScreen/WrappedProductNameLines.js | 4 +- .../src/js/Screens/ScaleScreen/ScaleScreen.js | 6 +- .../ControlButtons/InvoiceButton.js | 17 +- .../ControlButtons/ReprintReceiptButton.js | 8 +- .../js/Screens/TicketScreen/OrderDetails.js | 4 +- .../Screens/TicketScreen/OrderlineDetails.js | 4 +- .../js/Screens/TicketScreen/TicketScreen.js | 79 +++---- .../static/src/js/custom_hooks.js | 4 +- addons/point_of_sale/static/src/js/models.js | 114 +++++++++- .../point_of_sale/static/src/xml/Chrome.xml | 2 +- .../static/src/xml/Misc/MobileOrderWidget.xml | 2 +- .../static/src/xml/Misc/SearchBar.xml | 4 +- .../src/xml/Popups/ErrorTracebackPopup.xml | 2 +- .../xml/Popups/ProductConfiguratorPopup.xml | 2 +- .../PartnerListScreen/PartnerListScreen.xml | 4 +- .../PaymentScreen/PSNumpadInputButton.xml | 2 +- .../Screens/PaymentScreen/PaymentScreen.xml | 15 +- .../PaymentScreenPaymentLines.xml | 26 +-- .../Screens/ProductScreen/ActionpadWidget.xml | 4 +- .../Screens/ProductScreen/CategoryButton.xml | 2 +- .../OrderlineCustomerNoteButton.xml | 2 +- .../ControlButtons/ProductInfoButton.xml | 2 +- .../ControlButtons/RefundButton.xml | 2 +- .../SetFiscalPositionButton.xml | 2 +- .../ControlButtons/SetPricelistButton.xml | 2 +- .../xml/Screens/ProductScreen/OrderWidget.xml | 5 +- .../xml/Screens/ProductScreen/ProductItem.xml | 2 +- .../Screens/ProductScreen/ProductScreen.xml | 18 +- .../Screens/ProductScreen/ProductsWidget.xml | 12 +- .../ProductsWidgetControlPanel.xml | 10 +- .../ControlButtons/InvoiceButton.xml | 2 +- .../ControlButtons/ReprintReceiptButton.xml | 2 +- .../xml/Screens/TicketScreen/OrderDetails.xml | 6 +- .../Screens/TicketScreen/OrderlineDetails.xml | 4 +- .../xml/Screens/TicketScreen/TicketScreen.xml | 42 +++- .../tests/unit/number_buffer_service_tests.js | 10 +- .../tests/unit/popup_controller_tests.js | 6 +- .../pos_adyen/static/src/js/PaymentScreen.js | 2 +- .../static/src/js/DiscountButton.js | 9 +- .../static/src/xml/DiscountButton.xml | 2 +- .../pos_hr/static/src/js/HeaderLockButton.js | 6 +- addons/pos_hr/static/src/js/LoginScreen.js | 4 +- addons/pos_loyalty/__manifest__.py | 1 + .../pos_loyalty/static/src/app/pos_store.js | 195 ++++++++++++++++ .../src/js/ControlButtons/PromoCodeButton.js | 9 +- .../js/ControlButtons/ResetProgramsButton.js | 8 +- .../src/js/ControlButtons/RewardButton.js | 19 +- .../src/js/ControlButtons/eWalletButton.js | 8 +- addons/pos_loyalty/static/src/js/Loyalty.js | 59 ----- .../pos_loyalty/static/src/js/OrderWidget.js | 3 +- .../static/src/js/ProductScreen.js | 138 +----------- .../xml/ControlButtons/PromoCodeButton.xml | 2 +- .../ControlButtons/ResetProgramsButton.xml | 2 +- .../src/xml/ControlButtons/RewardButton.xml | 2 +- .../static/src/js/PaymentScreen.js | 25 +-- .../static/src/js/PaymentTransactionPopup.js | 3 +- .../src/app/navbar/BackToFloorButton.js | 4 +- .../static/src/app/pos_store.js | 3 +- .../static/src/js/Screens/PaymentScreen.js | 17 -- .../ControlButtons/OrderlineNoteButton.js | 9 +- .../ControlButtons/PrintBillButton.js | 8 +- .../ControlButtons/SplitBillButton.js | 8 +- .../ControlButtons/SubmitOrderButton.js | 4 +- .../ControlButtons/TableGuestsButton.js | 9 +- .../ControlButtons/TransferOrderButton.js | 8 +- .../SplitBillScreen/SplitBillScreen.js | 11 +- .../Screens/SplitBillScreen/SplitOrderline.js | 10 +- .../static/src/js/Screens/TicketScreen.js | 10 +- .../static/src/js/Screens/TipScreen.js | 10 +- .../PaymentScreenPaymentLines.xml | 4 +- .../ControlButtons/OrderlineNoteButton.xml | 2 +- .../ControlButtons/PrintBillButton.xml | 2 +- .../ControlButtons/SplitBillButton.xml | 2 +- .../ControlButtons/TableGuestsButton.xml | 2 +- .../ControlButtons/TransferOrderButton.xml | 2 +- .../SplitBillScreen/SplitBillScreen.xml | 5 +- .../SplitBillScreen/SplitOrderline.xml | 2 +- .../static/src/xml/Screens/TipScreen.xml | 2 +- .../MobileSaleOrderManagementScreen.js | 8 +- .../js/OrderManagementScreen/SaleOrderList.js | 11 +- .../SaleOrderManagementControlPanel.js | 16 +- .../SaleOrderManagementScreen.js | 27 +-- .../js/OrderManagementScreen/SaleOrderRow.js | 4 +- .../static/src/js/SetSaleOrderButton.js | 11 +- .../OrderManagementScreen/SaleOrderList.xml | 5 +- .../SaleOrderManagementControlPanel.xml | 8 +- .../SaleOrderManagementScreen.xml | 13 +- .../OrderManagementScreen/SaleOrderRow.xml | 2 +- .../static/src/xml/SetSaleOrderButton.xml | 2 +- addons/pos_six/static/src/js/BalanceButton.js | 4 +- 145 files changed, 1085 insertions(+), 1132 deletions(-) create mode 100644 addons/pos_loyalty/static/src/app/pos_store.js diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.js b/addons/point_of_sale/static/src/app/navbar/navbar.js index 60ffbbf2670..21cf1d283aa 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -1,6 +1,5 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { usePos } from "@point_of_sale/app/pos_hook"; import { useService } from "@web/core/utils/hooks"; @@ -12,8 +11,9 @@ import { SaleDetailsButton } from "@point_of_sale/js/ChromeWidgets/SaleDetailsBu import { SyncNotification } from "@point_of_sale/js/ChromeWidgets/SyncNotification"; import { CashMovePopup } from "./cash_move_popup/cash_move_popup"; import { TicketScreen } from "@point_of_sale/js/Screens/TicketScreen/TicketScreen"; +import { Component } from "@odoo/owl"; -export class Navbar extends LegacyComponent { +export class Navbar extends Component { static template = "point_of_sale.Navbar"; static components = { // FIXME POSREF remove some of these components diff --git a/addons/point_of_sale/static/src/app/number_buffer_service.js b/addons/point_of_sale/static/src/app/number_buffer_service.js index d626f17c714..58e934d4c00 100644 --- a/addons/point_of_sale/static/src/app/number_buffer_service.js +++ b/addons/point_of_sale/static/src/app/number_buffer_service.js @@ -1,12 +1,11 @@ /** @odoo-module */ -import { useListener } from "@web/core/utils/hooks"; import { parse } from "web.field_utils"; import { barcodeService } from "@barcodes/barcode_service"; import { _t } from "web.core"; import { registry } from "@web/core/registry"; +import { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } from "@odoo/owl"; -const { EventBus, onMounted, onWillUnmount, useComponent, useExternalListener } = owl; const INPUT_KEYS = new Set( ["Delete", "Backspace", "+1", "+2", "+5", "+10", "+20", "+50"].concat( "0123456789+-.,".split("") @@ -19,7 +18,6 @@ const getDefaultConfig = () => ({ triggerAtEnter: false, triggerAtEsc: false, triggerAtInput: false, - nonKeyboardInputEvent: false, useWithBarcode: false, }); @@ -124,7 +122,6 @@ class NumberBuffer extends EventBus { * @param {String|null} config.triggerAtEnter Event triggered when 'Enter' key is pressed. * @param {String|null} config.triggerAtEsc Event triggered when 'Esc' key is pressed. * @param {String|null} config.triggerAtInput Event triggered for every accepted input. - * @param {String|null} config.nonKeyboardInputEvent Also listen to a non-keyboard input event * that carries a payload of { key }. The key is checked if it is a valid input. If valid, * the number buffer is modified just as it is modified when a keyboard key is pressed. * @param {Boolean} config.useWithBarcode Whether this buffer is used with barcode. @@ -148,10 +145,6 @@ class NumberBuffer extends EventBus { this.bufferHolderStack.pop(); this._setUp(); }); - // Add listener that accepts non keyboard inputs - if (typeof config.nonKeyboardInputEvent === "string") { - useListener(config.nonKeyboardInputEvent, this._onNonKeyboardInput.bind(this)); - } } get _currentBufferHolder() { return this.bufferHolderStack[this.bufferHolderStack.length - 1]; @@ -172,7 +165,14 @@ class NumberBuffer extends EventBus { _onKeyboardInput(event) { return this._bufferEvents(this._onInput((event) => event.key))(event); } - _onNonKeyboardInput(event) { + sendKey(key) { + const event = new CustomEvent("", { + detail: { + key: key, + }, + }); + Object.defineProperty(event, "target", { value: {} }); + return this._bufferEvents(this._onInput((event) => event.detail.key))(event); } _bufferEvents(handler) { @@ -211,13 +211,13 @@ class NumberBuffer extends EventBus { } _handleInput(key) { if (key === "Enter" && this.config.triggerAtEnter) { - this.component.trigger(this.config.triggerAtEnter, this.state); + this.config.triggerAtEnter(this.state); } else if (key === "Esc" && this.config.triggerAtEsc) { - this.component.trigger(this.config.triggerAtEsc, this.state); + this.config.triggerAtEsc(this.state); } else if (INPUT_KEYS.has(key)) { this._updateBuffer(key); if (this.config.triggerAtInput) { - this.component.trigger(this.config.triggerAtInput, { + this.config.triggerAtInput({ buffer: this.state.buffer, key, }); diff --git a/addons/point_of_sale/static/src/app/popup/popup_service.js b/addons/point_of_sale/static/src/app/popup/popup_service.js index 9f1a8acf5d6..1708869e3f3 100644 --- a/addons/point_of_sale/static/src/app/popup/popup_service.js +++ b/addons/point_of_sale/static/src/app/popup/popup_service.js @@ -1,13 +1,12 @@ /** @odoo-module */ -import { reactive } from "@odoo/owl"; +import { Component, reactive } from "@odoo/owl"; import { registry } from "@web/core/registry"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { usePos } from "@point_of_sale/app/pos_hook"; -export class PopupContainer extends LegacyComponent { +export class PopupContainer extends Component { static template = "point_of_sale.PopupContainer"; setup() { - // FIXME POSREF: remove this after LegacyComponent is removed. + // FIXME POSREF: remove this when Chrome uses the new env this.__owl__.childEnv = usePos().legacyEnv; } } diff --git a/addons/point_of_sale/static/src/app/pos_store.js b/addons/point_of_sale/static/src/app/pos_store.js index a86aab625d7..af27060386f 100644 --- a/addons/point_of_sale/static/src/app/pos_store.js +++ b/addons/point_of_sale/static/src/app/pos_store.js @@ -4,8 +4,13 @@ import { PosGlobalState } from "@point_of_sale/js/models"; import { pos_env as legacyEnv } from "@point_of_sale/js/pos_env"; import { registry } from "@web/core/registry"; +import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { reactive, markRaw } from "@odoo/owl"; import { Reactive } from "@point_of_sale/utils"; +import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers"; +import { ConnectionLostError } from "@web/core/network/rpc_service"; +import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { _t } from "@web/core/l10n/translation"; export class PosStore extends Reactive { /** @type {'LOADING' | 'READY' | 'CLOSING'} */ @@ -17,10 +22,12 @@ export class PosStore extends Reactive { legacyEnv = legacyEnv; globalState = new PosGlobalState({ env: markRaw(legacyEnv) }); - static serviceDependencies = ["popup"]; - constructor({ popup }) { + static serviceDependencies = ["popup", "orm", "number_buffer"]; + constructor({ popup, orm, number_buffer }) { super(); + this.orm = orm; this.popup = popup; + this.numberBuffer = number_buffer; this.setup(); } // use setup instead of constructor because setup can be patched. @@ -40,6 +47,207 @@ export class PosStore extends Reactive { this.showScreen(screenName); } + connect_to_proxy() { + return new Promise((resolve, reject) => { + this.globalState.env.barcode_reader.disconnect_from_proxy(); + this.globalState.loadingSkipButtonIsShown = true; + this.globalState.env.proxy + .autoconnect({ + force_ip: this.globalState.config.proxy_ip || undefined, + progress: function (prog) {}, + }) + .then( + () => { + if (this.globalState.config.iface_scan_via_proxy) { + this.globalState.env.barcode_reader.connect_to_proxy(); + } + resolve(); + }, + (statusText, url) => { + // this should reject so that it can be captured when we wait for pos.ready + // in the chrome component. + // then, if it got really rejected, we can show the error. + if (statusText == "error" && window.location.protocol == "https:") { + reject({ + title: _t("HTTPS connection to IoT Box failed"), + body: _.str.sprintf( + _t( + "Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box." + ), + url + ), + popup: "alert", + }); + } else { + resolve(); + } + } + ); + }); + } + + async closePos() { + // If pos is not properly loaded, we just go back to /web without + // doing anything in the order data. + if (!this.globalState || this.globalState.db.get_orders().length === 0) { + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } + + if (this.globalState.db.get_orders().length) { + // If there are orders in the db left unsynced, we try to sync. + // If sync successful, close without asking. + // Otherwise, ask again saying that some orders are not yet synced. + try { + await this.globalState.push_orders(); + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } catch (error) { + console.warn(error); + const reason = this.globalState.failed + ? _t( + "Some orders could not be submitted to " + + "the server due to configuration errors. " + + "You can exit the Point of Sale, but do " + + "not close the session before the issue " + + "has been resolved." + ) + : _t( + "Some orders could not be submitted to " + + "the server due to internet connection issues. " + + "You can exit the Point of Sale, but do " + + "not close the session before the issue " + + "has been resolved." + ); + const { confirmed } = await this.popup.add(ConfirmPopup, { + title: _t("Offline Orders"), + body: reason, + }); + if (confirmed) { + // FIXME POSREF setting the location prevents the next render, the loading screen never shows + this.globalState.uiState = "CLOSING"; + this.globalState.loadingSkipButtonIsShown = false; + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } + } + } + } + async closeSession(defaultCashCounted, notes, otherPaymentMethods, payments) { + let response; + + if (this.globalState.config.cashControl) { + response = await this.orm.call( + "pos.session", + "post_closing_cash_details"[this.globalState.pos_session.id], + { + counted_cash: defaultCashCounted, + } + ); + + if (!response.successful) { + return this.handleClosingError(response); + } + } + + try { + await this.orm.call("pos.session", "update_closing_control_state_session", [ + this.globalState.pos_session.id, + notes, + ]); + } catch (error) { + // We have to handle the error manually otherwise the validation check stops the script. + // In case of "rescue session", we want to display the next popup with "handleClosingError". + // FIXME + if ( + !error.message && + !error.message.data && + error.message.data.message !== "This session is already closed." + ) { + throw error; + } + } + + try { + const bankPaymentMethodDiffPairs = otherPaymentMethods + .filter((pm) => pm.type == "bank") + .map((pm) => [pm.id, payments[pm.id].difference]); + response = await this.orm.call("pos.session", "close_session_from_ui", [ + this.globalState.pos_session.id, + bankPaymentMethodDiffPairs, + ]); + if (!response.successful) { + return this.handleClosingError(response); + } + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } catch (error) { + if (identifyError(error) instanceof ConnectionLostError) { + // Cannot redirect to backend when offline, let error handlers show the offline popup + // FIXME POSREF: doing this means closing again when online will redo the beginning of the method + // although it's impossible to close again because this.closeSessionClicked isn't reset to false + // The application state is corrupted. + throw error; + } else { + // FIXME POSREF: why are we catching errors here but not anywhere else in this method? + await this.popup.add(ErrorPopup, { + title: _t("Closing session error"), + body: _t( + "An error has occurred when trying to close the session.\n" + + "You will be redirected to the back-end to manually close the session." + ), + }); + window.location = "/web#action=point_of_sale.action_client_pos_menu"; + } + } + } + async selectPartner() { + // FIXME, find order to refund when we are in the ticketscreen. + const currentOrder = this.globalState.get_order(); + if (!currentOrder) { + return; + } + const currentPartner = currentOrder.get_partner(); + if (currentPartner && currentOrder.getHasRefundLines()) { + this.popup.add(ErrorPopup, { + title: this.env._t("Can't change customer"), + body: _.str.sprintf( + this.env._t( + "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer." + ), + currentPartner.name + ), + }); + return; + } + const { confirmed, payload: newPartner } = await this.showTempScreen("PartnerListScreen", { + partner: currentPartner, + }); + if (confirmed) { + currentOrder.set_partner(newPartner); + currentOrder.updatePricelist(newPartner); + } + } + // FIXME: POSREF, method exist only to be overrided + async addProductFromUi(product, options) { + this.globalState.get_order().add_product(product, options); + } + async addProductToCurrentOrder(product) { + if (Number.isInteger(product)) { + product = this.globalState.db.get_product_by_id(product); + } + const currentOrder = this.globalState.get_order(); + + if (!currentOrder) { + this.globalState.add_new_order(); + } + + const options = await product.getAddProductOptions(); + + if (!options) { + return; + } + + // Add the product after having the extra information. + this.addProductFromUi(product, options); + this.numberBuffer.reset(); + } // FIXME POSREF get rid of temp screens entirely? showTempScreen(name, props = {}) { return new Promise((resolve) => { diff --git a/addons/point_of_sale/static/src/js/Chrome.js b/addons/point_of_sale/static/src/js/Chrome.js index 8bbcb4d5db1..ee223a0ae14 100644 --- a/addons/point_of_sale/static/src/js/Chrome.js +++ b/addons/point_of_sale/static/src/js/Chrome.js @@ -1,9 +1,8 @@ /** @odoo-module */ import { loadCSS } from "@web/core/assets"; -import { useListener, useBus, useService } from "@web/core/utils/hooks"; +import { useBus, useService } from "@web/core/utils/hooks"; import BarcodeParser from "barcodes.BarcodeParser"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { batched } from "@point_of_sale/js/utils"; import { debounce } from "@web/core/utils/timing"; import { Transition } from "@web/core/transition"; @@ -18,7 +17,6 @@ import { pos_env as env } from "@point_of_sale/js/pos_env"; import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup"; import { CashOpeningPopup } from "./Popups/CashOpeningPopup"; -import { ConfirmPopup } from "./Popups/ConfirmPopup"; import { onMounted, @@ -27,13 +25,14 @@ import { useSubEnv, reactive, onWillUnmount, + Component, } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/pos_hook"; /** * Chrome is the root component of the PoS App. */ -export class Chrome extends LegacyComponent { +export class Chrome extends Component { static template = "Chrome"; // FIXME POSREF namespace templates static components = { Transition, MainComponentsContainer, WithEnv, ErrorHandler, Navbar }; setup() { @@ -90,9 +89,6 @@ export class Chrome extends LegacyComponent { super.setup(); useExternalListener(window, "beforeunload", this._onBeforeUnload); - useListener("close-pos", this._closePos); - useListener("loading-skip-callback", () => this.env.proxy.stop_searching()); - useListener("connect-to-proxy", this.connect_to_proxy); useBus(this.env.posbus, "start-cash-control", this.openCashControl); useService("number_buffer").activate(); @@ -157,7 +153,7 @@ export class Chrome extends LegacyComponent { await this.env.pos.load_server_data(); await this.setupBarcodeParser(); if (this.env.pos.config.use_proxy) { - await this.connect_to_proxy(); + await this.pos.connect_to_proxy(); } // Load the saved `env.pos.toRefundLines` from localStorage when // the PosGlobalState is ready. @@ -252,45 +248,6 @@ export class Chrome extends LegacyComponent { return barcode_parser.is_loaded(); } - connect_to_proxy() { - return new Promise((resolve, reject) => { - this.env.barcode_reader.disconnect_from_proxy(); - this.pos.loadingSkipButtonIsShown = true; - this.env.proxy - .autoconnect({ - force_ip: this.env.pos.config.proxy_ip || undefined, - progress: function (prog) {}, - }) - .then( - () => { - if (this.env.pos.config.iface_scan_via_proxy) { - this.env.barcode_reader.connect_to_proxy(); - } - resolve(); - }, - (statusText, url) => { - // this should reject so that it can be captured when we wait for pos.ready - // in the chrome component. - // then, if it got really rejected, we can show the error. - if (statusText == "error" && window.location.protocol == "https:") { - reject({ - title: this.env._t("HTTPS connection to IoT Box failed"), - body: _.str.sprintf( - this.env._t( - "Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box." - ), - url - ), - popup: "alert", - }); - } else { - resolve(); - } - } - ); - }); - } - openCashControl() { if (this.shouldShowCashControl()) { this.popup.add(CashOpeningPopup, { keepBehind: true }); @@ -306,48 +263,7 @@ export class Chrome extends LegacyComponent { // EVENT HANDLERS // async _closePos() { - // If pos is not properly loaded, we just go back to /web without - // doing anything in the order data. - if (!this.env.pos || this.env.pos.db.get_orders().length === 0) { - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } - - if (this.env.pos.db.get_orders().length) { - // If there are orders in the db left unsynced, we try to sync. - // If sync successful, close without asking. - // Otherwise, ask again saying that some orders are not yet synced. - try { - await this.env.pos.push_orders(); - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } catch (error) { - console.warn(error); - const reason = this.env.pos.failed - ? this.env._t( - "Some orders could not be submitted to " + - "the server due to configuration errors. " + - "You can exit the Point of Sale, but do " + - "not close the session before the issue " + - "has been resolved." - ) - : this.env._t( - "Some orders could not be submitted to " + - "the server due to internet connection issues. " + - "You can exit the Point of Sale, but do " + - "not close the session before the issue " + - "has been resolved." - ); - const { confirmed } = await this.popup.add(ConfirmPopup, { - title: this.env._t("Offline Orders"), - body: reason, - }); - if (confirmed) { - // FIXME POSREF setting the location prevents the next render, the loading screen never shows - this.pos.uiState = "CLOSING"; - this.pos.loadingSkipButtonIsShown = false; - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } - } - } + this.pos.closePos(); } /** * Save `env.pos.toRefundLines` in localStorage on beforeunload - closing the @@ -356,6 +272,10 @@ export class Chrome extends LegacyComponent { _onBeforeUnload() { this.env.pos.db.save("TO_REFUND_LINES", this.env.pos.toRefundLines); } + _onSetSyncStatus({ detail: { status, pending } }) { + this.env.pos.synch.status = status; + this.env.pos.synch.pending = pending; + } // MISC METHODS // _preloadImages() { @@ -416,7 +336,7 @@ export class Chrome extends LegacyComponent { msg.session == this.env.pos.pos_session.id ) { console.info("POS / Session opened in another window. EXITING POS"); - this._closePos(); + this.closePos(); } } }, 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 c65e547db52..78345988cdf 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/CashierName.js @@ -1,9 +1,9 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; // Previously UsernameWidget -export class CashierName extends LegacyComponent { +export class CashierName extends Component { static template = "CashierName"; get username() { diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/CustomerFacingDisplayButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/CustomerFacingDisplayButton.js index 17952c0d229..0f99f5d01d0 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/CustomerFacingDisplayButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/CustomerFacingDisplayButton.js @@ -1,10 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component, useState } from "@odoo/owl"; -const { useState } = owl; - -export class CustomerFacingDisplayButton extends LegacyComponent { +export class CustomerFacingDisplayButton extends Component { static template = "CustomerFacingDisplayButton"; setup() { diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js index caa535948e4..73374baad11 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/HeaderButton.js @@ -1,12 +1,12 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { useService } from "@web/core/utils/hooks"; import { ClosePosPopup } from "@point_of_sale/js/Popups/ClosePosPopup"; +import { Component } from "@odoo/owl"; // Previously HeaderButtonWidget // This is the close session button -export class HeaderButton extends LegacyComponent { +export class HeaderButton extends Component { static template = "HeaderButton"; setup() { diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js index df3ee3ba304..265bd7b2f04 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/ProxyStatus.js @@ -1,11 +1,9 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; - -const { onMounted, onWillUnmount, useState } = owl; +import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl"; // Previously ProxyStatusWidget -export class ProxyStatus extends LegacyComponent { +export class ProxyStatus extends Component { static template = "ProxyStatus"; setup() { diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js index 31cdc109311..6e3f53010cc 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SaleDetailsButton.js @@ -1,16 +1,17 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { useService } from "@web/core/utils/hooks"; import { renderToString } from "@web/core/utils/render"; import { ErrorPopup } from "../Popups/ErrorPopup"; +import { Component } from "@odoo/owl"; -export class SaleDetailsButton extends LegacyComponent { +export class SaleDetailsButton extends Component { static template = "SaleDetailsButton"; setup() { super.setup(...arguments); this.popup = useService("popup"); + this.rpc = useService("rpc"); } async onClick() { diff --git a/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js index e5dad0f2c32..ce4c91c092d 100644 --- a/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js +++ b/addons/point_of_sale/static/src/js/ChromeWidgets/SyncNotification.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class SyncNotification extends LegacyComponent { +export class SyncNotification extends Component { static template = "SyncNotification"; onClick() { diff --git a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js index 5511053ae4d..1980266cbca 100644 --- a/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js +++ b/addons/point_of_sale/static/src/js/Misc/AbstractReceiptScreen.js @@ -1,11 +1,9 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { useService } from "@web/core/utils/hooks"; import { ConfirmPopup } from "../Popups/ConfirmPopup"; import { ErrorPopup } from "../Popups/ErrorPopup"; - -const { useRef } = owl; +import { Component, useRef } from "@odoo/owl"; /** * This relies on the assumption that there is a reference to @@ -13,7 +11,7 @@ const { useRef } = owl; * `order-receipt` in the template of the Component that extends * this abstract component. */ -export class AbstractReceiptScreen extends LegacyComponent { +export class AbstractReceiptScreen extends Component { setup() { super.setup(); this.orderReceipt = useRef("order-receipt"); diff --git a/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js b/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js index 099d5268ddf..7b27da75337 100644 --- a/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js +++ b/addons/point_of_sale/static/src/js/Misc/CurrencyAmount.js @@ -1,7 +1,7 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class CurrencyAmount extends LegacyComponent { +export class CurrencyAmount extends Component { static template = "CurrencyAmount"; } diff --git a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js index 560cfedbb14..153caff7442 100644 --- a/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js +++ b/addons/point_of_sale/static/src/js/Misc/IndependentToOrderScreen.js @@ -1,9 +1,9 @@ /** @odoo-module */ +import { Component } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/pos_hook"; -import { LegacyComponent } from "@web/legacy/legacy_component"; -export class IndependentToOrderScreen extends LegacyComponent { +export class IndependentToOrderScreen extends Component { static storeOnOrder = false; setup() { super.setup(...arguments); diff --git a/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js b/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js index 298cc17e0ff..8f215b51b4b 100644 --- a/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js +++ b/addons/point_of_sale/static/src/js/Misc/MobileOrderWidget.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class MobileOrderWidget extends LegacyComponent { +export class MobileOrderWidget extends Component { static template = "MobileOrderWidget"; get order() { diff --git a/addons/point_of_sale/static/src/js/Misc/SearchBar.js b/addons/point_of_sale/static/src/js/Misc/SearchBar.js index 3e6586be4bb..b2c5daae81a 100644 --- a/addons/point_of_sale/static/src/js/Misc/SearchBar.js +++ b/addons/point_of_sale/static/src/js/Misc/SearchBar.js @@ -1,9 +1,7 @@ /** @odoo-module */ -import { useAutofocus, useListener } from "@web/core/utils/hooks"; -import { LegacyComponent } from "@web/legacy/legacy_component"; - -const { useExternalListener, useState } = owl; +import { Component, useExternalListener, useState } from "@odoo/owl"; +import { useAutofocus } from "@web/core/utils/hooks"; /** * This is a simple configurable search bar component. It has search fields @@ -23,20 +21,17 @@ const { useExternalListener, useState } = owl; * placeholder: string, * }} * @emits search @payload { fieldName: string, searchTerm: '' } - * @emits filter-selected @payload { filter: string } * * NOTE: The payload of the emitted event is accessible via the `detail` * field of the event. */ -export class SearchBar extends LegacyComponent { +export class SearchBar extends Component { static template = "point_of_sale.SearchBar"; setup() { super.setup(); useAutofocus(); useExternalListener(window, "click", this._hideOptions); - useListener("click-search-field", this._onClickSearchField); - useListener("select-filter", this._onSelectFilter); this.filterOptionsList = [...this.props.config.filter.options.keys()]; this.searchFieldsList = [...this.props.config.searchFields.keys()]; const defaultSearchFieldId = this.searchFieldsList.indexOf( @@ -50,9 +45,9 @@ export class SearchBar extends LegacyComponent { selectedFilter: this.props.config.defaultFilter || this.filterOptionsList[0], }); } - _onSelectFilter({ detail: key }) { + _onSelectFilter(key) { this.state.selectedFilter = key; - this.trigger("filter-selected", { filter: this.state.selectedFilter }); + this.props.onFilterSelected(this.state.selectedFilter); } /** * When pressing vertical arrow keys, do not move the input cursor. @@ -83,9 +78,9 @@ export class SearchBar extends LegacyComponent { /** * Called when a search field is clicked. */ - _onClickSearchField({ detail: fieldName }) { + _onClickSearchField(fieldName) { this.state.showSearchFields = false; - this.trigger("search", { fieldName, searchTerm: this.state.searchInput }); + this.props.onSearch({ fieldName, searchTerm: this.state.searchInput }); } /** * Given an arrow key, return the next selectedSearchFieldId. diff --git a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js index 50a2dd7e547..8916bc1bd45 100644 --- a/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js +++ b/addons/point_of_sale/static/src/js/Popups/AbstractAwaitablePopup.js @@ -1,7 +1,6 @@ /** @odoo-module */ -import { useExternalListener } from "@odoo/owl"; -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component, useExternalListener } from "@odoo/owl"; /** * Implement this abstract class by extending it like so: @@ -28,7 +27,7 @@ import { LegacyComponent } from "@web/legacy/legacy_component"; * // otherwise, payload = null * ``` */ -export class AbstractAwaitablePopup extends LegacyComponent { +export class AbstractAwaitablePopup extends Component { setup() { super.setup(...arguments); useExternalListener(window, "keyup", this._onWindowKeyup); 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 c96f4489f7e..b04ce829d66 100644 --- a/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/CashOpeningPopup.js @@ -3,8 +3,7 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { useService } from "@web/core/utils/hooks"; import { MoneyDetailsPopup } from "./MoneyDetailsPopup"; - -const { useState } = owl; +import { useState } from "@odoo/owl"; export class CashOpeningPopup extends AbstractAwaitablePopup { static template = "CashOpeningPopup"; @@ -19,6 +18,7 @@ export class CashOpeningPopup extends AbstractAwaitablePopup { openingCash: this.env.pos.pos_session.cash_register_balance_start || 0, }); this.popup = useService("popup"); + this.rpc = useService("rpc"); } //@override async confirm() { 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 e5cae0cb6cf..a969276b159 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -1,16 +1,13 @@ /** @odoo-module */ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; -import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers"; -import { ConnectionLostError } from "@web/core/network/rpc_service"; import { SaleDetailsButton } from "../ChromeWidgets/SaleDetailsButton"; import { ConfirmPopup } from "./ConfirmPopup"; import { ErrorPopup } from "./ErrorPopup"; import { MoneyDetailsPopup } from "./MoneyDetailsPopup"; import { AlertPopup } from "./AlertPopup"; import { useService } from "@web/core/utils/hooks"; - -const { useState } = owl; +import { useState } from "@odoo/owl"; export class ClosePosPopup extends AbstractAwaitablePopup { static components = { SaleDetailsButton }; @@ -19,6 +16,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { setup() { super.setup(); this.popup = useService("popup"); + this.pos = useService("pos"); this.manualInputCashCount = false; this.cashControl = this.env.pos.config.cash_control; this.closeSessionClicked = false; @@ -123,82 +121,25 @@ export class ClosePosPopup extends AbstractAwaitablePopup { return true; } closePos() { - this.trigger("close-pos"); + this.pos.closePos(); } async closeSession() { if (!this.closeSessionClicked) { this.closeSessionClicked = true; - let response; - if (this.cashControl) { - response = await this.rpc({ - model: "pos.session", - method: "post_closing_cash_details", - args: [this.env.pos.pos_session.id], - kwargs: { - counted_cash: this.state.payments[this.defaultCashDetails.id].counted, - }, - }); - if (!response.successful) { - return this.handleClosingError(response); - } - } - try { - await this.rpc({ - model: "pos.session", - method: "update_closing_control_state_session", - args: [this.env.pos.pos_session.id, this.state.notes], - }); - } catch (error) { - // We have to handle the error manually otherwise the validation check stops the script. - // In case of "rescue session", we want to display the next popup with "handleClosingError". - // FIXME - if ( - !error.message && - !error.message.data && - error.message.data.message !== "This session is already closed." - ) { - throw error; - } - } - - try { - const bankPaymentMethodDiffPairs = this.otherPaymentMethods - .filter((pm) => pm.type == "bank") - .map((pm) => [pm.id, this.state.payments[pm.id].difference]); - response = await this.rpc({ - model: "pos.session", - method: "close_session_from_ui", - args: [this.env.pos.pos_session.id, bankPaymentMethodDiffPairs], - context: this.env.session.user_context, - }); - if (!response.successful) { - return this.handleClosingError(response); - } - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } catch (error) { - if (identifyError(error) instanceof ConnectionLostError) { - // Cannot redirect to backend when offline, let error handlers show the offline popup - // FIXME POSREF: doing this means closing again when online will redo the beginning of the method - // although it's impossible to close again because this.closeSessionClicked isn't reset to false - // The application state is corrupted. - throw error; - } else { - // FIXME POSREF: why are we catching errors here but not anywhere else in this method? - await this.popup.add(ErrorPopup, { - title: this.env._t("Closing session error"), - body: this.env._t( - "An error has occurred when trying to close the session.\n" + - "You will be redirected to the back-end to manually close the session." - ), - }); - window.location = "/web#action=point_of_sale.action_client_pos_menu"; - } - } + const defaultCashCounted = this.env.pos.config.cashControl + ? this.state.payments[this.defaultCashDetails.id].counted + : 0; + this.pos.closeSession( + defaultCashCounted, + this.state.notes, + this.otherPaymentMethods, + this.state.payments + ); } + this.closeSessionClicked = false; } - async handleClosingError(response) { let popupType = ""; let title = ""; diff --git a/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js b/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js index 98714b62269..e2a32a87db8 100644 --- a/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js @@ -2,28 +2,26 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { _lt } from "@web/core/l10n/translation"; - -const { onMounted, useRef, useState } = owl; +import { onMounted, useRef, useState } from "@odoo/owl"; export class DatePickerPopup extends AbstractAwaitablePopup { - static template = 'DatePickerPopup'; + static template = "DatePickerPopup"; static defaultProps = { - confirmText: _lt('Confirm'), - cancelText: _lt('Discard'), - title: _lt('DatePicker'), + confirmText: _lt("Confirm"), + cancelText: _lt("Discard"), + title: _lt("DatePicker"), }; setup() { super.setup(); - this.state = useState({shippingDate: this._today()}); - this.inputRef = useRef('input'); + this.state = useState({ shippingDate: this._today() }); + this.inputRef = useRef("input"); onMounted(() => this.inputRef.el.focus()); } getPayload() { - return this.state.shippingDate < this._today() ? this._today(): this.state.shippingDate; + return this.state.shippingDate < this._today() ? this._today() : this.state.shippingDate; } _today() { - return new Date().toISOString().split('T')[0] + return new Date().toISOString().split("T")[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 4cb250b3c8a..951eb2aeae3 100644 --- a/addons/point_of_sale/static/src/js/Popups/EditListInput.js +++ b/addons/point_of_sale/static/src/js/Popups/EditListInput.js @@ -1,6 +1,6 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; /** * props { @@ -9,7 +9,7 @@ import { LegacyComponent } from "@web/legacy/legacy_component"; * item: object, * } */ -export class EditListInput extends LegacyComponent { +export class EditListInput extends Component { static template = "EditListInput"; onKeyup(event) { 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 7b36bd90375..b69bf90a56d 100644 --- a/addons/point_of_sale/static/src/js/Popups/EditListPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/EditListPopup.js @@ -3,11 +3,9 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { useAutoFocusToLast } from "@point_of_sale/js/custom_hooks"; import { _lt } from "@web/core/l10n/translation"; - +import { useState } from "@odoo/owl"; import { EditListInput } from "./EditListInput"; -const { useState } = owl; - /** * Given a array of { id, text }, we show the user this popup to be able to modify this given array. * (used to replace PackLotLinePopupWidget) diff --git a/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js b/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js index 61212835e59..cd9c3f14bb7 100644 --- a/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ErrorTracebackPopup.js @@ -2,6 +2,7 @@ import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { _lt } from "@web/core/l10n/translation"; +import { useService } from "@web/core/utils/hooks"; // formerly ErrorTracebackPopupWidget export class ErrorTracebackPopup extends ErrorPopup { @@ -14,9 +15,12 @@ export class ErrorTracebackPopup extends ErrorPopup { body: "", exitButtonIsShown: false, exitButtonText: _lt("Exit Pos"), - exitButtonTrigger: "close-pos", }; + setup() { + this.pos = useService("pos"); + } + get tracebackUrl() { const blob = new Blob([this.props.body]); const URL = window.URL || window.webkitURL; 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 3657d0d5136..e16be77251e 100644 --- a/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/MoneyDetailsPopup.js @@ -1,11 +1,9 @@ /** @odoo-module */ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; - +import { useState } from "@odoo/owl"; import { CurrencyAmount } from "../Misc/CurrencyAmount"; -const { useState } = owl; - export class MoneyDetailsPopup extends AbstractAwaitablePopup { static components = { CurrencyAmount }; static template = "MoneyDetailsPopup"; 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 a8ad3bad436..ae90a7d006d 100644 --- a/addons/point_of_sale/static/src/js/Popups/NumberPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/NumberPopup.js @@ -3,9 +3,8 @@ import core from "web.core"; var _t = core._t; import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; -import { useListener, useService } from "@web/core/utils/hooks"; - -const { useState } = owl; +import { useService } from "@web/core/utils/hooks"; +import { useState } from "@odoo/owl"; export class NumberPopup extends AbstractAwaitablePopup { static template = "NumberPopup"; @@ -31,8 +30,6 @@ export class NumberPopup extends AbstractAwaitablePopup { */ setup() { super.setup(); - useListener("accept-input", this.confirm); - useListener("close-this-popup", this.cancel); let startingBuffer = ""; if (typeof this.props.startingValue === "number" && this.props.startingValue > 0) { startingBuffer = this.props.startingValue @@ -42,9 +39,8 @@ export class NumberPopup extends AbstractAwaitablePopup { this.state = useState({ buffer: startingBuffer, toStartOver: this.props.isInputSelected }); this.numberBuffer = useService("number_buffer"); this.numberBuffer.use({ - nonKeyboardInputEvent: "numpad-click-input", - triggerAtEnter: "accept-input", - triggerAtEscape: "close-this-popup", + triggerAtEnter: () => this.confirm(), + triggerAtEscape: () => this.cancel(), state: this.state, }); } @@ -67,7 +63,7 @@ export class NumberPopup extends AbstractAwaitablePopup { } } sendInput(key) { - this.trigger("numpad-click-input", { key }); + this.numberBuffer.sendKey(key); } getPayload() { return this.numberBuffer.get(); diff --git a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js index 44822142cf2..47f8f4dbd00 100644 --- a/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ProductConfiguratorPopup.js @@ -1,10 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; +import { Component, useRef, useState, useSubEnv } from "@odoo/owl"; -const { useState, useSubEnv } = owl; - -export class BaseProductAttribute extends LegacyComponent { +export class BaseProductAttribute extends Component { setup() { super.setup(); this.env.attribute_components.push(this); @@ -38,6 +36,7 @@ export class RadioProductAttribute extends BaseProductAttribute { setup() { super.setup(); + this.root = useRef("root"); owl.onMounted(this.onMounted); } onMounted() { @@ -45,7 +44,7 @@ export class RadioProductAttribute extends BaseProductAttribute { // a matching `value` attribute. In our case, we use `t-att-value` so `value` is // not found yet and no radio is selected by default. // We then manually select the first input of each radio attribute. - $(this.el).find('input[type="radio"]:first').prop("checked", true); + this.root.el.querySelector("input[type=radio]").checked = true; } } diff --git a/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js b/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js index 57a3b2a9b3b..d6e055e16b3 100644 --- a/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/SelectionPopup.js @@ -2,8 +2,7 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { _lt } from "@web/core/l10n/translation"; - -const { useState } = owl; +import { useState } from "@odoo/owl"; export class SelectionPopup extends AbstractAwaitablePopup { static template = "SelectionPopup"; 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 88ec0aa937d..ff72c604770 100644 --- a/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/TextAreaPopup.js @@ -2,8 +2,7 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { _lt } from "@web/core/l10n/translation"; - -const { onMounted, useRef, useState } = owl; +import { onMounted, useRef, useState } from "@odoo/owl"; // IMPROVEMENT: This code is very similar to TextInputPopup. // Combining them would reduce the code. 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 2ab6a397b05..c3350361799 100644 --- a/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/TextInputPopup.js @@ -2,8 +2,7 @@ import { AbstractAwaitablePopup } from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; import { _lt } from "@web/core/l10n/translation"; - -const { onMounted, useRef, useState } = owl; +import { onMounted, useRef, useState } from "@odoo/owl"; // formerly TextInputPopupWidget export class TextInputPopup extends AbstractAwaitablePopup { diff --git a/addons/point_of_sale/static/src/js/PosContext.js b/addons/point_of_sale/static/src/js/PosContext.js index 677ebdad04e..aa6b56e3ba8 100644 --- a/addons/point_of_sale/static/src/js/PosContext.js +++ b/addons/point_of_sale/static/src/js/PosContext.js @@ -1,6 +1,5 @@ /** @odoo-module */ -const { reactive } = owl; - +import { reactive } from "@odoo/owl"; // Create global context objects // e.g. component.env.device = new Context({ isMobile: false }); export const orderManagement = reactive({ searchString: "", selectedOrder: null }); diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js index fea447fcd25..c522a9c2899 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerDetailsEdit.js @@ -2,13 +2,11 @@ import { _t } from "web.core"; import { getDataURLFromFile } from "web.utils"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { useService } from "@web/core/utils/hooks"; +import { Component, onMounted, onWillUnmount } from "@odoo/owl"; -const { onMounted, onWillUnmount } = owl; - -export class PartnerDetailsEdit extends LegacyComponent { +export class PartnerDetailsEdit extends Component { static template = "PartnerDetailsEdit"; setup() { @@ -72,7 +70,7 @@ export class PartnerDetailsEdit extends LegacyComponent { }); } processedChanges.id = this.props.partner.id || false; - this.trigger("save-changes", { processedChanges }); + this.props.saveChanges(processedChanges); } async uploadImage(event) { const file = event.target.files[0]; diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js index ae665338648..71e7ce0dbad 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerLine.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class PartnerLine extends LegacyComponent { +export class PartnerLine extends Component { static template = "PartnerLine"; get highlight() { diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js index 376f43d2a13..d274850879e 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js @@ -1,16 +1,14 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { registry } from "@web/core/registry"; import { debounce } from "@web/core/utils/timing"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { PartnerLine } from "./PartnerLine"; import { PartnerDetailsEdit } from "./PartnerDetailsEdit"; import { usePos } from "@point_of_sale/app/pos_hook"; - -const { onWillUnmount, useRef } = owl; +import { Component, onWillUnmount, useRef } from "@odoo/owl"; /** * Render this screen using `showTempScreen` to select partner. @@ -27,16 +25,15 @@ const { onWillUnmount, useRef } = owl; * * @props partner - originally selected partner */ -export class PartnerListScreen extends LegacyComponent { +export class PartnerListScreen extends Component { static components = { PartnerDetailsEdit, PartnerLine }; static template = "PartnerListScreen"; setup() { super.setup(); this.pos = usePos(); + this.rpc = useService("rpc"); this.notification = useService("pos_notification"); - useListener("click-save", () => this.env.bus.trigger("save-partner")); - useListener("save-changes", this.saveChanges); this.searchWordInputRef = useRef("search-word-input-partner"); // We are not using useState here because the object @@ -159,11 +156,11 @@ export class PartnerListScreen extends LegacyComponent { }; this.activateEditMode(); } - async saveChanges(event) { + async saveChanges(processedChanges) { const partnerId = await this.rpc({ model: "res.partner", method: "create_from_ui", - args: [event.detail.processedChanges], + args: [processedChanges], }); await this.env.pos.load_new_partners(); this.state.selectedPartner = this.env.pos.db.get_partner_by_id(partnerId); diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js index d2965e66d57..c5c764cb734 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PSNumpadInputButton.js @@ -1,10 +1,15 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; -export class PSNumpadInputButton extends LegacyComponent { +export class PSNumpadInputButton extends Component { static template = "PSNumpadInputButton"; + setup() { + this.numberBuffer = useService("number_buffer"); + } + get _class() { return this.props.changeClassTo || "input-button number-char"; } diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index d8e51af6d8b..d78a268977e 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -1,10 +1,9 @@ /** @odoo-module */ import { parse } from "web.field_utils"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { useErrorHandlers } from "@point_of_sale/js/custom_hooks"; import { registry } from "@web/core/registry"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import utils from "web.utils"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -16,8 +15,9 @@ import { PaymentScreenNumpad } from "./PaymentScreenNumpad"; import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines"; import { PaymentScreenStatus } from "./PaymentScreenStatus"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component } from "@odoo/owl"; -export class PaymentScreen extends LegacyComponent { +export class PaymentScreen extends Component { static template = "PaymentScreen"; static components = { PaymentScreenNumpad, @@ -29,16 +29,8 @@ export class PaymentScreen extends LegacyComponent { super.setup(); this.pos = usePos(); this.popup = useService("popup"); + this.rpc = useService("rpc"); this.notification = useService("pos_notification"); - useListener("delete-payment-line", this.deletePaymentLine); - useListener("select-payment-line", this.selectPaymentLine); - useListener("new-payment-line", this.addNewPaymentLine); - useListener("update-selected-paymentline", this._updateSelectedPaymentline); - useListener("send-payment-request", this._sendPaymentRequest); - useListener("send-payment-cancel", this._sendPaymentCancel); - useListener("send-payment-reverse", this._sendPaymentReverse); - useListener("send-force-done", this._sendForceDone); - useListener("validate-order", () => this.validateOrder(false)); this.payment_methods_from_config = this.env.pos.payment_methods.filter((method) => this.env.pos.config.payment_method_ids.includes(method.id) ); @@ -59,12 +51,9 @@ export class PaymentScreen extends LegacyComponent { } get _getNumberBufferConfig() { const config = { - // The numberBuffer listens to this event to update its state. - // Basically means 'update the buffer when this event is triggered' - nonKeyboardInputEvent: "input-from-numpad", // When the buffer is updated, trigger this event. // Note that the component listens to it. - triggerAtInput: "update-selected-paymentline", + triggerAtInput: () => this.updateSelectedPaymentline(), }; // Check if pos has a cash payment method const hasCashPaymentMethod = this.payment_methods_from_config.some( @@ -102,7 +91,7 @@ export class PaymentScreen extends LegacyComponent { this.currentOrder.updatePricelist(newPartner); } } - addNewPaymentLine({ detail: paymentMethod }) { + addNewPaymentLine(paymentMethod) { // original function: click_paymentmethods const result = this.currentOrder.add_paymentline(paymentMethod); if (result) { @@ -116,7 +105,7 @@ export class PaymentScreen extends LegacyComponent { return false; } } - _updateSelectedPaymentline() { + updateSelectedPaymentline() { if (this.paymentLines.every((line) => line.paid)) { this.currentOrder.add_paymentline(this.payment_methods_from_config[0]); } @@ -132,7 +121,7 @@ export class PaymentScreen extends LegacyComponent { return; } if (this.numberBuffer.get() === null) { - this.deletePaymentLine({ detail: { cid: this.selectedPaymentLine.cid } }); + this.deletePaymentLine(this.selectedPaymentLine.cid); } else { this.selectedPaymentLine.set_amount(this.numberBuffer.getFloat()); } @@ -173,11 +162,9 @@ export class PaymentScreen extends LegacyComponent { this.currentOrder.setShippingDate(false); } } - deletePaymentLine(event) { + deletePaymentLine(cid) { var self = this; - const { cid } = event.detail; const line = this.paymentLines.find((line) => line.cid === cid); - // If a paymentline with a payment terminal linked to // it is removed, the terminal should get a cancel // request. @@ -196,14 +183,14 @@ export class PaymentScreen extends LegacyComponent { this.render(true); } } - selectPaymentLine(event) { - const { cid } = event.detail; + selectPaymentLine(cid) { const line = this.paymentLines.find((line) => line.cid === cid); this.currentOrder.select_paymentline(line); this.numberBuffer.reset(); this.render(true); } async validateOrder(isForceValidate) { + this.numberBuffer.capture(); if (this.env.pos.config.cash_rounding) { if (!this.env.pos.get_order().check_paymentlines_rounding()) { this.popup.add(ErrorPopup, { @@ -467,8 +454,9 @@ export class PaymentScreen extends LegacyComponent { async _postPushOrderResolve(order, order_server_ids) { return true; } - async _sendPaymentRequest({ detail: line }) { + async sendPaymentRequest(line) { // Other payment lines can not be reversed anymore + this.numberBuffer.capture(); this.paymentLines.forEach(function (line) { line.can_be_reversed = false; }); @@ -490,13 +478,13 @@ export class PaymentScreen extends LegacyComponent { ) && this.env.pos.config.auto_validate_terminal_payment ) { - this.trigger("validate-order"); + this.validateOrder(false); } } else { line.set_payment_status("retry"); } } - async _sendPaymentCancel({ detail: line }) { + async sendPaymentCancel(line) { const payment_terminal = line.payment_method.payment_terminal; line.set_payment_status("waitingCancel"); const isCancelSuccessful = await payment_terminal.send_payment_cancel( @@ -509,7 +497,7 @@ export class PaymentScreen extends LegacyComponent { line.set_payment_status("waitingCard"); } } - async _sendPaymentReverse({ detail: line }) { + async sendPaymentReverse(line) { const payment_terminal = line.payment_method.payment_terminal; line.set_payment_status("reversing"); @@ -522,7 +510,7 @@ export class PaymentScreen extends LegacyComponent { line.set_payment_status("done"); } } - async _sendForceDone({ detail: line }) { + async sendForceDone(line) { line.set_payment_status("done"); } } diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js index 74fc52b3917..f8d0510ff72 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenNumpad.js @@ -1,10 +1,10 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { PSNumpadInputButton } from "./PSNumpadInputButton"; +import { Component } from "@odoo/owl"; -export class PaymentScreenNumpad extends LegacyComponent { +export class PaymentScreenNumpad extends Component { static components = { PSNumpadInputButton }; static template = "PaymentScreenNumpad"; diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js index 2dcd952b2f4..8f1b6274ea2 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenPaymentLines.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class PaymentScreenPaymentLines extends LegacyComponent { +export class PaymentScreenPaymentLines extends Component { static template = "PaymentScreenPaymentLines"; formatLineAmount(paymentline) { diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js index e50367c2f55..cf604f7c331 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreenStatus.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class PaymentScreenStatus extends LegacyComponent { +export class PaymentScreenStatus extends Component { static template = "PaymentScreenStatus"; get changeText() { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js index 31e9fc45d70..2bd9d63250c 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js @@ -1,18 +1,21 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component } from "@odoo/owl"; /** * @props partner - * @emits click-partner - * @emits click-pay */ -export class ActionpadWidget extends LegacyComponent { +export class ActionpadWidget extends Component { static template = "ActionpadWidget"; static defaultProps = { isActionButtonHighlighted: false, }; + setup() { + this.pos = usePos(); + } + get isLongName() { return this.props.partner && this.props.partner.name.length > 10; } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js index 4647c292c88..e5d8fa9a5b8 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js @@ -1,8 +1,8 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class CategoryButton extends LegacyComponent { +export class CategoryButton extends Component { static template = "CategoryButton"; get imageUrl() { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js index 7fabd760302..f435e22be44 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/OrderlineCustomerNoteButton.js @@ -1,17 +1,16 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { TextAreaPopup } from "@point_of_sale/js/Popups/TextAreaPopup"; +import { Component } from "@odoo/owl"; -export class OrderlineCustomerNoteButton extends LegacyComponent { +export class OrderlineCustomerNoteButton extends Component { static template = "OrderlineCustomerNoteButton"; setup() { super.setup(); this.popup = useService("popup"); - useListener("click", this.onClick); } async onClick() { const selectedOrderline = this.env.pos.get_order().get_selected_orderline(); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js index cc180c9c88e..5cd793e867a 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/ProductInfoButton.js @@ -1,19 +1,18 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { ProductInfoPopup } from "@point_of_sale/js/Popups/ProductInfoPopup"; +import { Component } from "@odoo/owl"; -export class ProductInfoButton extends LegacyComponent { +export class ProductInfoButton extends Component { static template = "ProductInfoButton"; setup() { super.setup(); - useListener("click", this.onClick); this.popup = useService("popup"); } - async onClick() { + async click() { const orderline = this.env.pos.get_order().get_selected_orderline(); if (orderline) { const product = orderline.get_product(); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js index 1413e677096..516d0768b8d 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/RefundButton.js @@ -1,19 +1,17 @@ /** @odoo-module */ import { usePos } from "@point_of_sale/app/pos_hook"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener } from "@web/core/utils/hooks"; +import { Component } from "@odoo/owl"; -export class RefundButton extends LegacyComponent { +export class RefundButton extends Component { static template = "point_of_sale.RefundButton"; setup() { super.setup(); this.pos = usePos(); - useListener("click", this._onClick); } - _onClick() { + click() { const partner = this.env.pos.get_order().get_partner(); const searchDetails = partner ? { fieldName: "PARTNER", searchTerm: partner.name } : {}; this.pos.showScreen("TicketScreen", { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js index d4cf3e51159..b7525feb1de 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetFiscalPositionButton.js @@ -1,17 +1,16 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; +import { Component } from "@odoo/owl"; -export class SetFiscalPositionButton extends LegacyComponent { +export class SetFiscalPositionButton extends Component { static template = "SetFiscalPositionButton"; setup() { super.setup(); this.popup = useService("popup"); - useListener("click", this.onClick); } get currentOrder() { return this.env.pos.get_order(); @@ -21,7 +20,7 @@ export class SetFiscalPositionButton extends LegacyComponent { ? this.currentOrder.fiscal_position.display_name : this.env._t("Tax"); } - async onClick() { + async click() { const currentFiscalPosition = this.currentOrder.fiscal_position; const fiscalPosList = [ { diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js index 4754b9a9065..86ff979bd68 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SetPricelistButton.js @@ -1,17 +1,16 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ProductScreen } from "@point_of_sale/js/Screens/ProductScreen/ProductScreen"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { SelectionPopup } from "@point_of_sale/js/Popups/SelectionPopup"; +import { Component } from "@odoo/owl"; -export class SetPricelistButton extends LegacyComponent { +export class SetPricelistButton extends Component { static template = "SetPricelistButton"; setup() { super.setup(); this.popup = useService("popup"); - useListener("click", this.onClick); } get currentOrder() { return this.env.pos.get_order(); @@ -20,7 +19,7 @@ export class SetPricelistButton extends LegacyComponent { const order = this.currentOrder; return order && order.pricelist ? order.pricelist.display_name : this.env._t("Pricelist"); } - async onClick() { + async click() { // Create the list to be passed to the SelectionPopup. // Pricelist object is passed as item in the list because it // is the object that will be returned when the popup is confirmed. diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js index f59a1b2fa0c..763b59c7135 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js @@ -1,21 +1,26 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { useService } from "@web/core/utils/hooks"; +import { Component } from "@odoo/owl"; /** * @prop {'quantity' | 'price' | 'discount'} activeMode * @prop {Array<'quantity' | 'price' | 'discount'>} disabledModes * @prop {boolean} disableSign - * @event set-numpad-mode - triggered when mode button is clicked - * @event numpad-click-input - triggered when numpad button is clicked */ -export class NumpadWidget extends LegacyComponent { +export class NumpadWidget extends Component { static template = "NumpadWidget"; static defaultProps = { disabledModes: [], disableSign: false, }; - + setup() { + this.numberBuffer = useService("number_buffer"); + this.numberBuffer.use({ + triggerAtInput: (event) => this.props.updateSelectedOrderline(event), + useWithBarcode: true, + }); + } get hasPriceControlRights() { return ( this.env.pos.cashierHasPriceControlRights() && @@ -34,10 +39,12 @@ export class NumpadWidget extends LegacyComponent { if (!this.hasManualDiscount && mode === "discount") { return; } - this.trigger("set-numpad-mode", { mode }); + this.numberBuffer.capture(); + this.numberBuffer.reset(); + this.env.pos.numpadMode = mode; } sendInput(key) { - this.trigger("numpad-click-input", { key }); + this.numberBuffer.sendKey(key); } get decimalSeparator() { return this.env._t.database.parameters.decimal_point; diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js index 31afe6a29e6..9da6a5025be 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/OrderSummary.js @@ -1,9 +1,9 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; import { float_is_zero } from "web.utils"; -export class OrderSummary extends LegacyComponent { +export class OrderSummary extends Component { static template = "OrderSummary"; getTotal() { 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 64eeab8c7e7..604692fc88a 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 @@ -1,23 +1,20 @@ /** @odoo-module */ -import { useListener, useService } from "@web/core/utils/hooks"; -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { useService } from "@web/core/utils/hooks"; import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup"; import { Orderline } from "./Orderline"; import { OrderSummary } from "./OrderSummary"; +import { Component, useEffect, useRef } from "@odoo/owl"; -const { useEffect, useRef } = owl; - -export class OrderWidget extends LegacyComponent { +export class OrderWidget extends Component { static components = { Orderline, OrderSummary }; static template = "OrderWidget"; setup() { super.setup(); this.popup = useService("popup"); - useListener("select-line", this._selectLine); - useListener("edit-pack-lot-lines", this._editPackLotLines); + this.numberBuffer = useService("number_buffer"); this.scrollableRef = useRef("scrollable"); useEffect( () => { @@ -37,8 +34,9 @@ export class OrderWidget extends LegacyComponent { get orderlinesArray() { return this.order ? this.order.get_orderlines() : []; } - _selectLine(event) { - this.order.select_orderline(event.detail.orderline); + selectLine(orderline) { + this.numberBuffer.reset(); + this.order.select_orderline(orderline); } // IMPROVEMENT: Might be better to lift this to ProductScreen // because there is similar operation when clicking a product. @@ -47,8 +45,7 @@ export class OrderWidget extends LegacyComponent { // to an orderline that has product tracked by lot. Lot tracking (based // on the current implementation) requires that 1 item per orderline is // allowed. - async _editPackLotLines(event) { - const orderline = event.detail.orderline; + async editPackLotLines(orderline) { const isAllowOnlyOneLot = orderline.product.isAllowOnlyOneLot(); const packLotLinesToEdit = orderline.getPackLotLinesToEdit(isAllowOnlyOneLot); const { confirmed, payload } = await this.popup.add(EditListPopup, { @@ -68,6 +65,6 @@ export class OrderWidget extends LegacyComponent { orderline.setPackLotLines({ modifiedPackLotLines, newPackLotLines }); } - this.order.select_orderline(event.detail.orderline); + this.order.select_orderline(orderline); } } diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js index 704023fb455..e1b96722b1a 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/Orderline.js @@ -1,15 +1,15 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class Orderline extends LegacyComponent { +export class Orderline extends Component { static template = "Orderline"; selectLine() { - this.trigger("select-line", { orderline: this.props.line }); + this.props.selectLine(this.props.line); } lotIconClicked() { - this.trigger("edit-pack-lot-lines", { orderline: this.props.line }); + this.props.editPackLotLines(this.props.line); } get addedClasses() { return { 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 af81c432827..a3ca2c6f67d 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 @@ -1,10 +1,14 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; +import { usePos } from "@point_of_sale/app/pos_hook"; -export class ProductItem extends LegacyComponent { +export class ProductItem extends Component { static template = "ProductItem"; + setup() { + this.pos = usePos(); + } /** * For accessibility, pressing should be like clicking the product. * is not considered because it conflicts with the barcode. @@ -13,7 +17,7 @@ export class ProductItem extends LegacyComponent { */ spaceClickProduct(event) { if (event.which === 32) { - this.trigger("click-product", this.props.product); + this.pos.addProductToCurrentOrder(this.props.product); } } get imageUrl() { 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 c215b787de3..e4533fde4ef 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 @@ -1,14 +1,11 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { ControlButtonsMixin } from "@point_of_sale/js/ControlButtonsMixin"; import { registry } from "@web/core/registry"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { useBarcodeReader } from "@point_of_sale/js/custom_hooks"; import { parse } from "web.field_utils"; -import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfiguratorPopup"; -import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup"; import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { ErrorBarcodePopup } from "@point_of_sale/js/Popups/ErrorBarcodePopup"; @@ -21,10 +18,9 @@ import { NumpadWidget } from "./NumpadWidget"; import { OrderWidget } from "./OrderWidget"; import { ProductsWidget } from "./ProductsWidget"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component, onMounted, useState } from "@odoo/owl"; -const { onMounted, useState } = owl; - -export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { +export class ProductScreen extends ControlButtonsMixin(Component) { static template = "ProductScreen"; static components = { ActionpadWidget, @@ -38,12 +34,13 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { super.setup(); this.pos = usePos(); this.popup = useService("popup"); - useListener("update-selected-orderline", this._updateSelectedOrderline); - useListener("select-line", this._selectLine); - useListener("set-numpad-mode", this._setNumpadMode); - useListener("click-product", this._clickProduct); - useListener("click-partner", this.onClickPartner); - useListener("click-pay", this._onClickPay); + this.rpc = useService("rpc"); + this.numberBuffer = useService("number_buffer"); + onMounted(this.onMounted); + // Call `reset` when the `onMounted` callback in `numberBuffer.use` is done. + // We don't do this in the `mounted` lifecycle method because it is called before + // the callbacks in `onMounted` hook. + onMounted(() => this.numberBuffer.reset()); useBarcodeReader({ product: this._barcodeProductAction, weight: this._barcodeProductAction, @@ -52,17 +49,6 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { discount: this._barcodeDiscountAction, error: this._barcodeErrorAction, }); - this.numberBuffer = useService("number_buffer"); - this.numberBuffer.use({ - nonKeyboardInputEvent: "numpad-click-input", - triggerAtInput: "update-selected-orderline", - useWithBarcode: true, - }); - onMounted(this.onMounted); - // Call `reset` when the `onMounted` callback in `numberBuffer.use` is done. - // We don't do this in the `mounted` lifecycle method because it is called before - // the callbacks in `onMounted` hook. - onMounted(() => this.numberBuffer.reset()); this.state = useState({ mobile_pane: this.props.mobile_pane || "right", }); @@ -75,130 +61,13 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { * connected scale. * @see _onScaleNotAvailable */ - get isScaleAvailable() { - return true; - } get partner() { return this.currentOrder ? this.currentOrder.get_partner() : null; } get currentOrder() { return this.env.pos.get_order(); } - async _getAddProductOptions(product, base_code) { - let price_extra = 0.0; - let draftPackLotLines, weight, description, packLotLinesToEdit; - - if (_.some(product.attribute_line_ids, (id) => id in this.env.pos.attributes_by_ptal_id)) { - const attributes = _.map( - product.attribute_line_ids, - (id) => this.env.pos.attributes_by_ptal_id[id] - ).filter((attr) => attr !== undefined); - const { confirmed, payload } = await this.popup.add(ProductConfiguratorPopup, { - product: product, - attributes: attributes, - }); - - if (confirmed) { - description = payload.selected_attributes.join(", "); - price_extra += payload.price_extra; - } else { - return; - } - } - - // Gather lot information if required. - if ( - ["serial", "lot"].includes(product.tracking) && - (this.env.pos.picking_type.use_create_lots || - this.env.pos.picking_type.use_existing_lots) - ) { - const isAllowOnlyOneLot = product.isAllowOnlyOneLot(); - if (isAllowOnlyOneLot) { - packLotLinesToEdit = []; - } else { - const orderline = this.currentOrder - .get_orderlines() - .filter((line) => !line.get_discount()) - .find((line) => line.product.id === product.id); - if (orderline) { - packLotLinesToEdit = orderline.getPackLotLinesToEdit(); - } else { - packLotLinesToEdit = []; - } - } - const { confirmed, payload } = await this.popup.add(EditListPopup, { - title: this.env._t("Lot/Serial Number(s) Required"), - name: product.display_name, - isSingleItem: isAllowOnlyOneLot, - array: packLotLinesToEdit, - }); - if (confirmed) { - // Segregate the old and new packlot lines - const modifiedPackLotLines = Object.fromEntries( - payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text]) - ); - const newPackLotLines = payload.newArray - .filter((item) => !item.id) - .map((item) => ({ lot_name: item.text })); - - draftPackLotLines = { modifiedPackLotLines, newPackLotLines }; - } else { - // We don't proceed on adding product. - return; - } - } - - // Take the weight if necessary. - if (product.to_weight && this.env.pos.config.iface_electronic_scale) { - // Show the ScaleScreen to weigh the product. - if (this.isScaleAvailable) { - const { confirmed, payload } = await this.pos.showTempScreen("ScaleScreen", { - product, - }); - if (confirmed) { - weight = payload.weight; - } else { - // do not add the product; - return; - } - } else { - await this._onScaleNotAvailable(); - } - } - - if (base_code && this.env.pos.db.product_packaging_by_barcode[base_code.code]) { - weight = this.env.pos.db.product_packaging_by_barcode[base_code.code].qty; - } - - return { draftPackLotLines, quantity: weight, description, price_extra }; - } - async _addProduct(product, options) { - this.currentOrder.add_product(product, options); - } - async _clickProduct(event) { - if (!this.currentOrder) { - this.env.pos.add_new_order(); - } - const product = event.detail; - const options = await this._getAddProductOptions(product); - // Do not add product if options is undefined. - if (!options) { - return; - } - // Add the product after having the extra information. - await this._addProduct(product, options); - this.numberBuffer.reset(); - } - _setNumpadMode(event) { - const { mode } = event.detail; - this.numberBuffer.capture(); - this.numberBuffer.reset(); - this.env.pos.numpadMode = mode; - } - _selectLine() { - this.numberBuffer.reset(); - } - async _updateSelectedOrderline(event) { + async updateSelectedOrderline({ buffer, key }) { if (this.env.pos.numpadMode === "quantity" && this.env.pos.disallowLineQuantityChange()) { const order = this.env.pos.get_order(); if (!order.orderlines.length) { @@ -219,16 +88,15 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { }); return; } - const parsedInput = (event.detail.buffer && parse.float(event.detail.buffer)) || 0; + const parsedInput = (buffer && parse.float(buffer)) || 0; if (lastId != selectedLine.cid) { this._showDecreaseQuantityPopup(); } else if (currentQuantity < parsedInput) { - this._setValue(event.detail.buffer); + this._setValue(buffer); } else if (parsedInput < currentQuantity) { this._showDecreaseQuantityPopup(); } } else { - const { buffer } = event.detail; const val = buffer === null ? "remove" : buffer; this._setValue(val); if (val == "remove") { @@ -272,9 +140,9 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { return this._barcodeErrorAction(code); } } - const options = await this._getAddProductOptions(product, code); + const options = await product.getAddProductOptions(code); // Do not proceed on adding the product when no options is returned. - // This is consistent with _clickProduct. + // This is consistent with clickProduct. if (!options) { return; } @@ -337,12 +205,8 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { controlButtons: this.controlButtons, }); } - /** - * override this method to perform procedure if the scale is not available. - * @see isScaleAvailable - */ - async _onScaleNotAvailable() {} async _showDecreaseQuantityPopup() { + this.numberBuffer.reset(); const { confirmed, payload: inputNumber } = await this.popup.add(NumberPopup, { startingValue: 0, title: this.env._t("Set the new quantity"), @@ -370,33 +234,7 @@ export class ProductScreen extends ControlButtonsMixin(LegacyComponent) { } } } - async onClickPartner() { - // IMPROVEMENT: This code snippet is very similar to selectPartner of PaymentScreen. - const currentPartner = this.currentOrder.get_partner(); - if (currentPartner && this.currentOrder.getHasRefundLines()) { - this.popup.add(ErrorPopup, { - title: this.env._t("Can't change customer"), - body: _.str.sprintf( - this.env._t( - "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer." - ), - currentPartner.name - ), - }); - return; - } - const { confirmed, payload: newPartner } = await this.pos.showTempScreen( - "PartnerListScreen", - { - partner: currentPartner, - } - ); - if (confirmed) { - this.currentOrder.set_partner(newPartner); - this.currentOrder.updatePricelist(newPartner); - } - } - async _onClickPay() { + async onClickPay() { if ( this.env.pos .get_order() diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js index d4fb237c6ab..bc868dff6e2 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js @@ -1,15 +1,12 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; -import { useListener } from "@web/core/utils/hooks"; import { usePos } from "@point_of_sale/app/pos_hook"; import { ProductItem } from "./ProductItem"; import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel"; +import { Component, useState } from "@odoo/owl"; -const { useState } = owl; - -export class ProductsWidget extends LegacyComponent { +export class ProductsWidget extends Component { static components = { ProductItem, ProductsWidgetControlPanel }; static template = "ProductsWidget"; @@ -19,10 +16,6 @@ export class ProductsWidget extends LegacyComponent { */ setup() { super.setup(); - useListener("switch-category", this._switchCategory); - useListener("update-search", this._updateSearch); - useListener("clear-search", this._clearSearch); - useListener("update-product-list", this._updateProductList); this.state = useState({ searchWord: "" }); this.pos = usePos(); } @@ -66,17 +59,17 @@ export class ProductsWidget extends LegacyComponent { get shouldShowButton() { return this.productsToDisplay.length === 0 && this.searchWord; } - _switchCategory(event) { - this.env.pos.setSelectedCategoryId(event.detail); + switchCategory(categoryId) { + this.env.pos.setSelectedCategoryId(categoryId); } - _updateSearch(event) { - this.state.searchWord = event.detail; + updateSearch(searchWord) { + this.state.searchWord = searchWord; } - _clearSearch() { + clearSearch() { this.state.searchWord = ""; } - _updateProductList(event) { + updateProductList(event) { this.render(true); - this.trigger("switch-category", 0); + this.switchCategory(0); } } 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 cfe5f5cbe88..4ba1b6cdfbd 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 @@ -1,15 +1,14 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { debounce } from "@web/core/utils/timing"; import { usePos } from "@point_of_sale/app/pos_hook"; import { CategoryButton } from "./CategoryButton"; -import { onMounted, onWillUnmount, useState } from "@odoo/owl"; +import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; -export class ProductsWidgetControlPanel extends LegacyComponent { +export class ProductsWidgetControlPanel extends Component { static components = { CategoryButton }; static template = "ProductsWidgetControlPanel"; @@ -17,6 +16,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent { super.setup(); this.pos = usePos(); this.notification = useService("pos_notification"); + this.rpc = useService("rpc"); this.updateSearch = debounce(this.updateSearch, 100); this.state = useState({ searchInput: "", mobileSearchBarIsShown: false }); @@ -33,7 +33,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent { } _clearSearch() { this.state.searchInput = ""; - this.trigger("clear-search"); + this.props.clearSearch(); } get displayCategImages() { return ( @@ -42,7 +42,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent { ); } updateSearch(event) { - this.trigger("update-search", this.state.searchInput); + this.props.updateSearch(this.state.searchInput); if (event.key === "Enter") { this._onPressEnterKey(); } @@ -63,8 +63,8 @@ export class ProductsWidgetControlPanel extends LegacyComponent { } searchProductFromInfo(productName) { this.state.searchInput = productName; - this.trigger("switch-category", 0); - this.trigger("update-search", productName); + this.props.switchCategory(0); + this.props.updateSearch(productName); } async loadProductFromDB() { if (!this.state.searchInput) { @@ -90,7 +90,7 @@ export class ProductsWidgetControlPanel extends LegacyComponent { if (ProductIds.length) { await this.env.pos._addProducts(ProductIds, false); } - this.trigger("update-product-list"); + this.props.updateProductList(); return ProductIds; } } diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js index 255c39babed..b911c366bac 100644 --- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js +++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js @@ -1,12 +1,9 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; - import { WrappedProductNameLines } from "./WrappedProductNameLines"; +import { Component, onWillUpdateProps } from "@odoo/owl"; -const { onWillUpdateProps } = owl; - -export class OrderReceipt extends LegacyComponent { +export class OrderReceipt extends Component { static components = { WrappedProductNameLines }; static template = "OrderReceipt"; diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js index 672091990b8..1594c9b149a 100644 --- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/ReceiptScreen.js @@ -9,6 +9,7 @@ import { registry } from "@web/core/registry"; import { OrderReceipt } from "./OrderReceipt"; import { onMounted, useRef, status } from "@odoo/owl"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { useService } from "@web/core/utils/hooks"; export class ReceiptScreen extends AbstractReceiptScreen { static template = "ReceiptScreen"; @@ -18,6 +19,7 @@ export class ReceiptScreen extends AbstractReceiptScreen { super.setup(); this.pos = usePos(); useErrorHandlers(); + this.rpc = useService("rpc"); this.orderReceipt = useRef("order-receipt"); this.currentOrder = this.env.pos.get_order(); const partner = this.currentOrder.get_partner(); diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js index 7613b06b12b..d718118777e 100644 --- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js +++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/WrappedProductNameLines.js @@ -1,7 +1,7 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; -export class WrappedProductNameLines extends LegacyComponent { +export class WrappedProductNameLines extends Component { static template = "WrappedProductNameLines"; } diff --git a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js index abe61c6c11c..1bc040c2d03 100644 --- a/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ScaleScreen/ScaleScreen.js @@ -1,13 +1,11 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { round_precision as round_pr } from "web.utils"; import { registry } from "@web/core/registry"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component, onMounted, onWillUnmount, useExternalListener, useState } from "@odoo/owl"; -const { onMounted, onWillUnmount, useExternalListener, useState } = owl; - -export class ScaleScreen extends LegacyComponent { +export class ScaleScreen extends Component { static template = "ScaleScreen"; /** diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js index b25a2663db5..8cd58bdbf38 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/InvoiceButton.js @@ -1,19 +1,20 @@ /** @odoo-module */ -import { useListener, useService } from "@web/core/utils/hooks"; -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { useService } from "@web/core/utils/hooks"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component, useRef } from "@odoo/owl"; -export class InvoiceButton extends LegacyComponent { +export class InvoiceButton extends Component { static template = "InvoiceButton"; setup() { super.setup(); this.pos = usePos(); + this.invoiceButton = useRef("invoice-button"); this.popup = useService("popup"); - useListener("click", this._onClick); + this.rpc = useService("rpc"); } get isAlreadyInvoiced() { if (!this.props.order) { @@ -110,14 +111,14 @@ export class InvoiceButton extends LegacyComponent { // Part 3: Download invoice. await this._downloadInvoice(orderId); - this.trigger("order-invoiced", orderId); + this.props.onInvoiceOrder(orderId); } - async _onClick() { + async click() { try { - this.el.style.pointerEvents = "none"; + this.invoiceButton.el.style.pointerEvents = "none"; await this._invoiceOrder(); } finally { - this.el.style.pointerEvents = "auto"; + this.invoiceButton.el.style.pointerEvents = "auto"; } } } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js index eecba47f6bd..68fb68bc0ae 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/ControlButtons/ReprintReceiptButton.js @@ -1,18 +1,16 @@ /** @odoo-module */ -import { useListener } from "@web/core/utils/hooks"; -import { LegacyComponent } from "@web/legacy/legacy_component"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { Component } from "@odoo/owl"; -export class ReprintReceiptButton extends LegacyComponent { +export class ReprintReceiptButton extends Component { static template = "ReprintReceiptButton"; setup() { super.setup(); this.pos = usePos(); - useListener("click", this._onClick); } - async _onClick() { + async click() { if (!this.props.order) { return; } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js index 045fe19f260..0b81b928cd8 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderDetails.js @@ -1,12 +1,12 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; import { OrderlineDetails } from "./OrderlineDetails"; +import { Component } from "@odoo/owl"; /** * @props {models.Order} order */ -export class OrderDetails extends LegacyComponent { +export class OrderDetails extends Component { static components = { OrderlineDetails }; static template = "OrderDetails"; diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js index 5fae44f44d4..d9f9bc1f399 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js @@ -1,13 +1,13 @@ /** @odoo-module */ -import { LegacyComponent } from "@web/legacy/legacy_component"; +import { Component } from "@odoo/owl"; import { format } from "web.field_utils"; import { round_precision as round_pr } from "web.utils"; /** * @props {pos.order.line} line */ -export class OrderlineDetails extends LegacyComponent { +export class OrderlineDetails extends Component { static template = "OrderlineDetails"; get line() { diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js index 7cf5db954f2..42f23af210d 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js @@ -3,7 +3,7 @@ import { Order } from "@point_of_sale/js/models"; import { IndependentToOrderScreen } from "@point_of_sale/js/Misc/IndependentToOrderScreen"; import { registry } from "@web/core/registry"; -import { useListener, useService } from "@web/core/utils/hooks"; +import { useService } from "@web/core/utils/hooks"; import { parse } from "web.field_utils"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; @@ -42,22 +42,10 @@ export class TicketScreen extends IndependentToOrderScreen { super.setup(); this.pos = usePos(); this.popup = useService("popup"); - useListener("filter-selected", this._onFilterSelected); - useListener("search", this._onSearch); - useListener("click-order", this._onClickOrder); - useListener("create-new-order", this._onCreateNewOrder); - useListener("delete-order", this._onDeleteOrder); - useListener("next-page", this._onNextPage); - useListener("prev-page", this._onPrevPage); - useListener("order-invoiced", this._onInvoiceOrder); - useListener("click-order-line", this._onClickOrderline); - useListener("click-refund-order-uid", this._onClickRefundOrderUid); - useListener("update-selected-orderline", this._onUpdateSelectedOrderline); - useListener("do-refund", this._onDoRefund); + this.rpc = useService("rpc"); this.numberBuffer = useService("number_buffer"); this.numberBuffer.use({ - nonKeyboardInputEvent: "numpad-click-input", - triggerAtInput: "update-selected-orderline", + triggerAtInput: (event) => this._onUpdateSelectedOrderline(event), }); this._state = this.env.pos.TICKET_SCREEN_STATE; this.state = useState({ @@ -73,33 +61,39 @@ export class TicketScreen extends IndependentToOrderScreen { }; Object.assign(this._state.ui, defaultUIState, this.props.ui || {}); - onMounted(() => { - this._onFilterSelected({ detail: { filter: this._state.ui.filter } }); - }); - onWillUnmount(() => { - /** - * Automatically create new order when there is no currently active order. - * Important in fiscal modules to keep the sequence of the orders. - */ - if (this.env.pos.orders.length == 0 && this.allowNewOrders) { - this.env.pos.add_new_order(); - } + onMounted(this.onMounted); + onWillUnmount(this.onWillUnmount); + } + //#region LIFECYCLE METHODS + onMounted() { + this.env.posbus.on("ticket-button-clicked", this, this.close); + setTimeout(() => { + // Show updated list of synced orders when going back to the screen. + this.onFilterSelected(this._state.ui.filter); }); } - async _onFilterSelected(event) { - this._state.ui.filter = event.detail.filter; + onWillUnmount() { + this.env.posbus.off("ticket-button-clicked", this); + } + //#endregion + //#region EVENT HANDLERS + onCloseScreen() { + this.close(); + } + async onFilterSelected(selectedFilter) { + this._state.ui.filter = selectedFilter; if (this._state.ui.filter == "SYNCED") { await this._fetchSyncedOrders(); } } - async _onSearch(event) { - Object.assign(this._state.ui.searchDetails, event.detail); + async onSearch(search) { + Object.assign(this._state.ui.searchDetails, search); if (this._state.ui.filter == "SYNCED") { this._state.syncedOrders.currentPage = 1; await this._fetchSyncedOrders(); } } - _onClickOrder({ detail: clickedOrder }) { + onClickOrder(clickedOrder) { if (!clickedOrder || clickedOrder.locked) { if (this._state.ui.selectedSyncedOrderId == clickedOrder.backendId) { this._state.ui.selectedSyncedOrderId = null; @@ -118,7 +112,7 @@ export class TicketScreen extends IndependentToOrderScreen { this._setOrder(clickedOrder); } } - _onCreateNewOrder() { + onCreateNewOrder() { this.env.pos.add_new_order(); this.pos.showScreen("ProductScreen"); } @@ -129,7 +123,7 @@ export class TicketScreen extends IndependentToOrderScreen { orderList[currentOrderIndex + 1] || orderList[currentOrderIndex - 1] ); } - async _onDeleteOrder({ detail: order }) { + async onDeleteOrder(order) { const screen = order.get_screen_data(); if ( ["ProductScreen", "PaymentScreen"].includes(screen.name) && @@ -156,36 +150,35 @@ export class TicketScreen extends IndependentToOrderScreen { this.env.pos.removeOrder(order); } } - async _onNextPage() { + async onNextPage() { if (this._state.syncedOrders.currentPage < this._getLastPage()) { this._state.syncedOrders.currentPage += 1; await this._fetchSyncedOrders(); } } - async _onPrevPage() { + async onPrevPage() { if (this._state.syncedOrders.currentPage > 1) { this._state.syncedOrders.currentPage -= 1; await this._fetchSyncedOrders(); } } - async _onInvoiceOrder({ detail: orderId }) { + async onInvoiceOrder(orderId) { this.env.pos._invalidateSyncedOrdersCache([orderId]); await this._fetchSyncedOrders(); } - _onClickOrderline({ detail: orderline }) { + onClickOrderline(orderline) { const order = this.getSelectedSyncedOrder(); this._state.ui.selectedOrderlineIds[order.backendId] = orderline.id; this.numberBuffer.reset(); } - _onClickRefundOrderUid({ detail: orderUid }) { + onClickRefundOrderUid(orderUid) { // Open the refund order. const refundOrder = this.env.pos.orders.find((order) => order.uid == orderUid); if (refundOrder) { this._setOrder(refundOrder); } } - _onUpdateSelectedOrderline({ detail }) { - const buffer = detail.buffer; + _onUpdateSelectedOrderline({ key, buffer }) { const order = this.getSelectedSyncedOrder(); if (!order) { return this.numberBuffer.reset(); @@ -229,7 +222,7 @@ export class TicketScreen extends IndependentToOrderScreen { } } } - async _onDoRefund() { + async onDoRefund() { const order = this.getSelectedSyncedOrder(); if (this._doesOrderHaveSoleItem(order)) { @@ -278,8 +271,10 @@ export class TicketScreen extends IndependentToOrderScreen { this.env.pos.set_order(destinationOrder); } - this.close(); + this.onCloseScreen(); } + //#endregion + //#region PUBLIC METHODS getSelectedSyncedOrder() { if (this._state.ui.filter == "SYNCED") { return this._state.syncedOrders.cache[this._state.ui.selectedSyncedOrderId]; diff --git a/addons/point_of_sale/static/src/js/custom_hooks.js b/addons/point_of_sale/static/src/js/custom_hooks.js index 036c897b776..4db00c0b8f1 100644 --- a/addons/point_of_sale/static/src/js/custom_hooks.js +++ b/addons/point_of_sale/static/src/js/custom_hooks.js @@ -3,9 +3,7 @@ import { OfflineErrorPopup } from "./Popups/OfflineErrorPopup"; import { ConfirmPopup } from "./Popups/ConfirmPopup"; import { ErrorTracebackPopup } from "./Popups/ErrorTracebackPopup"; import { ErrorPopup } from "./Popups/ErrorPopup"; -import { useEnv } from "@odoo/owl"; - -const { onMounted, onPatched, onWillUnmount, useComponent } = owl; +import { useEnv, onMounted, onPatched, onWillUnmount, useComponent } from "@odoo/owl"; /** * Introduce error handlers in the component. diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 44ef48be33c..77e54e569d5 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -9,14 +9,15 @@ import time from "web.time"; import utils from "web.utils"; import { batched, uuidv4 } from "@point_of_sale/js/utils"; import { ErrorPopup } from "./Popups/ErrorPopup"; +import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfiguratorPopup"; +import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup"; +import { markRaw, reactive } from "@odoo/owl"; var QWeb = core.qweb; var _t = core._t; var round_di = utils.round_decimals; var round_pr = utils.round_precision; -const Markup = utils.Markup - -const { markRaw, reactive } = owl; +const Markup = utils.Markup; // Container of the product images fetched during rendering // of customer display. There is no need to observe it, thus, @@ -1201,7 +1202,8 @@ export class PosGlobalState extends PosModel { } else if (tax.amount_type === "division") { incl_division_amount += tax.amount * tax.sum_repartition_factor; } else if (tax.amount_type === "fixed") { - incl_fixed_amount += Math.abs(quantity) * tax.amount * tax.sum_repartition_factor; + incl_fixed_amount += + Math.abs(quantity) * tax.amount * tax.sum_repartition_factor; } else { var tax_amount = self._compute_all(tax, base, quantity); incl_fixed_amount += tax_amount; @@ -1252,7 +1254,10 @@ export class PosGlobalState extends PosModel { } tax_amount = round_pr(tax_amount, currency_rounding); - var factorized_tax_amount = round_pr(tax_amount * tax.sum_repartition_factor, currency_rounding) + var factorized_tax_amount = round_pr( + tax_amount * tax.sum_repartition_factor, + currency_rounding + ); if (tax.price_include && total_included_checkpoints[i] === undefined) { cumulated_tax_included_amount += factorized_tax_amount; @@ -1476,6 +1481,103 @@ export class Product extends PosModel { } return this.pos.units_by_id[unit_id]; } + async _onScaleNotAvailable() {} + get isScaleAvailable() { + return true; + } + async getAddProductOptions(base_code) { + let price_extra = 0.0; + let draftPackLotLines, weight, description, packLotLinesToEdit; + + if (_.some(this.attribute_line_ids, (id) => id in this.pos.attributes_by_ptal_id)) { + const attributes = _.map( + this.attribute_line_ids, + (id) => this.pos.attributes_by_ptal_id[id] + ).filter((attr) => attr !== undefined); + const { confirmed, payload } = await this.pos.env.services.popup.add( + ProductConfiguratorPopup, + { + product: this, + attributes: attributes, + } + ); + + if (confirmed) { + description = payload.selected_attributes.join(", "); + price_extra += payload.price_extra; + } else { + return; + } + } + // Gather lot information if required. + if ( + ["serial", "lot"].includes(this.tracking) && + (this.pos.picking_type.use_create_lots || this.pos.picking_type.use_existing_lots) + ) { + const isAllowOnlyOneLot = this.isAllowOnlyOneLot(); + if (isAllowOnlyOneLot) { + packLotLinesToEdit = []; + } else { + const orderline = this.currentOrder + .get_orderlines() + .filter((line) => !line.get_discount()) + .find((line) => line.product.id === this.id); + if (orderline) { + packLotLinesToEdit = orderline.getPackLotLinesToEdit(); + } else { + packLotLinesToEdit = []; + } + } + const { confirmed, payload } = await this.pos.env.services.popup.add(EditListPopup, { + title: this.pos.env._t("Lot/Serial Number(s) Required"), + name: this.display_name, + isSingleItem: isAllowOnlyOneLot, + array: packLotLinesToEdit, + }); + if (confirmed) { + // Segregate the old and new packlot lines + const modifiedPackLotLines = Object.fromEntries( + payload.newArray.filter((item) => item.id).map((item) => [item.id, item.text]) + ); + const newPackLotLines = payload.newArray + .filter((item) => !item.id) + .map((item) => ({ lot_name: item.text })); + + draftPackLotLines = { modifiedPackLotLines, newPackLotLines }; + } else { + // We don't proceed on adding product. + return; + } + } + + // Take the weight if necessary. + if (this.to_weight && this.pos.config.iface_electronic_scale) { + // Show the ScaleScreen to weigh the product. + if (this.isScaleAvailable) { + const product = this; + const { confirmed, payload } = await this.pos.env.services.pos.showTempScreen( + "ScaleScreen", + { + product, + } + ); + if (confirmed) { + weight = payload.weight; + } else { + // do not add the product; + return; + } + } else { + await this._onScaleNotAvailable(); + } + } + + if (base_code && this.pos.db.product_packaging_by_barcode[base_code.code]) { + weight = this.pos.db.product_packaging_by_barcode[base_code.code].qty; + } + + return { draftPackLotLines, quantity: weight, description, price_extra }; + } // Port of _get_product_price on product.pricelist. // // Anything related to UOM can be ignored, the POS will always use @@ -2995,7 +3097,7 @@ export class Order extends PosModel { line.set_unit_price(line.compute_fixed_price(line.price)); } - add_product(product, options) { + async add_product(product, options) { if (this._printed) { // when adding product with a barcode while being in receipt screen this.pos.removeOrder(this); diff --git a/addons/point_of_sale/static/src/xml/Chrome.xml b/addons/point_of_sale/static/src/xml/Chrome.xml index 5ac17ee3615..0a2c6146e50 100644 --- a/addons/point_of_sale/static/src/xml/Chrome.xml +++ b/addons/point_of_sale/static/src/xml/Chrome.xml @@ -33,7 +33,7 @@

Connecting to devices

-