diff --git a/product_plm_import/README.rst b/product_plm_import/README.rst index d40b7cc9..106b2ca1 100644 --- a/product_plm_import/README.rst +++ b/product_plm_import/README.rst @@ -37,12 +37,12 @@ Configuration Update fields in the company (in the 'PLM I/F' tab): -- PLM Path: the absolute path to the PLM directory to fetch the files - from. -- PLM Notification Body: the text will be included in the notification - email body. -- PLM Notified Groups: assign groups to notify when a new file is - fetched from the PLM. +- PLM Path: the absolute path to the PLM directory to fetch the files + from. +- PLM Notification Body: the text will be included in the notification + email body. +- PLM Notified Groups: assign groups to notify when a new file is + fetched from the PLM. The PLM-Product Mapping menu enables users to define product policies, such as Product Type, Product Category, Routes, etc., which will be diff --git a/product_plm_import/__manifest__.py b/product_plm_import/__manifest__.py index 16a5949f..6c91cea8 100644 --- a/product_plm_import/__manifest__.py +++ b/product_plm_import/__manifest__.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Product PLM Import", - "version": "16.0.1.0.0", + "version": "16.0.1.1.0", "category": "Stock", "license": "AGPL-3", "author": "Quartile Limited", diff --git a/product_plm_import/models/plm_import_log.py b/product_plm_import/models/plm_import_log.py index 5f20780b..c8a2f4ec 100644 --- a/product_plm_import/models/plm_import_log.py +++ b/product_plm_import/models/plm_import_log.py @@ -72,7 +72,7 @@ def _send_plm_import_notification(self): notified_groups = company.plm_notif_group_ids notified_partners = notified_groups.users.partner_id if not notified_partners: - return + continue rec.message_subscribe(partner_ids=notified_partners.ids) state_desc = rec._get_state_description( "plm_product_state", rec.plm_product_state diff --git a/product_plm_import/models/plm_product_mapping.py b/product_plm_import/models/plm_product_mapping.py index 3d0f4457..f29286ba 100644 --- a/product_plm_import/models/plm_product_mapping.py +++ b/product_plm_import/models/plm_product_mapping.py @@ -3,7 +3,8 @@ import fnmatch -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class PlmProductMapping(models.Model): @@ -43,13 +44,26 @@ class PlmProductMapping(models.Model): ) auto_create_lot = fields.Boolean() lot_sequence_padding = fields.Integer() - lot_sequence_prefix = fields.Char() + lot_sequence_prefix = fields.Char( + help="Lot sequence prefix. Supports '{esc_code}' placeholder to use the ESC ID " + "of each PLM record (e.g. '{esc_code}')." + ) default_active = fields.Boolean( help="Default value for active field of the created product." ) company_id = fields.Many2one("res.company") active = fields.Boolean(default=True) + @api.constrains("lot_sequence_prefix") + def _check_lot_sequence_prefix(self): + for record in self: + if not record.lot_sequence_prefix: + continue + try: + record.lot_sequence_prefix.format(esc_code="") + except (ValueError, KeyError): + raise ValidationError(_("Lot Sequence Prefix is invalid.")) from None + @api.onchange("product_type") def onchange_product_type(self): if self.product_type != "product": diff --git a/product_plm_import/models/product_plm.py b/product_plm_import/models/product_plm.py index bdcd8929..656499c3 100644 --- a/product_plm_import/models/product_plm.py +++ b/product_plm_import/models/product_plm.py @@ -90,7 +90,6 @@ def _get_uom(self): def _create_product(self): self.ensure_one() - product = self.env["product.product"] description_purchase = self._get_description_purchase() uom = self._get_uom() mapping = self.mapping_id @@ -113,12 +112,12 @@ def _create_product(self): "is_draft": True, } try: - product = self.env["product.product"].create(vals) + return self.env["product.product"].create(vals) except Exception as e: _logger.error( "ProductPlm._create_product - failed to create product: %s", str(e) ) - return product + return self.env["product.product"] @api.model def _get_create_products_domain(self): @@ -144,7 +143,11 @@ def create_products(self, batch_size=30): if mapping.lot_sequence_padding: product.lot_sequence_id.padding = mapping.lot_sequence_padding if mapping.lot_sequence_prefix: - product.lot_sequence_id.prefix = mapping.lot_sequence_prefix + prefix = mapping.lot_sequence_prefix.format( + esc_code=plm_rec.esc_code or "", + ) + if prefix: + product.lot_sequence_id.prefix = prefix product.product_tmpl_id.active = mapping.default_active plm_rec.write({"state": "done", "product_id": product.id}) # This step fails with CasheMiss error in case product creation in diff --git a/product_plm_import/static/description/index.html b/product_plm_import/static/description/index.html index 27b36582..5c24ac77 100644 --- a/product_plm_import/static/description/index.html +++ b/product_plm_import/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { diff --git a/product_plm_import/tests/__init__.py b/product_plm_import/tests/__init__.py new file mode 100644 index 00000000..8893a775 --- /dev/null +++ b/product_plm_import/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_plm diff --git a/product_plm_import/tests/test_product_plm.py b/product_plm_import/tests/test_product_plm.py new file mode 100644 index 00000000..d57de5f7 --- /dev/null +++ b/product_plm_import/tests/test_product_plm.py @@ -0,0 +1,54 @@ +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestProductPlm(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env["ir.config_parameter"].set_param( + "product_lot_sequence.policy", "product" + ) + cls.item_type = cls.env["plm.item.type"].create({"name": "Test PCBA"}) + cls.mapping = cls.env["plm.product.mapping"].create( + { + "item_type_id": cls.item_type.id, + "product_type": "product", + "product_categ_id": cls.env.ref("product.product_category_all").id, + "tracking": "serial", + "auto_create_lot": True, + } + ) + cls.plm_rec = cls.env["product.plm"].create( + { + "part_number": "TEST-001", + "name": "Test Product", + "mapping_id": cls.mapping.id, + "company_id": cls.env.company.id, + "esc_code": "ESC123", + } + ) + + def test_lot_sequence_prefix_valid(self): + self.mapping.write({"lot_sequence_prefix": "{esc_code}"}) + self.mapping.write({"lot_sequence_prefix": "STATIC"}) + self.mapping.write({"lot_sequence_prefix": False}) + with self.assertRaises(ValidationError): + self.mapping.write({"lot_sequence_prefix": "{invalid_key}"}) + with self.assertRaises(ValidationError): + self.mapping.write({"lot_sequence_prefix": "{"}) + + def test_create_products_prefix_from_esc_code(self): + """create_products() sets lot sequence prefix from ESC ID.""" + self.mapping.write({"lot_sequence_prefix": "{esc_code}"}) + self.env["product.plm"].create_products() + self.assertEqual(self.plm_rec.state, "done") + self.assertEqual(self.plm_rec.product_id.lot_sequence_id.prefix, "ESC123") + + def test_create_products_no_prefix_without_esc_code(self): + """create_products() skips prefix when ESC ID is absent.""" + self.mapping.write({"lot_sequence_prefix": "{esc_code}"}) + self.plm_rec.write({"esc_code": False}) + self.env["product.plm"].create_products() + self.assertEqual(self.plm_rec.state, "done") + self.assertFalse(self.plm_rec.product_id.lot_sequence_id.prefix)