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

-