Add flat children option on location

issue6563
review36391002
This commit is contained in:
Cédric Krier
2017-09-28 23:42:22 +02:00
parent a8f15c8ea7
commit 2ad008f490
6 changed files with 240 additions and 15 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add flat children option on location
* Raise single warning for move without origin
* Allow to set manual effective date on move
* Improve computation of cost price
+2
View File
@@ -57,6 +57,8 @@ virtually stored. The following location types are defined:
Locations are organised in tree structures, allowing to define
fine grained structures.
It is possible to restrict a location to have only one level of children, this
allows to improve the performance of the stock quantity computation.
Location Lead Time
------------------
+19 -3
View File
@@ -54,6 +54,9 @@ class Location(ModelSQL, ModelView):
left = fields.Integer('Left', required=True, select=True)
right = fields.Integer('Right', required=True, select=True)
childs = fields.One2Many("stock.location", "parent", "Children")
flat_childs = fields.Boolean(
"Flat Children",
help="Check to restrict to one level of children.")
warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'),
'get_warehouse')
input_location = fields.Many2One(
@@ -126,8 +129,17 @@ class Location(ModelSQL, ModelView):
"to be deactivated."),
})
parent_domain = []
childs_domain = []
parent_domain = [
['OR',
('parent.flat_childs', '=', False),
('parent', '=', None),
]
]
childs_domain = [
If(Eval('flat_childs', False),
('childs', '=', None),
()),
]
childs_mapping = cls._childs_domain()
for type_, allowed_parents in cls._parent_domain().iteritems():
parent_domain.append(If(Eval('type') == type_,
@@ -136,7 +148,7 @@ class Location(ModelSQL, ModelView):
('type', 'in', childs_mapping[type_]), ()))
cls.parent.domain = parent_domain
cls.childs.domain = childs_domain
cls.childs.depends.append('type')
cls.childs.depends.extend(['flat_childs', 'type'])
@classmethod
def _parent_domain(cls):
@@ -256,6 +268,10 @@ class Location(ModelSQL, ModelView):
def default_right():
return 0
@classmethod
def default_flat_childs(cls):
return False
@staticmethod
def default_type():
return 'storage'
+105 -9
View File
@@ -9,7 +9,7 @@ from itertools import groupby
from sql import Literal, Union, Column, Null, For
from sql.aggregate import Sum
from sql.conditionals import Coalesce
from sql.conditionals import Coalesce, Case
from sql.operators import Concat
from trytond.model import Workflow, Model, ModelView, ModelSQL, fields, Check
@@ -1012,6 +1012,84 @@ class Move(Workflow, ModelSQL, ModelView):
if PeriodCache:
period_cache = PeriodCache.__table__()
if with_childs:
# Replace tables with union which replaces flat children locations
# by their parent location.
from_location = Location.__table__()
from_parent_location = Location.__table__()
to_location = Location.__table__()
to_parent_location = Location.__table__()
columns = ['id', 'state', 'effective_date', 'planned_date',
'internal_quantity'] + list(grouping)
columns = [Column(move, c).as_(c) for c in columns]
move_with_parent = (move
.join(from_location,
condition=move.from_location == from_location.id)
.join(from_parent_location, type_='LEFT',
condition=from_location.parent == from_parent_location.id)
.join(to_location,
condition=move.to_location == to_location.id)
.join(to_parent_location, type_='LEFT',
condition=to_location.parent == to_parent_location.id))
move = Union(
# Moves not linked to any flat location
move_with_parent.select(
move.from_location.as_('from_location'),
move.to_location.as_('to_location'),
*columns,
where=(Coalesce(from_parent_location.flat_childs, False)
!= True)
& (Coalesce(to_parent_location.flat_childs, False)
!= True)),
# Append moves linked from/to flat locations to their parents
move_with_parent.select(
Case(
(from_parent_location.flat_childs,
from_parent_location.id),
else_=move.from_location).as_('from_location'),
Case(
(to_parent_location.flat_childs,
to_parent_location.id),
else_=move.to_location).as_('to_location'),
*columns,
where=(from_parent_location.flat_childs == True)
| (to_parent_location.flat_childs == True)),
# Append moves linked to from/to flat locations only
move_with_parent.select(
Case(
(from_parent_location.flat_childs,
from_location.id),
else_=Null).as_('from_location'),
Case(
(to_parent_location.flat_childs,
to_location.id),
else_=Null).as_('to_location'),
*columns,
where=(from_parent_location.flat_childs == True)
| (to_parent_location.flat_childs == True)),
all_=True)
if PeriodCache:
location = Location.__table__()
parent_location = Location.__table__()
columns = ['internal_quantity', 'period'] + list(grouping)
columns = [Column(period_cache, c).as_(c) for c in columns]
period_cache = Union(
period_cache.select(
period_cache.location.as_('location'),
*columns),
period_cache.join(location,
condition=period_cache.location == location.id
).join(parent_location, type_='LEFT',
condition=location.parent == parent_location.id
).select(
parent_location.id.as_('location'),
*columns,
where=parent_location.flat_childs == True),
all_=True)
if not context.get('stock_date_end'):
context['stock_date_end'] = datetime.date.max
@@ -1152,9 +1230,7 @@ class Move(Workflow, ModelSQL, ModelView):
state_date_clause_out &= state_date_clause()
if with_childs:
location_query = Location.search([
('parent', 'child_of', location_ids),
], query=True, order=[])
location_query = _location_children(location_ids, query=True)
else:
location_query = location_ids[:]
@@ -1251,7 +1327,6 @@ class Move(Workflow, ModelSQL, ModelView):
and quantity as value.
"""
pool = Pool()
Location = pool.get('stock.location')
Product = pool.get('product.product')
assert query is not None, (
@@ -1283,15 +1358,13 @@ class Move(Workflow, ModelSQL, ModelView):
# Propagate quantities on from child locations to their parents
if with_childs and len(location_ids) > 1:
# Fetch all child locations
locations = Location.search([
('parent', 'child_of', location_ids),
])
locations = _location_children(location_ids)
# Generate a set of locations without childs and a dict
# giving the parent of each location.
leafs = set([l.id for l in locations])
parent = {}
for location in locations:
if not location.parent:
if not location.parent or location.parent.flat_childs:
continue
if location.parent.id in leafs:
leafs.remove(location.parent.id)
@@ -1330,3 +1403,26 @@ class Move(Workflow, ModelSQL, ModelView):
quantities[key] = uom.round(quantity)
return quantities
def _location_children(location_ids, query=False):
"Return children location without including flat children"
pool = Pool()
Location = pool.get('stock.location')
nested_location_ids = []
flat_location_ids = []
for location in Location.browse(location_ids):
if location.flat_childs:
flat_location_ids.append(location.id)
else:
nested_location_ids.append(location.id)
if nested_location_ids:
return Location.search(['OR',
('parent', 'child_of', nested_location_ids),
('id', 'in', flat_location_ids),
], query=query, order=[])
else:
if query:
return flat_location_ids
else:
return Location.browse(flat_location_ids)
+109
View File
@@ -442,6 +442,115 @@ class StockTestCase(ModuleTestCase):
])
self.assertListEqual([product], found_products)
@with_transaction()
def test_products_by_location_flat_childs(self, period_closed=False):
"Test products_by_location on flat_childs"
pool = Pool()
Uom = pool.get('product.uom')
Template = pool.get('product.template')
Product = pool.get('product.product')
Location = pool.get('stock.location')
Move = pool.get('stock.move')
Date = pool.get('ir.date')
Period = pool.get('stock.period')
unit, = Uom.search([('name', '=', 'Unit')])
template, = Template.create([{
'name': "Product",
'type': 'goods',
'list_price': Decimal(0),
'default_uom': unit.id,
}])
product, = Product.create([{
'template': template.id,
}])
lost_found, = Location.search([('type', '=', 'lost_found')])
storage, = Location.search([('code', '=', 'STO')])
storage.flat_childs = True
storage.save()
storage1, = Location.create([{
'name': 'Storage 1',
'type': 'storage',
'parent': storage.id,
}])
storage2, = Location.create([{
'name': 'Storage 2',
'type': 'storage',
'parent': storage.id,
}])
company = create_company()
with set_company(company):
date = Date.today() - relativedelta(days=1)
moves = Move.create([{
'product': product.id,
'uom': unit.id,
'quantity': 1,
'from_location': lost_found.id,
'to_location': storage.id,
'planned_date': date,
'effective_date': date,
'company': company.id,
}, {
'product': product.id,
'uom': unit.id,
'quantity': 1,
'from_location': lost_found.id,
'to_location': storage1.id,
'planned_date': date,
'effective_date': date,
'company': company.id,
}, {
'product': product.id,
'uom': unit.id,
'quantity': 1,
'from_location': storage1.id,
'to_location': storage2.id,
'planned_date': date,
'effective_date': date,
'company': company.id,
}])
Move.do(moves)
if period_closed:
period, = Period.create([{
'date': date,
'company': company.id,
}])
Period.close([period])
# Test flat location
products_by_location = Product.products_by_location(
[storage.id], [product.id], with_childs=True)
self.assertEqual(
products_by_location[(storage.id, product.id)], 2)
# Test mixed flat and nested location
products_by_location = Product.products_by_location(
[storage.parent.id, storage.id, storage1.id, storage2.id],
[product.id],
with_childs=True)
self.assertEqual(
products_by_location[(storage.parent.id, product.id)], 2)
self.assertEqual(
products_by_location[(storage.id, product.id)], 2)
self.assertEqual(
products_by_location[(storage1.id, product.id)], 0)
self.assertEqual(
products_by_location[(storage2.id, product.id)], 1)
# Test non flat
products_by_location = Product.products_by_location(
[lost_found.id], [product.id], with_childs=True)
self.assertEqual(
products_by_location[(lost_found.id, product.id)], -2)
def test_products_by_location_flat_childs_period_closed(self):
"Test products_by_location on flat_childs with period closed"
self.test_products_by_location_flat_childs(period_closed=True)
@with_transaction()
def test_period(self):
'Test period'
+4 -3
View File
@@ -6,13 +6,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="name"/>
<label name="code"/>
<field name="code"/>
<label name="parent"/>
<field name="parent"/>
<newline/>
<label name="type"/>
<field name="type"/>
<label name="active"/>
<field name="active"/>
<label name="parent"/>
<field name="parent"/>
<label name="flat_childs"/>
<field name="flat_childs"/>
<newline/>
<label name="address"/>
<field name="address"/>