From 7eab1d3a5c1feeee7bc988d20f8ed3413ed274bd Mon Sep 17 00:00:00 2001 From: Sandip-OSI Date: Wed, 28 Dec 2022 11:40:13 +0530 Subject: [PATCH 01/12] [16.0][ADD] pos_sale_product_config_no_variant --- pos_sale_product_config_no_variant/README.rst | 98 ++++ .../__init__.py | 4 + .../__manifest__.py | 21 + .../models/__init__.py | 6 + .../models/pos_order.py | 17 + .../models/pos_order_line.py | 61 +++ .../models/pos_session.py | 15 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/CREDITS.rst | 3 + .../readme/DESCRIPTION.rst | 1 + .../readme/USAGE.rst | 6 + .../static/description/icon.png | Bin 0 -> 28332 bytes .../static/description/index.html | 440 ++++++++++++++++++ .../static/src/js/OrderLines.js | 37 ++ .../static/src/js/PosProductConfig.js | 50 ++ .../static/src/js/ProductScreen.js | 129 +++++ .../views/pos_order_views.xml | 20 + 17 files changed, 909 insertions(+) create mode 100644 pos_sale_product_config_no_variant/README.rst create mode 100644 pos_sale_product_config_no_variant/__init__.py create mode 100644 pos_sale_product_config_no_variant/__manifest__.py create mode 100644 pos_sale_product_config_no_variant/models/__init__.py create mode 100644 pos_sale_product_config_no_variant/models/pos_order.py create mode 100644 pos_sale_product_config_no_variant/models/pos_order_line.py create mode 100644 pos_sale_product_config_no_variant/models/pos_session.py create mode 100644 pos_sale_product_config_no_variant/readme/CONTRIBUTORS.rst create mode 100644 pos_sale_product_config_no_variant/readme/CREDITS.rst create mode 100644 pos_sale_product_config_no_variant/readme/DESCRIPTION.rst create mode 100644 pos_sale_product_config_no_variant/readme/USAGE.rst create mode 100644 pos_sale_product_config_no_variant/static/description/icon.png create mode 100644 pos_sale_product_config_no_variant/static/description/index.html create mode 100644 pos_sale_product_config_no_variant/static/src/js/OrderLines.js create mode 100644 pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js create mode 100644 pos_sale_product_config_no_variant/static/src/js/ProductScreen.js create mode 100644 pos_sale_product_config_no_variant/views/pos_order_views.xml diff --git a/pos_sale_product_config_no_variant/README.rst b/pos_sale_product_config_no_variant/README.rst new file mode 100644 index 0000000000..9bb359d0d2 --- /dev/null +++ b/pos_sale_product_config_no_variant/README.rst @@ -0,0 +1,98 @@ +===================================== +POS - Product Configurator No Variant +===================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/16.0/pos_sale_product_config_no_variant + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_sale_product_config_no_variant + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/pos&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allow user to see the selected attribute values into POS order line + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +#. Configure the product with variant creation mode no Variant +#. Open the POS Session add that product with attribute values +#. The POS Order will be created +#. the selected attribute values stored in to the POS order line 'Extra Values' list view optional hide field + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Open Source Integrators + +Contributors +~~~~~~~~~~~~ + +* Sandip Vyas + +Other credits +~~~~~~~~~~~~~ + +The development of this module has been financially supported by: + +* Open Source Integrators + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-ursais| image:: https://github.com/ursais.png?size=40px + :target: https://github.com/ursais + :alt: ursais + +Current `maintainer `__: + +|maintainer-ursais| + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_sale_product_config_no_variant/__init__.py b/pos_sale_product_config_no_variant/__init__.py new file mode 100644 index 0000000000..ecd4f0837c --- /dev/null +++ b/pos_sale_product_config_no_variant/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/pos_sale_product_config_no_variant/__manifest__.py b/pos_sale_product_config_no_variant/__manifest__.py new file mode 100644 index 0000000000..aadafa08c1 --- /dev/null +++ b/pos_sale_product_config_no_variant/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "POS - Product Configurator No Variant", + "version": "16.0.1.0.0", + "summary": "Manage Point Of Sale via Configurator of no variant", + "author": "Open Source Integrators, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Point of Sale", + "maintainer": "Open Source Integrators", + "website": "https://github.com/OCA/pos", + "depends": ["point_of_sale"], + "data": ["views/pos_order_views.xml"], + "maintainers": ["ursais"], + "installable": True, + "assets": { + "point_of_sale.assets": [ + "pos_sale_product_config_no_variant/static/src/js/**/*.js", + ], + }, +} diff --git a/pos_sale_product_config_no_variant/models/__init__.py b/pos_sale_product_config_no_variant/models/__init__.py new file mode 100644 index 0000000000..883d510f4e --- /dev/null +++ b/pos_sale_product_config_no_variant/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import pos_order_line +from . import pos_order +from . import pos_session diff --git a/pos_sale_product_config_no_variant/models/pos_order.py b/pos_sale_product_config_no_variant/models/pos_order.py new file mode 100644 index 0000000000..09b3a680ff --- /dev/null +++ b/pos_sale_product_config_no_variant/models/pos_order.py @@ -0,0 +1,17 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class PosOrder(models.Model): + _inherit = "pos.order" + + def _get_fields_for_order_line(self): + fields = super(PosOrder, self)._get_fields_for_order_line() + fields.extend( + [ + "product_no_variant_attribute_value_ids", + ] + ) + return fields diff --git a/pos_sale_product_config_no_variant/models/pos_order_line.py b/pos_sale_product_config_no_variant/models/pos_order_line.py new file mode 100644 index 0000000000..9d57b03eaa --- /dev/null +++ b/pos_sale_product_config_no_variant/models/pos_order_line.py @@ -0,0 +1,61 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class PosOrderLine(models.Model): + _inherit = "pos.order.line" + + product_no_variant_attribute_value_ids = fields.Many2many( + comodel_name="product.template.attribute.value", + string="Extra Values", + compute="_compute_no_variant_attribute_values", + store=True, + readonly=False, + precompute=True, + ondelete="restrict", + ) + + @api.depends("product_id") + def _compute_no_variant_attribute_values(self): + for line in self: + if not line.product_id: + line.product_no_variant_attribute_value_ids = False + continue + if not line.product_no_variant_attribute_value_ids: + continue + attribute_lines = ( + line.product_id.product_tmpl_id.valid_product_template_attribute_line_ids + ) + valid_values = attribute_lines.product_template_value_ids + # remove the no_variant attributes that don't belong to this template + for ptav in line.product_no_variant_attribute_value_ids: + if ptav._origin not in valid_values: + line.product_no_variant_attribute_value_ids -= ptav + + def get_product_attribute_value(self, attribute_values, product_tmpl_id): + return self.env["product.template.attribute.value"].search( + [ + ("product_attribute_value_id", "in", attribute_values), + ("product_tmpl_id", "=", product_tmpl_id.id), + ] + ) + + def _order_line_fields(self, line, session_id=None): + result = super()._order_line_fields(line, session_id) + vals = result[2] + if "product_no_variant_attribute_value_ids" in vals and vals.get( + "product_no_variant_attribute_value_ids" + ): + product_id = self.env["product.product"].browse(vals.get("product_id")) + vals["product_no_variant_attribute_value_ids"] = [ + [ + 6, + False, + self.get_product_attribute_value( + vals.get("product_no_variant_attribute_value_ids"), + product_id.product_tmpl_id, + ).mapped(lambda value: value.id), + ] + ] + return result diff --git a/pos_sale_product_config_no_variant/models/pos_session.py b/pos_sale_product_config_no_variant/models/pos_session.py new file mode 100644 index 0000000000..67ca13f7b6 --- /dev/null +++ b/pos_sale_product_config_no_variant/models/pos_session.py @@ -0,0 +1,15 @@ +# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class PosSession(models.Model): + _inherit = "pos.session" + + def _loader_params_pos_order_line(self): + result = super()._loader_params_product_attribute_value() + result["search_params"]["fields"].append( + "product_no_variant_attribute_value_ids" + ) + return result diff --git a/pos_sale_product_config_no_variant/readme/CONTRIBUTORS.rst b/pos_sale_product_config_no_variant/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..b7be22b0cc --- /dev/null +++ b/pos_sale_product_config_no_variant/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Sandip Vyas diff --git a/pos_sale_product_config_no_variant/readme/CREDITS.rst b/pos_sale_product_config_no_variant/readme/CREDITS.rst new file mode 100644 index 0000000000..0eff0acf4e --- /dev/null +++ b/pos_sale_product_config_no_variant/readme/CREDITS.rst @@ -0,0 +1,3 @@ +The development of this module has been financially supported by: + +* Open Source Integrators diff --git a/pos_sale_product_config_no_variant/readme/DESCRIPTION.rst b/pos_sale_product_config_no_variant/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..77ab31a148 --- /dev/null +++ b/pos_sale_product_config_no_variant/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module allow user to see the selected attribute values into POS order line diff --git a/pos_sale_product_config_no_variant/readme/USAGE.rst b/pos_sale_product_config_no_variant/readme/USAGE.rst new file mode 100644 index 0000000000..4b21b3d5e1 --- /dev/null +++ b/pos_sale_product_config_no_variant/readme/USAGE.rst @@ -0,0 +1,6 @@ +To use this module, you need to: + +#. Configure the product with variant creation mode no Variant +#. Open the POS Session add that product with attribute values +#. The POS Order will be created +#. the selected attribute values stored in to the POS order line 'Extra Values' list view optional hide field diff --git a/pos_sale_product_config_no_variant/static/description/icon.png b/pos_sale_product_config_no_variant/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..85d01763f361c30600d58dc27612405c1827fa00 GIT binary patch literal 28332 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 XbJh3;7b);{a{ydLTe<9sW!V1%857^I literal 0 HcmV?d00001 diff --git a/pos_sale_product_config_no_variant/static/description/index.html b/pos_sale_product_config_no_variant/static/description/index.html new file mode 100644 index 0000000000..66eaa8dacb --- /dev/null +++ b/pos_sale_product_config_no_variant/static/description/index.html @@ -0,0 +1,440 @@ + + + + + + +POS - Product Configurator No Variant + + + +
+

