changeset f89e5a8d48d8 in modules/stock_product_location:default
details: 
https://hg.tryton.org/modules/stock_product_location?cmd=changeset;node=f89e5a8d48d8
description:
        Add default locations on products

        issue9214
        review299351002
diffstat:

 CHANGELOG                                          |    2 +
 __init__.py                                        |    1 +
 location.py                                        |   40 ++++++-
 product.py                                         |   73 +++++++++++-
 product.xml                                        |    6 +-
 tests/scenario_product_copy_locations.rst          |   67 +++++++++++
 tests/scenario_stock_product_location_template.rst |  125 +++++++++++++++++++++
 tests/test_stock_product_location.py               |   10 +
 view/product_location_form.xml                     |    3 +
 view/product_location_tree.xml                     |    1 +
 10 files changed, 319 insertions(+), 9 deletions(-)

diffs (429 lines):

diff -r c191b7b66ab4 -r f89e5a8d48d8 CHANGELOG
--- a/CHANGELOG Mon May 04 12:33:15 2020 +0200
+++ b/CHANGELOG Fri May 22 11:35:01 2020 +0200
@@ -1,3 +1,5 @@
+* Add default locations on templates
+
 Version 5.6.0 - 2020-05-04
 * Bug fixes (see mercurial logs for details)
 
