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"/>