From 5f4f75924bbb1a6a4855e4fb2bee04acd42a905a Mon Sep 17 00:00:00 2001 From: chien Date: Fri, 1 Dec 2023 16:01:26 +0700 Subject: [PATCH] [MIG] pos_lot_selection: Migration to 17.0 --- pos_lot_selection/README.rst | 7 ++ pos_lot_selection/__manifest__.py | 7 +- pos_lot_selection/models/stock_lot.py | 4 +- pos_lot_selection/readme/CONTRIBUTORS.md | 3 + .../static/description/index.html | 13 +++- .../static/src/js/EditListPopup.esm.js | 19 +++++ .../static/src/js/EditListPopup.js | 23 ------- .../static/src/js/OrderWidget.esm.js | 18 +++++ .../static/src/js/OrderWidget.js | 29 -------- .../static/src/js/Product.esm.js | 20 ++++++ .../static/src/js/ProductScreen.js | 28 -------- pos_lot_selection/static/src/js/models.esm.js | 25 +++++++ pos_lot_selection/static/src/js/models.js | 35 ---------- .../tests/tours/LotSelection.tour.esm.js | 67 ++++++++++++++++++ pos_lot_selection/tests/__init__.py | 1 + pos_lot_selection/tests/test_frontend.py | 69 +++++++++++++++++++ 16 files changed, 247 insertions(+), 121 deletions(-) create mode 100644 pos_lot_selection/readme/CONTRIBUTORS.md create mode 100644 pos_lot_selection/static/src/js/EditListPopup.esm.js delete mode 100644 pos_lot_selection/static/src/js/EditListPopup.js create mode 100644 pos_lot_selection/static/src/js/OrderWidget.esm.js delete mode 100644 pos_lot_selection/static/src/js/OrderWidget.js create mode 100644 pos_lot_selection/static/src/js/Product.esm.js delete mode 100644 pos_lot_selection/static/src/js/ProductScreen.js create mode 100644 pos_lot_selection/static/src/js/models.esm.js delete mode 100644 pos_lot_selection/static/src/js/models.js create mode 100644 pos_lot_selection/static/tests/tours/LotSelection.tour.esm.js create mode 100644 pos_lot_selection/tests/__init__.py create mode 100644 pos_lot_selection/tests/test_frontend.py diff --git a/pos_lot_selection/README.rst b/pos_lot_selection/README.rst index 5635964cbf..08a45d2120 100644 --- a/pos_lot_selection/README.rst +++ b/pos_lot_selection/README.rst @@ -68,6 +68,13 @@ Authors * Tecnativa * Camptocamp +Contributors +------------ + +- Tecnativa +- Camptocamp +- Nguyen Minh Chien + Maintainers ----------- diff --git a/pos_lot_selection/__manifest__.py b/pos_lot_selection/__manifest__.py index b72be28a01..bba3c7676b 100644 --- a/pos_lot_selection/__manifest__.py +++ b/pos_lot_selection/__manifest__.py @@ -4,7 +4,7 @@ { "name": "POS Lot Selection", - "version": "16.0.1.0.0", + "version": "17.0.1.0.0", "category": "Point of Sale", "author": "Tecnativa, Camptocamp, Odoo Community Association (OCA)", "website": "https://github.com/OCA/pos", @@ -13,10 +13,13 @@ "point_of_sale", ], "assets": { - "point_of_sale.assets": [ + "point_of_sale._assets_pos": [ "pos_lot_selection/static/src/js/**/*.js", "pos_lot_selection/static/src/xml/**/*.xml", ], + "web.assets_tests": [ + "pos_lot_selection/static/tests/tours/**/*", + ], }, "application": False, "installable": True, diff --git a/pos_lot_selection/models/stock_lot.py b/pos_lot_selection/models/stock_lot.py index 974577f537..e12a52b1aa 100644 --- a/pos_lot_selection/models/stock_lot.py +++ b/pos_lot_selection/models/stock_lot.py @@ -21,8 +21,8 @@ def get_available_lots_for_pos(self, product_id, company_id): ) lots = lots.filtered( - lambda l: float_compare( - l.product_qty, 0, precision_digits=l.product_uom_id.rounding + lambda rec: float_compare( + rec.product_qty, 0, precision_digits=rec.product_uom_id.rounding ) > 0 ) diff --git a/pos_lot_selection/readme/CONTRIBUTORS.md b/pos_lot_selection/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..9db880a4a6 --- /dev/null +++ b/pos_lot_selection/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Tecnativa +- Camptocamp +- Nguyen Minh Chien \<\> diff --git a/pos_lot_selection/static/description/index.html b/pos_lot_selection/static/description/index.html index 963077ce3a..51cfca8c37 100644 --- a/pos_lot_selection/static/description/index.html +++ b/pos_lot_selection/static/description/index.html @@ -378,7 +378,8 @@

POS Lot Selection

  • Bug Tracker
  • Credits
  • @@ -415,8 +416,16 @@

    Authors

  • Camptocamp
  • +
    +

    Contributors

    + +
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association

    OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/pos_lot_selection/static/src/js/EditListPopup.esm.js b/pos_lot_selection/static/src/js/EditListPopup.esm.js new file mode 100644 index 0000000000..e23db30b48 --- /dev/null +++ b/pos_lot_selection/static/src/js/EditListPopup.esm.js @@ -0,0 +1,19 @@ +/** @odoo-module */ + +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +import {EditListPopup} from "@point_of_sale/app/store/select_lot_popup/select_lot_popup"; +import {_t} from "@web/core/l10n/translation"; +import {patch} from "@web/core/utils/patch"; +import {session} from "@web/session"; + +patch(EditListPopup.prototype, { + setup() { + super.setup(...arguments); + if (this.props.title === _t("Lot/Serial Number(s) Required")) { + this.props.lots = session.lots; + } + }, +}); diff --git a/pos_lot_selection/static/src/js/EditListPopup.js b/pos_lot_selection/static/src/js/EditListPopup.js deleted file mode 100644 index 24950b0780..0000000000 --- a/pos_lot_selection/static/src/js/EditListPopup.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - Copyright 2022 Camptocamp SA - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -*/ -odoo.define("pos_lot_selection.EditListPopup", function (require) { - "use strict"; - - const EditListPopup = require("point_of_sale.EditListPopup"); - const Registries = require("point_of_sale.Registries"); - - const LotSelectEditListPopup = (EditListPopup) => - class extends EditListPopup { - setup() { - super.setup(); - if (this.props.title === this.env._t("Lot/Serial Number(s) Required")) { - this.props.lots = this.env.session.lots; - } - } - }; - - Registries.Component.extend(EditListPopup, LotSelectEditListPopup); - return EditListPopup; -}); diff --git a/pos_lot_selection/static/src/js/OrderWidget.esm.js b/pos_lot_selection/static/src/js/OrderWidget.esm.js new file mode 100644 index 0000000000..fcc8ee5019 --- /dev/null +++ b/pos_lot_selection/static/src/js/OrderWidget.esm.js @@ -0,0 +1,18 @@ +/** @odoo-module */ + +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ +import {Orderline} from "@point_of_sale/app/store/models"; +import {patch} from "@web/core/utils/patch"; +import {session} from "@web/session"; + +patch(Orderline.prototype, { + async editPackLotLines() { + session.lots = await this.pos.getProductLots(this.product); + const res = await super.editPackLotLines(...arguments); + session.lots = undefined; + return res; + }, +}); diff --git a/pos_lot_selection/static/src/js/OrderWidget.js b/pos_lot_selection/static/src/js/OrderWidget.js deleted file mode 100644 index bcb65a5337..0000000000 --- a/pos_lot_selection/static/src/js/OrderWidget.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2022 Camptocamp SA - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -*/ -odoo.define("pos_lot_selection.CustomOrderWidget", function (require) { - "use strict"; - - const Registries = require("point_of_sale.Registries"); - const OrderWidget = require("point_of_sale.OrderWidget"); - - const CustomOrderWidget = (OrderWidget) => - class extends OrderWidget { - /** - * @override - */ - async _editPackLotLines(event) { - const orderline = event.detail.orderline; - this.env.session.lots = await this.env.pos.getProductLots( - orderline.product - ); - const res = await super._editPackLotLines(...arguments); - this.env.session.lots = undefined; - return res; - } - }; - - Registries.Component.extend(OrderWidget, CustomOrderWidget); - return OrderWidget; -}); diff --git a/pos_lot_selection/static/src/js/Product.esm.js b/pos_lot_selection/static/src/js/Product.esm.js new file mode 100644 index 0000000000..430622fc4a --- /dev/null +++ b/pos_lot_selection/static/src/js/Product.esm.js @@ -0,0 +1,20 @@ +/** @odoo-module */ + +/* + Copyright 2022 Camptocamp SA (https://www.camptocamp.com). + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +*/ +import {Product} from "@point_of_sale/app/store/models"; +import {patch} from "@web/core/utils/patch"; +import {session} from "@web/session"; + +patch(Product.prototype, { + async getAddProductOptions(code) { + if (["serial", "lot"].includes(this.tracking)) { + session.lots = await this.pos.getProductLots(this); + } + const res = await super.getAddProductOptions(code); + session.lots = undefined; + return res; + }, +}); diff --git a/pos_lot_selection/static/src/js/ProductScreen.js b/pos_lot_selection/static/src/js/ProductScreen.js deleted file mode 100644 index 434475d4f8..0000000000 --- a/pos_lot_selection/static/src/js/ProductScreen.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - Copyright 2022 Camptocamp SA (https://www.camptocamp.com). - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -*/ -odoo.define("pos_lot_selection.ProductScreen", function (require) { - "use strict"; - - const ProductScreen = require("point_of_sale.ProductScreen"); - const Registries = require("point_of_sale.Registries"); - - const PosLotSaleProductScreen = (ProductScreen) => - class extends ProductScreen { - /** - * @override - */ - async _getAddProductOptions(product) { - if (["serial", "lot"].includes(product.tracking)) { - this.env.session.lots = await this.env.pos.getProductLots(product); - } - const res = await super._getAddProductOptions(...arguments); - this.env.session.lots = undefined; - return res; - } - }; - - Registries.Component.extend(ProductScreen, PosLotSaleProductScreen); - return ProductScreen; -}); diff --git a/pos_lot_selection/static/src/js/models.esm.js b/pos_lot_selection/static/src/js/models.esm.js new file mode 100644 index 0000000000..fe7b3f6fd8 --- /dev/null +++ b/pos_lot_selection/static/src/js/models.esm.js @@ -0,0 +1,25 @@ +/** @odoo-module */ + +/* + Copyright 2022 Camptocamp SA + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +*/ + +import {PosStore} from "@point_of_sale/app/store/pos_store"; +import {patch} from "@web/core/utils/patch"; +import {session} from "@web/session"; + +patch(PosStore.prototype, { + async getProductLots(product) { + try { + return await this.orm.silent.call( + "stock.lot", + "get_available_lots_for_pos", + [product.id, session.user_companies.current_company] + ); + } catch (error) { + console.error(error); + return []; + } + }, +}); diff --git a/pos_lot_selection/static/src/js/models.js b/pos_lot_selection/static/src/js/models.js deleted file mode 100644 index 0356f7a263..0000000000 --- a/pos_lot_selection/static/src/js/models.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2022 Camptocamp SA - License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) -*/ -odoo.define("pos_lot_selection.models", function (require) { - "use strict"; - - const {PosGlobalState} = require("point_of_sale.models"); - const Registries = require("point_of_sale.Registries"); - - const LotSelectPosGlobalState = (PosGlobalState) => - class extends PosGlobalState { - async getProductLots(product) { - try { - return await this.env.services.rpc( - { - model: "stock.lot", - method: "get_available_lots_for_pos", - kwargs: { - product_id: product.id, - company_id: this.env.session.company_id, - }, - }, - {shadow: true} - ); - } catch (error) { - console.error(error); - return []; - } - } - }; - - Registries.Model.extend(PosGlobalState, LotSelectPosGlobalState); - return PosGlobalState; -}); diff --git a/pos_lot_selection/static/tests/tours/LotSelection.tour.esm.js b/pos_lot_selection/static/tests/tours/LotSelection.tour.esm.js new file mode 100644 index 0000000000..096a88a881 --- /dev/null +++ b/pos_lot_selection/static/tests/tours/LotSelection.tour.esm.js @@ -0,0 +1,67 @@ +/** @odoo-module */ + +import * as Chrome from "@point_of_sale/../tests/tours/helpers/ChromeTourMethods"; +import * as ReceiptScreen from "@point_of_sale/../tests/tours/helpers/ReceiptScreenTourMethods"; +import * as PaymentScreen from "@point_of_sale/../tests/tours/helpers/PaymentScreenTourMethods"; +import * as ProductScreen from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods"; +import {registry} from "@web/core/registry"; + +export function selectLotNumber(number) { + return [ + { + content: `check lot '${number}'`, + trigger: ".list-line-input:first()", + run: () => { + const lot = $(`datalist option:contains("${number}")`); + if (lot.length === 0) { + throw new Error(`Lot ${number} not found`); + } + }, + }, + { + content: "set lot input", + trigger: ".list-line-input:first()", + run: "text " + number, + }, + { + content: "click validate lot number", + trigger: ".popup .button.confirm", + }, + ]; +} + +registry.category("web_tour.tours").add("LotSelectionTour", { + test: true, + url: "/pos/ui", + steps: () => + [ + ProductScreen.confirmOpeningPopup(), + ProductScreen.clickHomeCategory(), + ProductScreen.clickDisplayedProduct("Lot Product 1"), + selectLotNumber("10120000515"), + ProductScreen.selectedOrderlineHas("Lot Product 1"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Cash"), + PaymentScreen.clickValidate(), + ReceiptScreen.trackingMethodIsLot(), + Chrome.endTour(), + ].flat(), +}); + +registry.category("web_tour.tours").add("ClickLotIconTour", { + test: true, + url: "/pos/ui", + steps: () => + [ + ProductScreen.clickHomeCategory(), + ProductScreen.clickDisplayedProduct("Lot Product 1"), + ProductScreen.enterLotNumber("10120000515"), + ProductScreen.clickLotIcon(), + selectLotNumber("10120000516"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Cash"), + PaymentScreen.clickValidate(), + ReceiptScreen.trackingMethodIsLot(), + Chrome.endTour(), + ].flat(), +}); diff --git a/pos_lot_selection/tests/__init__.py b/pos_lot_selection/tests/__init__.py new file mode 100644 index 0000000000..ab211c0007 --- /dev/null +++ b/pos_lot_selection/tests/__init__.py @@ -0,0 +1 @@ +from . import test_frontend diff --git a/pos_lot_selection/tests/test_frontend.py b/pos_lot_selection/tests/test_frontend.py new file mode 100644 index 0000000000..0ac59f6d7c --- /dev/null +++ b/pos_lot_selection/tests/test_frontend.py @@ -0,0 +1,69 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo.tests + +from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon + + +@odoo.tests.tagged("post_install", "-at_install") +class TestLotScanning(TestPointOfSaleHttpCommon): + @classmethod + def setUpClass(cls, chart_template_ref=None): + super().setUpClass(chart_template_ref=chart_template_ref) + + cls.lot_product_1 = cls.env["product.product"].create( + { + "name": "Lot Product 1", + "type": "product", + "tracking": "lot", + "categ_id": cls.env.ref("product.product_category_all").id, + "available_in_pos": True, + } + ) + lots = cls.env["stock.lot"].create( + [ + { + "name": "10120000515", + "product_id": cls.lot_product_1.id, + "company_id": cls.env.company.id, + }, + { + "name": "10120000516", + "product_id": cls.lot_product_1.id, + "company_id": cls.env.company.id, + }, + ] + ) + location_id = cls.main_pos_config.picking_type_id.default_location_src_id.id + cls.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": cls.lot_product_1.id, + "inventory_quantity": 100, + "location_id": location_id, + "lot_id": lots[0].id, + } + ).action_apply_inventory() + cls.env["stock.quant"].with_context(inventory_mode=True).create( + { + "product_id": cls.lot_product_1.id, + "inventory_quantity": 100, + "location_id": location_id, + "lot_id": lots[1].id, + } + ).action_apply_inventory() + + def test_lot_selection(self): + self.main_pos_config.with_user(self.pos_user).open_ui() + self.start_tour( + "/pos/ui?config_id=%d" % self.main_pos_config.id, + "LotSelectionTour", + login="pos_user", + ) + + def test_click_lot_icon(self): + self.main_pos_config.with_user(self.pos_user).open_ui() + self.start_tour( + "/pos/ui?config_id=%d" % self.main_pos_config.id, + "ClickLotIconTour", + login="pos_user", + )