POS - Product Configurator No Variant

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

+

This module allow user to see the selected attribute values into POS order line

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Configure the product with variant creation mode no Variant
  2. +
  3. Open the POS Session add that product with attribute values
  4. +
  5. The POS Order will be created
  6. +
  7. the selected attribute values stored in to the POS order line ‘Extra Values’ list view optional hide field
  8. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Open Source Integrators
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

ursais

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_sale_product_config_no_variant/static/src/js/OrderLines.js b/pos_sale_product_config_no_variant/static/src/js/OrderLines.js new file mode 100644 index 0000000000..de96f08df5 --- /dev/null +++ b/pos_sale_product_config_no_variant/static/src/js/OrderLines.js @@ -0,0 +1,37 @@ +odoo.define( + "pos_sale_product_config_no_variant.PosNoVariantOrderline", + function (require) { + "use strict"; + + const ProductConfiguratorPopup = + require("point_of_sale.ProductConfiguratorPopup").ProductConfiguratorPopup; + const Registries = require("point_of_sale.Registries"); + const {useSubEnv} = owl; + + const ProductConfiguratorNoVariantPopup = (ProductConfiguratorPopup) => + class extends ProductConfiguratorPopup { + setup() { + super.setup(); + useSubEnv({product_no_variant_attribute_value_ids: []}); + } + getPayload() { + const results = super.getPayload(); + const product_no_variant_attribute_value_ids = + this.env.attribute_components.map((attribute) => + parseInt(attribute.state.selected_value) + ); + return Object.assign(results, { + product_no_variant_attribute_value_ids: + product_no_variant_attribute_value_ids, + }); + } + }; + + Registries.Component.extend( + ProductConfiguratorPopup, + ProductConfiguratorNoVariantPopup + ); + + return ProductConfiguratorPopup; + } +); diff --git a/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js b/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js new file mode 100644 index 0000000000..8b2d3f6243 --- /dev/null +++ b/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js @@ -0,0 +1,50 @@ +odoo.define("pos_sale_product_config_no_variant.Orderline", function (require) { + "use strict"; + + const {Order, Orderline} = require("point_of_sale.models"); + const Registries = require("point_of_sale.Registries"); + + const PosNoVariantOrderline = (Orderline) => + class PosNoVariantOrderline extends Orderline { + constructor(obj, options) { + super(...arguments); + this.product_no_variant_attribute_value_ids = + options.product_no_variant_attribute_value_ids || []; + } + export_as_JSON() { + const result = super.export_as_JSON(...arguments); + result.product_no_variant_attribute_value_ids = _.map( + this.product_no_variant_attribute_value_ids, + (value) => parseInt(value) + ); + return result; + } + init_from_JSON(json) { + if (json.product_no_variant_attribute_value_ids) { + this.product_no_variant_attribute_value_ids = + json.product_no_variant_attribute_value_ids && + json.product_no_variant_attribute_value_ids.length !== 0 + ? json.product_no_variant_attribute_value_ids[0][2] + : undefined; + } + super.init_from_JSON(...arguments); + } + }; + Registries.Model.extend(Orderline, PosNoVariantOrderline); + + const PosNoVariantOrder = (Order) => + class PosNoVariantOrder extends Order { + constructor() { + super(...arguments); + } + set_orderline_options(line, options) { + super.set_orderline_options(...arguments); + if (options && options.product_no_variant_attribute_value_ids) { + line.product_no_variant_attribute_value_ids = + options.product_no_variant_attribute_value_ids; + } + } + }; + + Registries.Model.extend(Order, PosNoVariantOrder); +}); diff --git a/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js b/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js new file mode 100644 index 0000000000..d0fa19d8d8 --- /dev/null +++ b/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js @@ -0,0 +1,129 @@ +/* eslint-disable */ +odoo.define("pos_sale_product_config_no_variant.ProductScreen", function (require) { + "use strict"; + + const ProductScreen = require("point_of_sale.ProductScreen"); + const Registries = require("point_of_sale.Registries"); + + const PosNoVariantProductScreen = (ProductScreen) => + class extends ProductScreen { + async _getAddProductOptions(product, base_code) { + let price_extra = 0.0; + let draftPackLotLines, weight, description, packLotLinesToEdit; + let product_no_variant_attribute_value_ids = []; + + 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.showPopup( + "ProductConfiguratorPopup", + { + product: product, + attributes: attributes, + } + ); + + if (confirmed) { + description = payload.selected_attributes.join(", "); + price_extra += payload.price_extra; + product_no_variant_attribute_value_ids = + payload.product_no_variant_attribute_value_ids; + } 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.showPopup("EditListPopup", { + title: this.env._t("Lot/Serial Number(s) Required"), + 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.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, + product_no_variant_attribute_value_ids, + }; + } + }; + + Registries.Component.extend(ProductScreen, PosNoVariantProductScreen); + + return ProductScreen; +}); diff --git a/pos_sale_product_config_no_variant/views/pos_order_views.xml b/pos_sale_product_config_no_variant/views/pos_order_views.xml new file mode 100644 index 0000000000..7e116996fe --- /dev/null +++ b/pos_sale_product_config_no_variant/views/pos_order_views.xml @@ -0,0 +1,20 @@ + + + + pos.order.form.pos.config.no.variant + pos.order + + + + + + + + From 512ff9d17897eb5e018a26fdb79fc92b18cf889c Mon Sep 17 00:00:00 2001 From: oca-ci Date: Tue, 23 Jan 2024 17:13:23 +0000 Subject: [PATCH 02/12] [UPD] Update pos_sale_product_config_no_variant.pot --- .../pos_sale_product_config_no_variant.pot | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pos_sale_product_config_no_variant/i18n/pos_sale_product_config_no_variant.pot diff --git a/pos_sale_product_config_no_variant/i18n/pos_sale_product_config_no_variant.pot b/pos_sale_product_config_no_variant/i18n/pos_sale_product_config_no_variant.pot new file mode 100644 index 0000000000..b5d4ce5baf --- /dev/null +++ b/pos_sale_product_config_no_variant/i18n/pos_sale_product_config_no_variant.pot @@ -0,0 +1,41 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_sale_product_config_no_variant +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_sale_product_config_no_variant +#: model:ir.model.fields,field_description:pos_sale_product_config_no_variant.field_pos_order_line__product_no_variant_attribute_value_ids +msgid "Extra Values" +msgstr "" + +#. module: pos_sale_product_config_no_variant +#. odoo-javascript +#: code:addons/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js:0 +#, python-format +msgid "Lot/Serial Number(s) Required" +msgstr "" + +#. module: pos_sale_product_config_no_variant +#: model:ir.model,name:pos_sale_product_config_no_variant.model_pos_order_line +msgid "Point of Sale Order Lines" +msgstr "" + +#. module: pos_sale_product_config_no_variant +#: model:ir.model,name:pos_sale_product_config_no_variant.model_pos_order +msgid "Point of Sale Orders" +msgstr "" + +#. module: pos_sale_product_config_no_variant +#: model:ir.model,name:pos_sale_product_config_no_variant.model_pos_session +msgid "Point of Sale Session" +msgstr "" From 5252f5d6534b8e52491e597546cdcfb11f4665b2 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 23 Jan 2024 17:17:43 +0000 Subject: [PATCH 03/12] [BOT] post-merge updates --- pos_sale_product_config_no_variant/README.rst | 11 +++-- .../static/description/index.html | 43 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/pos_sale_product_config_no_variant/README.rst b/pos_sale_product_config_no_variant/README.rst index 9bb359d0d2..b9b61302d8 100644 --- a/pos_sale_product_config_no_variant/README.rst +++ b/pos_sale_product_config_no_variant/README.rst @@ -2,10 +2,13 @@ POS - Product Configurator No Variant ===================================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e9db6f392ad90bfd816fd3f254e840afe2744217f77bc439dacdab2afa7a1d48 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -20,10 +23,10 @@ POS - Product Configurator No Variant :target: https://translation.odoo-community.org/projects/pos-16-0/pos-16-0-pos_sale_product_config_no_variant :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/pos&target_branch=16.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/pos&target_branch=16.0 :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module allow user to see the selected attribute values into POS order line @@ -47,7 +50,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. -If you spotted it first, help us smashing it by providing a detailed and welcomed +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/pos_sale_product_config_no_variant/static/description/index.html b/pos_sale_product_config_no_variant/static/description/index.html index 66eaa8dacb..ec01937aee 100644 --- a/pos_sale_product_config_no_variant/static/description/index.html +++ b/pos_sale_product_config_no_variant/static/description/index.html @@ -1,20 +1,19 @@ - - + POS - Product Configurator No Variant -
-

POS - Product Configurator No Variant

+
+ + +Odoo Community Association + +
+

POS - Product Configurator No Variant

-

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

-

This module allow user to see the selected attribute values into POS order line

+

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

+

This module allow user to see the selected attribute values into POS +order line

Table of contents

    @@ -386,46 +392,48 @@

    POS - Product Configurator No Variant

-

Usage

+

Usage

To use this module, you need to:

  1. Configure the product with variant creation mode no Variant
  2. Open the POS Session add that product with attribute values
  3. The POS Order will be created
  4. -
  5. the selected attribute values stored in to the POS order line ‘Extra Values’ list view optional hide field
  6. +
  7. the selected attribute values stored in to the POS order line ‘Extra +Values’ list view optional hide field
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Open Source Integrators
-

Other credits

+

Other credits

The development of this module has been financially supported by:

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -435,10 +443,11 @@

Maintainers

promote its widespread use.

Current maintainer:

ursais

-

This module is part of the OCA/pos project on GitHub.

+

This module is part of the OCA/pos project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
From 003171554ef9acfe5d78e0ac1e9e82b602c94f1a Mon Sep 17 00:00:00 2001 From: stferraro Date: Wed, 29 Apr 2026 10:38:12 -0400 Subject: [PATCH 12/12] [MIG] pos_sale_product_config_no_variant: Migration to 19.0 --- .../__manifest__.py | 7 +- .../migration19.0/migration.md | 120 ++++++++++++++++ .../models/__init__.py | 2 - .../models/pos_order.py | 17 --- .../models/pos_order_line.py | 51 ++----- .../models/pos_session.py | 15 -- .../static/src/js/OrderLines.js | 37 ----- .../static/src/js/PosProductConfig.js | 47 ------- .../static/src/js/ProductScreen.js | 129 ----------------- .../tests/__init__.py | 1 + .../tests/test_pos_order_line.py | 132 ++++++++++++++++++ .../views/pos_order_views.xml | 2 +- 12 files changed, 267 insertions(+), 293 deletions(-) create mode 100644 pos_sale_product_config_no_variant/migration19.0/migration.md delete mode 100644 pos_sale_product_config_no_variant/models/pos_order.py delete mode 100644 pos_sale_product_config_no_variant/models/pos_session.py delete mode 100644 pos_sale_product_config_no_variant/static/src/js/OrderLines.js delete mode 100644 pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js delete mode 100644 pos_sale_product_config_no_variant/static/src/js/ProductScreen.js create mode 100644 pos_sale_product_config_no_variant/tests/__init__.py create mode 100644 pos_sale_product_config_no_variant/tests/test_pos_order_line.py diff --git a/pos_sale_product_config_no_variant/__manifest__.py b/pos_sale_product_config_no_variant/__manifest__.py index 352bcf7396..3f5eb92952 100644 --- a/pos_sale_product_config_no_variant/__manifest__.py +++ b/pos_sale_product_config_no_variant/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { "name": "POS - Product Configurator No Variant", - "version": "16.0.1.0.1", + "version": "19.0.1.0.0", "summary": "Manage Point Of Sale via Configurator of no variant", "author": "Open Source Integrators, Odoo Community Association (OCA)", "license": "AGPL-3", @@ -13,9 +13,4 @@ "data": ["views/pos_order_views.xml"], "maintainers": ["ursais"], "installable": True, - "assets": { - "point_of_sale.assets": [ - "pos_sale_product_config_no_variant/static/src/js/**/*.js", - ], - }, } diff --git a/pos_sale_product_config_no_variant/migration19.0/migration.md b/pos_sale_product_config_no_variant/migration19.0/migration.md new file mode 100644 index 0000000000..ea3412c82a --- /dev/null +++ b/pos_sale_product_config_no_variant/migration19.0/migration.md @@ -0,0 +1,120 @@ +# Migration Notes - Odoo 19.0 + +## Scope + +This document describes the migration of `pos_sale_product_config_no_variant` from a +legacy POS integration pattern (Odoo 16 style) to the Odoo 19 POS architecture while +preserving functional behavior. + +## Functional Goal Kept + +The module still ensures that **no-variant attribute values selected in POS** are +available on `pos.order.line` and visible in backend order lines through the **Extra +Values** field. + +## Why Changes Were Needed + +Odoo 19 POS uses a different frontend and data-loading architecture: + +- Legacy frontend extension style (`odoo.define`, `Registries`) is obsolete for this use + case. +- Legacy backend hooks used by older POS flows are no longer valid in Odoo 19: + - `_order_line_fields` + - `_get_fields_for_order_line` + - `_loader_params_*` style hooks used in this module +- Odoo 19 already provides native attribute propagation through `attribute_value_ids` on + `pos.order.line`. + +## Implemented Changes + +### 1) Backend model logic aligned with Odoo 19 + +Updated `models/pos_order_line.py`: + +- Kept `product_no_variant_attribute_value_ids` as a stored computed field. +- Reworked compute dependencies to rely on native Odoo 19 data: + - `@api.depends("product_id", "attribute_value_ids")` +- Compute now derives values from `attribute_value_ids` filtered by: + - same product template + - `create_variant == "no_variant"` + +This removes custom parsing/normalization logic that is no longer required in Odoo 19. + +### 2) Removed obsolete backend overrides + +Deleted files no longer valid in Odoo 19: + +- `models/pos_order.py` +- `models/pos_session.py` + +Updated `models/__init__.py` accordingly. + +### 3) Removed legacy POS JS patches + +Deleted legacy JS files: + +- `static/src/js/ProductScreen.js` +- `static/src/js/OrderLines.js` +- `static/src/js/PosProductConfig.js` + +Reason: Odoo 19 already handles configurator payload and selected attribute values +natively in POS app models/services. + +### 4) Manifest cleanup + +Updated `__manifest__.py`: + +- Removed obsolete POS asset bundle declaration for legacy JS patches. +- Kept only required module metadata and backend view data. + +## Compatibility and Behavior + +- The feature remains available from a business perspective: users can still see + selected no-variant values in POS order lines (`Extra Values`). +- The implementation now follows Odoo 19 native mechanisms, reducing technical debt and + migration risk. + +## Validation Performed + +- Static code validation reported no errors after cleanup. +- Repository status confirmed expected file modifications/deletions. + +## Automated Tests Added (TransactionCase) + +A dedicated TransactionCase test suite was added to validate the compute logic in an +Odoo 19-compatible way: + +- `tests/test_pos_order_line.py` + +Test design: + +- Uses `setUpClass` to create all required fixtures (attributes, attribute values, + product templates, PTAVs). +- Verifies `_compute_no_variant_attribute_values` keeps only: + - values from the same product template + - values with `create_variant == "no_variant"` +- Verifies compute result is empty when `product_id` is not set. + +This test scope is focused on functional correctness of the migrated compute path for +`pos.order.line`. + +## Coverage Objective + +The target is to push module coverage close to 97% for the migrated logic area. Exact +global coverage depends on the full project test matrix execution and coverage tooling +configuration in the target CI environment. + +## Recommended Runtime Validation + +1. Upgrade the module in a test database. +2. Create/use a product template with attributes configured as `no_variant`. +3. In POS, add product and select attribute values. +4. Confirm order and inspect backend POS order line. +5. Verify `Extra Values` contains the selected no-variant attribute values. + +## Notes for Future Maintenance + +- Prefer extending Odoo 19 POS through native model/serialization flow before + introducing custom frontend patches. +- Keep this module focused on backend visibility/reporting of no-variant values, not on + replacing core configurator behavior. diff --git a/pos_sale_product_config_no_variant/models/__init__.py b/pos_sale_product_config_no_variant/models/__init__.py index 883d510f4e..0bf765fae9 100644 --- a/pos_sale_product_config_no_variant/models/__init__.py +++ b/pos_sale_product_config_no_variant/models/__init__.py @@ -2,5 +2,3 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import pos_order_line -from . import pos_order -from . import pos_session diff --git a/pos_sale_product_config_no_variant/models/pos_order.py b/pos_sale_product_config_no_variant/models/pos_order.py deleted file mode 100644 index 644c7d3574..0000000000 --- a/pos_sale_product_config_no_variant/models/pos_order.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import models - - -class PosOrder(models.Model): - _inherit = "pos.order" - - def _get_fields_for_order_line(self): - fields = super()._get_fields_for_order_line() - fields.extend( - [ - "product_no_variant_attribute_value_ids", - ] - ) - return fields diff --git a/pos_sale_product_config_no_variant/models/pos_order_line.py b/pos_sale_product_config_no_variant/models/pos_order_line.py index c29535ac1f..bdd33a77e8 100644 --- a/pos_sale_product_config_no_variant/models/pos_order_line.py +++ b/pos_sale_product_config_no_variant/models/pos_order_line.py @@ -8,52 +8,25 @@ class PosOrderLine(models.Model): product_no_variant_attribute_value_ids = fields.Many2many( comodel_name="product.template.attribute.value", + relation="pos_order_line_no_variant_ptav_rel", + column1="order_line_id", + column2="ptav_id", string="Extra Values", compute="_compute_no_variant_attribute_values", store=True, - readonly=False, - precompute=True, - ondelete="restrict", + readonly=True, ) - @api.depends("product_id") + @api.depends("product_id", "attribute_value_ids") def _compute_no_variant_attribute_values(self): for line in self: if not line.product_id: line.product_no_variant_attribute_value_ids = False continue - if not line.product_no_variant_attribute_value_ids: - continue - attribute_lines = line.product_id.product_tmpl_id.valid_product_template_attribute_line_ids - valid_values = attribute_lines.product_template_value_ids - # remove the no_variant attributes that don't belong to this template - for ptav in line.product_no_variant_attribute_value_ids: - if ptav._origin not in valid_values: - line.product_no_variant_attribute_value_ids -= ptav - - def get_product_attribute_value(self, attribute_values, product_tmpl_id): - return self.env["product.template.attribute.value"].search( - [ - ("product_attribute_value_id", "in", attribute_values), - ("product_tmpl_id", "=", product_tmpl_id.id), - ] - ) - - def _order_line_fields(self, line, session_id=None): - result = super()._order_line_fields(line, session_id) - vals = result[2] - if "product_no_variant_attribute_value_ids" in vals and vals.get( - "product_no_variant_attribute_value_ids" - ): - product_id = self.env["product.product"].browse(vals.get("product_id")) - vals["product_no_variant_attribute_value_ids"] = [ - [ - 6, - False, - self.get_product_attribute_value( - vals.get("product_no_variant_attribute_value_ids"), - product_id.product_tmpl_id, - ).mapped(lambda value: value.id), - ] - ] - return result + template = line.product_id.product_tmpl_id + line.product_no_variant_attribute_value_ids = ( + line.attribute_value_ids.filtered( + lambda ptav, template=template: ptav.product_tmpl_id == template + and ptav.attribute_id.create_variant == "no_variant" + ) + ) diff --git a/pos_sale_product_config_no_variant/models/pos_session.py b/pos_sale_product_config_no_variant/models/pos_session.py deleted file mode 100644 index 67ca13f7b6..0000000000 --- a/pos_sale_product_config_no_variant/models/pos_session.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (C) 2022 Open Source Integrators (https://www.opensourceintegrators.com) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import models - - -class PosSession(models.Model): - _inherit = "pos.session" - - def _loader_params_pos_order_line(self): - result = super()._loader_params_product_attribute_value() - result["search_params"]["fields"].append( - "product_no_variant_attribute_value_ids" - ) - return result diff --git a/pos_sale_product_config_no_variant/static/src/js/OrderLines.js b/pos_sale_product_config_no_variant/static/src/js/OrderLines.js deleted file mode 100644 index 407142a1a2..0000000000 --- a/pos_sale_product_config_no_variant/static/src/js/OrderLines.js +++ /dev/null @@ -1,37 +0,0 @@ -odoo.define( - "pos_sale_product_config_no_variant.PosNoVariantOrderline", - function (require) { - "use strict"; - - const ProductConfiguratorPopup = - require("point_of_sale.ProductConfiguratorPopup").ProductConfiguratorPopup; - const Registries = require("point_of_sale.Registries"); - const {useSubEnv} = owl; - - const ProductConfiguratorNoVariantPopup = (ProductConfiguratorPopup) => - class extends ProductConfiguratorPopup { - setup() { - super.setup(); - useSubEnv({product_no_variant_attribute_value_ids: []}); - } - getPayload() { - const results = super.getPayload(); - const product_no_variant_attribute_value_ids = - this.env.attribute_components.map((attribute) => - parseInt(attribute.state.selected_value, 10) - ); - return Object.assign(results, { - product_no_variant_attribute_value_ids: - product_no_variant_attribute_value_ids, - }); - } - }; - - Registries.Component.extend( - ProductConfiguratorPopup, - ProductConfiguratorNoVariantPopup - ); - - return ProductConfiguratorPopup; - } -); diff --git a/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js b/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js deleted file mode 100644 index dae4a6621b..0000000000 --- a/pos_sale_product_config_no_variant/static/src/js/PosProductConfig.js +++ /dev/null @@ -1,47 +0,0 @@ -odoo.define("pos_sale_product_config_no_variant.Orderline", function (require) { - "use strict"; - - const {Order, Orderline} = require("point_of_sale.models"); - const Registries = require("point_of_sale.Registries"); - - const PosNoVariantOrderline = (Orderline) => - class PosNoVariantOrderline extends Orderline { - constructor(obj, options) { - super(...arguments); - this.product_no_variant_attribute_value_ids = - options.product_no_variant_attribute_value_ids || []; - } - export_as_JSON() { - const result = super.export_as_JSON(...arguments); - result.product_no_variant_attribute_value_ids = _.map( - this.product_no_variant_attribute_value_ids, - (value) => parseInt(value, 10) - ); - return result; - } - init_from_JSON(json) { - if (json.product_no_variant_attribute_value_ids) { - this.product_no_variant_attribute_value_ids = - json.product_no_variant_attribute_value_ids && - json.product_no_variant_attribute_value_ids.length !== 0 - ? json.product_no_variant_attribute_value_ids[0][2] - : undefined; - } - super.init_from_JSON(...arguments); - } - }; - Registries.Model.extend(Orderline, PosNoVariantOrderline); - - const PosNoVariantOrder = (Order) => - class PosNoVariantOrder extends Order { - set_orderline_options(line, options) { - super.set_orderline_options(...arguments); - if (options && options.product_no_variant_attribute_value_ids) { - line.product_no_variant_attribute_value_ids = - options.product_no_variant_attribute_value_ids; - } - } - }; - - Registries.Model.extend(Order, PosNoVariantOrder); -}); diff --git a/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js b/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js deleted file mode 100644 index d0fa19d8d8..0000000000 --- a/pos_sale_product_config_no_variant/static/src/js/ProductScreen.js +++ /dev/null @@ -1,129 +0,0 @@ -/* eslint-disable */ -odoo.define("pos_sale_product_config_no_variant.ProductScreen", function (require) { - "use strict"; - - const ProductScreen = require("point_of_sale.ProductScreen"); - const Registries = require("point_of_sale.Registries"); - - const PosNoVariantProductScreen = (ProductScreen) => - class extends ProductScreen { - async _getAddProductOptions(product, base_code) { - let price_extra = 0.0; - let draftPackLotLines, weight, description, packLotLinesToEdit; - let product_no_variant_attribute_value_ids = []; - - 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.showPopup( - "ProductConfiguratorPopup", - { - product: product, - attributes: attributes, - } - ); - - if (confirmed) { - description = payload.selected_attributes.join(", "); - price_extra += payload.price_extra; - product_no_variant_attribute_value_ids = - payload.product_no_variant_attribute_value_ids; - } 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.showPopup("EditListPopup", { - title: this.env._t("Lot/Serial Number(s) Required"), - 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.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, - product_no_variant_attribute_value_ids, - }; - } - }; - - Registries.Component.extend(ProductScreen, PosNoVariantProductScreen); - - return ProductScreen; -}); diff --git a/pos_sale_product_config_no_variant/tests/__init__.py b/pos_sale_product_config_no_variant/tests/__init__.py new file mode 100644 index 0000000000..d490259523 --- /dev/null +++ b/pos_sale_product_config_no_variant/tests/__init__.py @@ -0,0 +1 @@ +from . import test_pos_order_line diff --git a/pos_sale_product_config_no_variant/tests/test_pos_order_line.py b/pos_sale_product_config_no_variant/tests/test_pos_order_line.py new file mode 100644 index 0000000000..4ef9ac8980 --- /dev/null +++ b/pos_sale_product_config_no_variant/tests/test_pos_order_line.py @@ -0,0 +1,132 @@ +from odoo.tests import TransactionCase + + +class TestPosOrderLineNoVariantCompute(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.PosOrderLine = cls.env["pos.order.line"] + + cls.attr_no_variant = cls.env["product.attribute"].create( + { + "name": "Engraving", + "create_variant": "no_variant", + } + ) + cls.attr_always = cls.env["product.attribute"].create( + { + "name": "Color", + "create_variant": "always", + } + ) + + cls.value_no_variant_1 = cls.env["product.attribute.value"].create( + { + "name": "Front", + "attribute_id": cls.attr_no_variant.id, + } + ) + cls.value_no_variant_2 = cls.env["product.attribute.value"].create( + { + "name": "Back", + "attribute_id": cls.attr_no_variant.id, + } + ) + cls.value_always = cls.env["product.attribute.value"].create( + { + "name": "Red", + "attribute_id": cls.attr_always.id, + } + ) + + cls.template_main = cls.env["product.template"].create( + { + "name": "Custom Mug", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": cls.attr_no_variant.id, + "value_ids": [ + ( + 6, + 0, + [ + cls.value_no_variant_1.id, + cls.value_no_variant_2.id, + ], + ) + ], + }, + ), + ( + 0, + 0, + { + "attribute_id": cls.attr_always.id, + "value_ids": [(6, 0, [cls.value_always.id])], + }, + ), + ], + } + ) + + cls.template_other = cls.env["product.template"].create( + { + "name": "Sticker", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": cls.attr_no_variant.id, + "value_ids": [(6, 0, [cls.value_no_variant_1.id])], + }, + ) + ], + } + ) + + cls.product_main = cls.template_main.product_variant_ids[:1] + + cls.main_no_variant_ptav = cls.template_main.attribute_line_ids.filtered( + lambda line: line.attribute_id == cls.attr_no_variant + ).product_template_value_ids[:1] + cls.main_always_ptav = cls.template_main.attribute_line_ids.filtered( + lambda line: line.attribute_id == cls.attr_always + ).product_template_value_ids[:1] + cls.other_no_variant_ptav = cls.template_other.attribute_line_ids.filtered( + lambda line: line.attribute_id == cls.attr_no_variant + ).product_template_value_ids[:1] + + def test_compute_keeps_only_no_variant_values_for_same_template(self): + line = self.PosOrderLine.new( + { + "product_id": self.product_main.id, + } + ) + line.attribute_value_ids = ( + self.main_no_variant_ptav + | self.main_always_ptav + | self.other_no_variant_ptav + ) + + line._compute_no_variant_attribute_values() + + self.assertSetEqual( + set(line.product_no_variant_attribute_value_ids._origin.ids), + set(self.main_no_variant_ptav.ids), + "Only no-variant values from the same template must be kept.", + ) + + def test_compute_returns_empty_when_product_is_missing(self): + line = self.PosOrderLine.new({}) + line.attribute_value_ids = self.main_no_variant_ptav + + line._compute_no_variant_attribute_values() + + self.assertFalse( + line.product_no_variant_attribute_value_ids, + "Computed no-variant values must be empty when no product is set.", + ) diff --git a/pos_sale_product_config_no_variant/views/pos_order_views.xml b/pos_sale_product_config_no_variant/views/pos_order_views.xml index 7e116996fe..e195247ae7 100644 --- a/pos_sale_product_config_no_variant/views/pos_order_views.xml +++ b/pos_sale_product_config_no_variant/views/pos_order_views.xml @@ -6,7 +6,7 @@