From c46798faac6313e0e2350c05912be4abfe940b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 19 Mar 2020 00:28:43 +0100 Subject: [PATCH] Add code to template as variant prefix code The product code is stored as the concatenation of the template code and variant suffix code each time the template or the product is modified. issue9080 review293101002 --- CHANGELOG | 1 + doc/index.rst | 1 + product.py | 84 +++++++++++++++++++++++++++--- tests/scenario_product_variant.rst | 18 +++++++ tests/test_product.py | 4 +- view/product_form.xml | 8 +-- view/product_form_simple.xml | 5 +- view/product_tree.xml | 3 -- view/template_form.xml | 4 +- view/template_tree.xml | 1 + 10 files changed, 112 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b1c9525025..1fe906523c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add code to template as variant prefix code * Hide product fields inherited from template when empty * Skip default products when template is created from product * Search also on variant name when searching on template record name diff --git a/doc/index.rst b/doc/index.rst index 6d0ae5c850..8ce7d3dffe 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -42,6 +42,7 @@ Template and Product. The Product Template model contains the following fields: - Name. +- Code, a common prefix for all products. - Type, whose value can be *Goods*, *Assets*, *Service*. - Category. - List Price, the default sale price expressed in the List Price UOM. diff --git a/product.py b/product.py index 7ad1b2434f..1927da7d90 100644 --- a/product.py +++ b/product.py @@ -47,6 +47,7 @@ class Template( __name__ = "product.template" name = fields.Char( "Name", size=None, required=True, translate=True, select=True) + code = fields.Char("Code", select=True) type = fields.Selection(TYPES, "Type", required=True) consumable = fields.Boolean('Consumable', states={ @@ -97,6 +98,12 @@ class Template( return pool.get('product.cost_price_method') return super(Template, cls).multivalue_model(field) + def get_rec_name(self, name): + if self.code: + return '[' + self.code + ']' + self.name + else: + return self.name + @classmethod def search_rec_name(cls, name, clause): return [('products.rec_name',) + tuple(clause[1:])] @@ -146,10 +153,24 @@ class Template( @classmethod def create(cls, vlist): + pool = Pool() + Product = pool.get('product.product') vlist = [v.copy() for v in vlist] for values in vlist: values.setdefault('products', None) - return super(Template, cls).create(vlist) + templates = super(Template, cls).create(vlist) + products = sum((t.products for t in templates), ()) + Product.sync_code(products) + return templates + + @classmethod + def write(cls, *args): + pool = Pool() + Product = pool.get('product.product') + super().write(*args) + templates = sum(args[0:None:2], []) + products = sum((t.products for t in templates), ()) + Product.sync_code(products) @classmethod def search_global(cls, text): @@ -203,12 +224,19 @@ class Product( search_context={'default_products': False}) code_readonly = fields.Function(fields.Boolean('Code Readonly'), 'get_code_readonly') - code = fields.Char( - "Code", size=None, select=True, + prefix_code = fields.Function(fields.Char( + "Prefix Code", + states={ + 'invisible': ~Eval('prefix_code'), + }), + 'on_change_with_prefix_code') + suffix_code = fields.Char( + "Suffix Code", states={ 'readonly': Eval('code_readonly', False), }, depends=['code_readonly']) + code = fields.Char("Code", readonly=True, select=True) identifiers = fields.One2Many( 'product.identifier', 'product', "Identifiers", help="Add other identifiers to the variant.") @@ -263,6 +291,22 @@ class Product( if isinstance(tfield, fields.One2Many): getattr(cls, attr).setter = '_set_template_function' + @classmethod + def __register__(cls, module): + table = cls.__table__() + table_h = cls.__table_handler__(module) + fill_suffix_code = ( + table_h.column_exist('code') + and not table_h.column_exist('suffix_code')) + super().__register__(module) + cursor = Transaction().connection.cursor() + + # Migration from 5.4: split code into prefix/suffix + if fill_suffix_code: + cursor.execute(*table.update( + [table.suffix_code], + [table.code])) + @classmethod def _set_template_function(cls, products, name, value): # Prevent NotImplementedError for One2Many @@ -288,6 +332,11 @@ class Product( else: return value + @fields.depends('template', '_parent_template.code') + def on_change_with_prefix_code(self, name=None): + if self.template: + return self.template.code + @classmethod def multivalue_model(cls, field): pool = Pool() @@ -337,6 +386,7 @@ class Product( ('code', clause[1], code_value) + tuple(clause[3:]), ('identifiers.code', clause[1], code_value) + tuple(clause[3:]), ('template.name',) + tuple(clause[1:]), + ('template.code',) + tuple(clause[1:]), ] @staticmethod @@ -374,7 +424,7 @@ class Product( return self.default_code_readonly() @classmethod - def _new_code(cls): + def _new_suffix_code(cls): pool = Pool() Sequence = pool.get('ir.sequence') Configuration = pool.get('product.configuration') @@ -387,9 +437,17 @@ class Product( def create(cls, vlist): vlist = [x.copy() for x in vlist] for values in vlist: - if not values.get('code'): - values['code'] = cls._new_code() - return super().create(vlist) + if not values.get('suffix_code'): + values['suffix_code'] = cls._new_suffix_code() + products = super().create(vlist) + cls.sync_code(products) + return products + + @classmethod + def write(cls, *args): + super().write(*args) + products = sum(args[0:None:2], []) + cls.sync_code(products) @classmethod def copy(cls, products, default=None): @@ -397,6 +455,7 @@ class Product( default = {} else: default = default.copy() + default.setdefault('suffix_code', None) default.setdefault('code', None) return super().copy(products, default=default) @@ -404,6 +463,17 @@ class Product( def list_price_used(self): return self.template.get_multivalue('list_price') + @classmethod + def sync_code(cls, products): + for product in products: + code = ''.join(filter(None, [ + product.prefix_code, product.suffix_code])) + if not code: + code = None + if code != product.code: + product.code = code + cls.save(products) + class ProductListPrice(ModelSQL, CompanyValueMixin): "Product List Price" diff --git a/tests/scenario_product_variant.rst b/tests/scenario_product_variant.rst index 49f03cd918..a3e33519bf 100644 --- a/tests/scenario_product_variant.rst +++ b/tests/scenario_product_variant.rst @@ -21,9 +21,17 @@ Create a template:: >>> template.name = "Product" >>> template.default_uom = unit >>> template.list_price = Decimal('42.0000') + >>> template.code = "PROD" >>> template.save() >>> len(template.products) 1 + >>> product, = template.products + >>> product.code + 'PROD' + >>> product.suffix_code = "001" + >>> product.save() + >>> product.code + 'PROD001' Create a variant:: @@ -34,6 +42,16 @@ Create a variant:: 'Product' >>> product.list_price Decimal('42.0000') + >>> product.suffix_code = "002" >>> product.save() >>> product.list_price Decimal('42.0000') + >>> product.code + 'PROD002' + +Change template code:: + + >>> template.code = "PRD" + >>> template.save() + >>> sorted([p.code for p in template.products]) + ['PRD001', 'PRD002'] diff --git a/tests/test_product.py b/tests/test_product.py index 8b25de01cc..e7adcaf843 100644 --- a/tests/test_product.py +++ b/tests/test_product.py @@ -395,14 +395,14 @@ class ProductTestCase(ModuleTestCase): 'type': 'assets', 'list_price': Decimal('10'), 'default_uom': uom.id, - 'products': [('create', [{'code': 'AA'}])], + 'products': [('create', [{'suffix_code': 'AA'}])], } values2 = { 'name': 'Product B', 'type': 'goods', 'list_price': Decimal('10'), 'default_uom': uom.id, - 'products': [('create', [{'code': 'BB'}])], + 'products': [('create', [{'suffix_code': 'BB'}])], } template1, template2 = Template.create([values1, values2]) diff --git a/view/product_form.xml b/view/product_form.xml index 9c23b06a35..ebe3f2c9e2 100644 --- a/view/product_form.xml +++ b/view/product_form.xml @@ -2,9 +2,11 @@ - -