diff -r c191b7b66ab4 -r f89e5a8d48d8 __init__.py
--- a/__init__.py       Mon May 04 12:33:15 2020 +0200
+++ b/__init__.py       Fri May 22 11:35:01 2020 +0200
@@ -9,6 +9,7 @@
 
 def register():
     Pool.register(
+        product.Template,
         product.Product,
         location.ProductLocation,
         location.Move,
diff -r c191b7b66ab4 -r f89e5a8d48d8 location.py
--- a/location.py       Mon May 04 12:33:15 2020 +0200
+++ b/location.py       Fri May 22 11:35:01 2020 +0200
@@ -12,8 +12,23 @@
     It defines the default storage location by warehouse for a product.
     '''
     __name__ = 'stock.product.location'
-    product = fields.Many2One('product.product', 'Product', required=True,
-        select=True, ondelete='CASCADE')
+    template = fields.Many2One(
+        'product.template', "Product",
+        required=True, ondelete='CASCADE', select=True,
+        domain=[
+            If(Bool(Eval('product')),
+                ('products', '=', Eval('product')),
+                ()),
+            ],
+        depends=['product'])
+    product = fields.Many2One(
+        'product.product', "Variant", ondelete='CASCADE', select=True,
+        domain=[
+            If(Bool(Eval('template')),
+                ('template', '=', Eval('template')),
+                ()),
+            ],
+        depends=['template'])
     warehouse = fields.Many2One('stock.location', 'Warehouse', required=True,
         domain=[('type', '=', 'warehouse')], ondelete='CASCADE')
     location = fields.Many2One('stock.location', 'Storage Location',
@@ -24,6 +39,20 @@
                     [Eval('warehouse')], [])),
             ], depends=['warehouse'])
 
+    @classmethod
+    def __register__(cls, module_name):
+        table = cls.__table_handler__(module_name)
+
+        super().__register__(module_name)
+
+        # Migration from 5.6: Add template on locations
+        table.not_null_action('product', 'remove')
+
+    @fields.depends('product', '_parent_product.template')
+    def on_change_product(self):
+        if self.product:
+            self.template = self.product.template
+
 
 class Move(metaclass=PoolMeta):
     __name__ = 'stock.move'
@@ -33,7 +62,12 @@
         if getattr(self, 'shipment', None):
             pattern.setdefault('warehouse', self.shipment.warehouse.id)
 
-        for product_location in self.product.locations:
+        if self.product:
+            pattern.setdefault('template', self.product.template.id)
+            pattern.setdefault('product', self.product.id)
+
+        locations = self.product.locations + self.product.template.locations
+        for product_location in locations:
             if product_location.match(pattern):
                 setattr(self, field, product_location.location)
                 break
diff -r c191b7b66ab4 -r f89e5a8d48d8 product.py
--- a/product.py        Mon May 04 12:33:15 2020 +0200
+++ b/product.py        Fri May 22 11:35:01 2020 +0200
@@ -2,13 +2,80 @@
 # this repository contains the full copyright notices and license terms.
 from trytond.model import fields
 from trytond.pyson import Eval
-from trytond.pool import PoolMeta
+from trytond.pool import PoolMeta, Pool
+
+
+class Template(metaclass=PoolMeta):
+    __name__ = 'product.template'
+    locations = fields.One2Many('stock.product.location', 'template',
+        "Default Locations",
+        states={
+            'invisible': ~Eval('type').in_(['goods', 'assets']),
+            },
+        depends=['type'])
+
+    @classmethod
+    def copy(cls, templates, default=None):
+        pool = Pool()
+        ProductLocation = pool.get('stock.product.location')
+        if default is None:
+            default = {}
+        else:
+            default = default.copy()
+
+        copy_locations = 'locations' not in default
+        default.setdefault('locations', None)
+        new_templates = super().copy(templates, default)
+        if copy_locations:
+            old2new = {}
+            to_copy = []
+            for template, new_template in zip(templates, new_templates):
+                to_copy.extend(l for l in template.locations if not l.product)
+                old2new[template.id] = new_template.id
+            if to_copy:
+                ProductLocation.copy(to_copy, {
+                        'template': lambda d: old2new[d['template']],
+                        })
+        return new_templates
 
 
 class Product(metaclass=PoolMeta):
     __name__ = 'product.product'
     locations = fields.One2Many('stock.product.location', 'product',
-        'Default Locations', states={
+        "Default Locations",
+        domain=[
+            ('template', '=', Eval('template')),
+            ],
+        states={
             'invisible': ~Eval('type').in_(['goods', 'assets']),
             },
-        depends=['type'])
+        depends=['type', 'template'])
+
+    @classmethod
+    def copy(cls, products, default=None):
+        pool = Pool()
+        ProductLocation = pool.get('stock.product.location')
+        if default is None:
+            default = {}
+        else:
+            default = default.copy()
+
+        copy_locations = 'locations' not in default
+        if 'template' in default:
+            default.setdefault('locations', None)
+        new_products = super().copy(products, default)
+        if 'template' in default and copy_locations:
+            template2new = {}
+            product2new = {}
+            to_copy = []
+            for product, new_product in zip(products, new_products):
+                if product.locations:
+                    to_copy.extend(product.locations)
+                    template2new[product.template.id] = new_product.template.id
+                    product2new[product.id] = new_product.id
+            if to_copy:
+                ProductLocation.copy(to_copy, {
+                        'product': lambda d: product2new[d['product']],
+                        'template': lambda d: template2new[d['template']],
+                        })
+        return new_products
diff -r c191b7b66ab4 -r f89e5a8d48d8 product.xml
--- a/product.xml       Mon May 04 12:33:15 2020 +0200
+++ b/product.xml       Fri May 22 11:35:01 2020 +0200
@@ -3,9 +3,9 @@
 this repository contains the full copyright notices and license terms. -->
 <tryton>
     <data>
-        <record model="ir.ui.view" id="product_view_form">
-            <field name="model">product.product</field>
-            <field name="inherit" ref="product.product_view_form"/>
+        <record model="ir.ui.view" id="template_view_form">
+            <field name="model">product.template</field>
+            <field name="inherit" ref="product.template_view_form"/>
             <field name="name">product_form</field>
         </record>
     </data>
diff -r c191b7b66ab4 -r f89e5a8d48d8 tests/scenario_product_copy_locations.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_product_copy_locations.rst Fri May 22 11:35:01 2020 +0200
@@ -0,0 +1,67 @@
+=============================
+Product Copy Locaton Scenario
+=============================
+
+Imports::
+
+    >>> from decimal import Decimal
+    >>> from proteus import Model
+    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.modules.company.tests.tools import create_company, \
+    ...     get_company
+
+Install purchase::
+
+    >>> config = activate_modules('stock_product_location')
+
+Create company::
+
+    >>> _ = create_company()
+    >>> company = get_company()
+
+Get stock locations::
+
+    >>> Location = Model.get('stock.location')
+    >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+
+Create new location::
+
+    >>> Location = Model.get('stock.location')
+    >>> child_loc = Location()
+    >>> child_loc.name = 'Child Location'
+    >>> child_loc.parent = storage_loc
+    >>> child_loc.code = 'CHI'
+    >>> child_loc.save()
+
+Create a product with suppliers::
+
+    >>> ProductUom = Model.get('product.uom')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.list_price = Decimal('20')
+    >>> template_location = template.locations.new()
+    >>> template_location.warehouse = warehouse_loc
+    >>> template_location.location = child_loc
+    >>> template.save()
+    >>> product, = template.products
+    >>> product_location = product.locations.new()
+    >>> product_location.warehouse = warehouse_loc
+    >>> product_location.location = child_loc
+    >>> product_location.template == template
+    True
+    >>> product.save()
+
+Location is copied when copying the template::
+
+    >>> template_copy, = template.duplicate()
+    >>> product_copy, = template_copy.products
+    >>> len(template_copy.locations)
+    2
+    >>> len(product_copy.locations)
+    1
diff -r c191b7b66ab4 -r f89e5a8d48d8 
tests/scenario_stock_product_location_template.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_stock_product_location_template.rst        Fri May 22 
11:35:01 2020 +0200
@@ -0,0 +1,125 @@
+=========================================
+Stock Product Locations Template Scenario
+=========================================
+
+Imports::
+
+    >>> import datetime
+    >>> from decimal import Decimal
+    >>> from proteus import Model, Wizard
+    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.modules.company.tests.tools import create_company, \
+    ...     get_company
+    >>> today = datetime.date.today()
+
+Install stock_product_location Module::
+
+    >>> config = activate_modules('stock_product_location')
+
+Create company::
+
+    >>> _ = create_company()
+    >>> company = get_company()
+
+Create customer::
+
+    >>> Party = Model.get('party.party')
+    >>> customer = Party(name='Customer')
+    >>> customer.save()
+
+Create supplier::
+
+    >>> supplier = Party(name='Suppier')
+    >>> supplier.save()
+
+Get stock locations::
+
+    >>> Location = Model.get('stock.location')
+    >>> warehouse_loc, = Location.find([('code', '=', 'WH')])
+    >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+    >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+    >>> output_loc, = Location.find([('code', '=', 'OUT')])
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+    >>> input_loc, = Location.find([('code', '=', 'IN')])
+
+Create new location::
+
+    >>> Location = Model.get('stock.location')
+    >>> child_loc = Location()
+    >>> child_loc.name = 'Child Location'
+    >>> child_loc.parent = storage_loc
+    >>> child_loc.code = 'CHI'
+    >>> child_loc.save()
+
+Create product::
+
+    >>> ProductUom = Model.get('product.uom')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.list_price = Decimal('20')
+    >>> template_location = template.locations.new()
+    >>> template_location.warehouse = warehouse_loc
+    >>> template_location.location = child_loc
+    >>> template.save()
+    >>> product, = template.products
+
+Create Shipment in::
+
+    >>> Shipmentin = Model.get('stock.shipment.in')
+    >>> shipment_in = Shipmentin()
+    >>> shipment_in.planned_date = today
+    >>> shipment_in.supplier = supplier
+    >>> shipment_in.warehouse = warehouse_loc
+    >>> shipment_in.company = company
+
+Add one shipment line of product::
+
+    >>> move = shipment_in.incoming_moves.new()
+    >>> move.product = product
+    >>> move.uom = unit
+    >>> move.quantity = 1
+    >>> move.from_location = supplier_loc
+    >>> move.to_location = input_loc
+    >>> move.company = company
+    >>> move.unit_price = Decimal('1')
+    >>> move.currency = company.currency
+    >>> shipment_in.save()
+
+Test that to_location is child location on reception::
+
+    >>> shipment_in.click('receive')
+    >>> move, = shipment_in.inventory_moves
+    >>> move.to_location == child_loc
+    True
+
+Create return shipment out::
+
+    >>> Shipment_out_Return = Model.get('stock.shipment.out.return')
+    >>> shipment_out_return = Shipment_out_Return()
+    >>> shipment_out_return.customer = customer
+    >>> shipment_out_return.save()
+
+Add one shipment return line::
+
+    >>> move = shipment_out_return.incoming_moves.new()
+    >>> move.product = product
+    >>> move.uom =unit
+    >>> move.quantity = 1
+    >>> move.from_location = customer_loc
+    >>> move.to_location =  input_loc
+    >>> move.company = company
+    >>> move.unit_price = Decimal('1')
+    >>> move.currency = company.currency
+    >>> shipment_out_return.save()
+
+Test that to_location is child location on reception::
+
+    >>> shipment_out_return.click('receive')
+    >>> move, = shipment_out_return.inventory_moves
+    >>> move.to_location == child_loc
+    True
diff -r c191b7b66ab4 -r f89e5a8d48d8 tests/test_stock_product_location.py
--- a/tests/test_stock_product_location.py      Mon May 04 12:33:15 2020 +0200
+++ b/tests/test_stock_product_location.py      Fri May 22 11:35:01 2020 +0200
@@ -27,4 +27,14 @@
             tearDown=doctest_teardown, encoding='utf-8',
             checker=doctest_checker,
             optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite(
+            'scenario_stock_product_location_template.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite(
+            'scenario_product_copy_locations.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
     return suite
diff -r c191b7b66ab4 -r f89e5a8d48d8 view/product_location_form.xml
--- a/view/product_location_form.xml    Mon May 04 12:33:15 2020 +0200
+++ b/view/product_location_form.xml    Fri May 22 11:35:01 2020 +0200
@@ -2,6 +2,9 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <form cursor="product">
+    <label name="template"/>
+    <field name="template"/>
+    <newline/>
     <label name="sequence"/>
     <field name="sequence"/>
     <label name="product"/>
diff -r c191b7b66ab4 -r f89e5a8d48d8 view/product_location_tree.xml
--- a/view/product_location_tree.xml    Mon May 04 12:33:15 2020 +0200
+++ b/view/product_location_tree.xml    Fri May 22 11:35:01 2020 +0200
@@ -2,6 +2,7 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <tree>
+    <field name="template" expand="2"/>
     <field name="product" expand="2"/>
     <field name="warehouse" expand="1"/>
     <field name="location" expand="1"/>

Reply via email to