changeset 88f15ac40dbe in modules/product:default
details: https://hg.tryton.org/modules/product?cmd=changeset;node=88f15ac40dbe
description:
        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
diffstat:

 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(-)

diffs (319 lines):

diff -r 39e12a249fb0 -r 88f15ac40dbe CHANGELOG
--- a/CHANGELOG Tue Mar 17 20:08:12 2020 +0100
+++ b/CHANGELOG Thu Mar 19 00:28:43 2020 +0100
@@ -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 -r 39e12a249fb0 -r 88f15ac40dbe doc/index.rst
--- a/doc/index.rst     Tue Mar 17 20:08:12 2020 +0100
+++ b/doc/index.rst     Thu Mar 19 00:28:43 2020 +0100
@@ -42,6 +42,7 @@
 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 -r 39e12a249fb0 -r 88f15ac40dbe product.py
--- a/product.py        Tue Mar 17 20:08:12 2020 +0100
+++ b/product.py        Thu Mar 19 00:28:43 2020 +0100
@@ -47,6 +47,7 @@
     __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 @@
             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 @@
 
     @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 @@
         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.")
@@ -264,6 +292,22 @@
                     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
         pass
@@ -288,6 +332,11 @@
         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 @@
             ('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 @@
         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 @@
     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 @@
             default = {}
         else:
             default = default.copy()
+        default.setdefault('suffix_code', None)
         default.setdefault('code', None)
         return super().copy(products, default=default)
 
@@ -404,6 +463,17 @@
     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 -r 39e12a249fb0 -r 88f15ac40dbe tests/scenario_product_variant.rst
--- a/tests/scenario_product_variant.rst        Tue Mar 17 20:08:12 2020 +0100
+++ b/tests/scenario_product_variant.rst        Thu Mar 19 00:28:43 2020 +0100
@@ -21,9 +21,17 @@
     >>> 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 @@
     '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 -r 39e12a249fb0 -r 88f15ac40dbe tests/test_product.py
--- a/tests/test_product.py     Tue Mar 17 20:08:12 2020 +0100
+++ b/tests/test_product.py     Thu Mar 19 00:28:43 2020 +0100
@@ -395,14 +395,14 @@
             '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 -r 39e12a249fb0 -r 88f15ac40dbe view/product_form.xml
--- a/view/product_form.xml     Tue Mar 17 20:08:12 2020 +0100
+++ b/view/product_form.xml     Thu Mar 19 00:28:43 2020 +0100
@@ -2,9 +2,11 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <data>
-    <xpath expr="/form/field[@name='name']" position="after">
-        <label name="code"/>
-        <field name="code"/>
+    <xpath expr="/form/field[@name='code']" position="replace">
+        <group col="-1" name="code" string="">
+            <field name="prefix_code"/>
+            <field name="suffix_code"/>
+        </group>
     </xpath>
     <xpath expr="/form/notebook/page[@id='general']/label[@name='type']"
         position="before">
diff -r 39e12a249fb0 -r 88f15ac40dbe view/product_form_simple.xml
--- a/view/product_form_simple.xml      Tue Mar 17 20:08:12 2020 +0100
+++ b/view/product_form_simple.xml      Thu Mar 19 00:28:43 2020 +0100
@@ -6,7 +6,10 @@
     <field name="template"/>
     <newline/>
     <label name="code"/>
-    <field name="code"/>
+    <group col="-1" name="code" string="">
+        <field name="prefix_code"/>
+        <field name="suffix_code"/>
+    </group>
     <label name="active"/>
     <field name="active" xexpand="0" width="100"/>
     <label name="cost_price"/>
diff -r 39e12a249fb0 -r 88f15ac40dbe view/product_tree.xml
--- a/view/product_tree.xml     Tue Mar 17 20:08:12 2020 +0100
+++ b/view/product_tree.xml     Thu Mar 19 00:28:43 2020 +0100
@@ -4,7 +4,4 @@
 <data>
     <xpath expr="//field[@name='products']" position="replace">
     </xpath>
-    <xpath expr="/tree/field[@name='name']" position="before">
-        <field name="code" expand="1"/>
-    </xpath>
 </data>
diff -r 39e12a249fb0 -r 88f15ac40dbe view/template_form.xml
--- a/view/template_form.xml    Tue Mar 17 20:08:12 2020 +0100
+++ b/view/template_form.xml    Thu Mar 19 00:28:43 2020 +0100
@@ -4,9 +4,11 @@
 <form col="6">
   <label name="name"/>
   <field name="name" xexpand="1"/>
+  <label name="code"/>
+  <field name="code"/>
   <label name="active"/>
   <field name="active" xexpand="0" width="100"/>
-  <newline/>
+
   <notebook colspan="6">
       <page string="General" id="general">
           <label name="type"/>
diff -r 39e12a249fb0 -r 88f15ac40dbe view/template_tree.xml
--- a/view/template_tree.xml    Tue Mar 17 20:08:12 2020 +0100
+++ b/view/template_tree.xml    Thu Mar 19 00:28:43 2020 +0100
@@ -3,6 +3,7 @@
 this repository contains the full copyright notices and license terms. -->
 <tree>
     <field name="name" expand="2"/>
+    <field name="code" expand="1"/>
     <field name="list_price"/>
     <field name="cost_price"/>
     <field name="type"/>

Reply via email to