trytond-patches/319251002_309331002_3014410...

495 lines
21 KiB
Diff

diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py
index d7fc46ad..8bd4c3a6 100644
--- a/trytond/trytond/model/modelstorage.py
+++ b/trytond/trytond/model/modelstorage.py
@@ -1258,6 +1258,14 @@ class ModelStorage(Model):
if hasattr(field, 'selection') and field.selection:
if isinstance(field.selection, (tuple, list)):
test = set(dict(field.selection).keys())
+ instance_sel_func = False
+ else:
+ sel_func = getattr(cls, field.selection)
+ instance_sel_func = is_instance_method(
+ cls, field.selection)
+ if not instance_sel_func:
+ test = sel_func()
+ test = set(dict(test))
for record in records:
value = getattr(record, field_name)
if field._type == 'reference':
@@ -1265,12 +1273,8 @@ class ModelStorage(Model):
value = value.__class__.__name__
elif value:
value, _ = value.split(',')
- if not isinstance(field.selection, (tuple, list)):
- sel_func = getattr(cls, field.selection)
- if not is_instance_method(cls, field.selection):
- test = sel_func()
- else:
- test = sel_func(record)
+ if instance_sel_func:
+ test = sel_func(record)
test = set(dict(test))
# None and '' are equivalent
if '' in test or None in test:
@@ -1636,10 +1640,12 @@ class ModelStorage(Model):
def save(cls, records):
while records:
latter = []
- values = {}
- save_values = {}
to_create = []
to_write = []
+ to_create_save_values = []
+ to_write_save_values = []
+ to_create_values = []
+ to_write_values = []
first = next(iter(records))
transaction = first._transaction
user = first._user
@@ -1650,12 +1656,16 @@ class ModelStorage(Model):
or context != record._context):
latter.append(record)
continue
- save_values[record] = record._save_values
- values[record] = record._values
- record._values = None
+ save_values = record._save_values
if record.id is None or record.id < 0:
+ to_create_save_values.append(save_values)
+ to_create_values.append(record._values)
+ record._values = None
to_create.append(record)
- elif save_values[record]:
+ elif save_values:
+ to_write_save_values.append(save_values)
+ to_write_values.append(record._values)
+ record._values = None
to_write.append(record)
transaction = Transaction()
try:
@@ -1664,17 +1674,20 @@ class ModelStorage(Model):
transaction.reset_context(), \
transaction.set_context(context):
if to_create:
- news = cls.create([save_values[r] for r in to_create])
+ news = cls.create(to_create_save_values)
for record, new in zip(to_create, news):
record._ids.remove(record.id)
record._id = new.id
record._ids.append(record.id)
if to_write:
cls.write(*sum(
- (([r], save_values[r]) for r in to_write), ()))
+ (([r], v) for r, v in zip(
+ to_write, to_write_save_values)), ()))
except:
- for record in to_create + to_write:
- record._values = values[record]
+ for record, values in zip(to_create, to_create_values):
+ record._values = values
+ for record, values in zip(to_write, to_write_values):
+ record._values = values
raise
for record in to_create + to_write:
record._init_values = None
diff --git a/shipment.py b/shipment.py
index 6ef998f..8f1626b 100644
--- a/trytond/trytond/modules/stock_supply/shipment.py
+++ b/trytond/trytond/modules/stock_supply/shipment.py
@@ -81,6 +81,8 @@ class ShipmentInternal(ModelSQL, ModelView):
# Create a list of moves to create
moves = {}
for location in id2location.values():
+ loc_prov_location = location.provisioning_location
+ loc_over_location = location.overflowing_location
for product_id in product_ids:
qty = current_qties.get((location.id, product_id), 0)
op = product2op.get((location.id, product_id))
@@ -90,13 +92,12 @@ class ShipmentInternal(ModelSQL, ModelView):
prov_location = op.provisioning_location
over_location = op.overflowing_location
elif (location
- and (location.provisioning_location
- or location.overflowing_location)):
+ and (loc_prov_location or loc_over_location)):
target_qty = 0
- min_qty = 0 if location.provisioning_location else None
- max_qty = 0 if location.overflowing_location else None
- prov_location = location.provisioning_location
- over_location = location.overflowing_location
+ min_qty = 0 if loc_prov_location else None
+ max_qty = 0 if loc_over_location else None
+ prov_location = loc_prov_location
+ over_location = loc_over_location
else:
continue
diff --git a/move.py b/move.py
index 3576b4e..fa717b3 100644
--- a/trytond/trytond/modules/stock/move.py
+++ b/trytond/trytond/modules/stock/move.py
@@ -1362,29 +1362,40 @@ class Move(Workflow, ModelSQL, ModelView):
# 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 = {}
+ parents = {}
for location in locations:
if not location.parent or location.parent.flat_childs:
continue
if location.parent.id in leafs:
leafs.remove(location.parent.id)
- parent[location.id] = location.parent.id
+ # Search for the first ancestor in location_ids to make the
+ # propagation of quantites faster
+ parent = location.parent
+ while parent:
+ parents[location.id] = parent.id
+ if parent.id in location_ids:
+ break
+ parent = parent.parent
+
locations = set((l.id for l in locations))
while leafs:
- for l in leafs:
- locations.remove(l)
- if l not in parent:
+ for leaf in leafs:
+ locations.remove(leaf)
+ if leaf not in parents:
continue
+ parent = parents[leaf]
for key in keys:
- parent_key = (parent[l],) + key
- quantities.setdefault(parent_key, 0)
- quantities[parent_key] += quantities.get((l,) + key, 0)
+ parent_key = (parent,) + key
+ quantity = quantities.get((leaf,) + key, 0)
+ quantities[parent_key] = (
+ quantities.get(parent_key, 0) + quantity)
+
next_leafs = set(locations)
for l in locations:
- if l not in parent:
+ if l not in parents:
continue
- if parent[l] in next_leafs and parent[l] in locations:
- next_leafs.remove(parent[l])
+ if parents[l] in next_leafs and parents[l] in locations:
+ next_leafs.remove(parents[l])
leafs = next_leafs
# clean result
diff --git a/tests/test_stock.py b/tests/test_stock.py
index e0eccab..616328d 100644
--- a/trytond/trytond/modules/stock/tests/test_stock.py
+++ b/trytond/trytond/modules/stock/tests/test_stock.py
@@ -392,12 +392,12 @@ class StockTestCase(ModuleTestCase):
}])
storage2, = Location.create([{
'name': 'Storage 1.1',
- 'type': 'view',
+ 'type': 'storage',
'parent': storage1.id,
}])
storage3, = Location.create([{
'name': 'Storage 2',
- 'type': 'view',
+ 'type': 'storage',
'parent': storage.id,
}])
company = create_company()
@@ -409,7 +409,7 @@ class StockTestCase(ModuleTestCase):
'uom': unit.id,
'quantity': 1,
'from_location': lost_found.id,
- 'to_location': storage.id,
+ 'to_location': storage2.id,
'planned_date': today,
'effective_date': today,
'company': company.id,
@@ -418,7 +418,7 @@ class StockTestCase(ModuleTestCase):
'uom': unit.id,
'quantity': 1,
'from_location': input_.id,
- 'to_location': storage.id,
+ 'to_location': storage3.id,
'planned_date': today,
'effective_date': today,
'company': company.id,
diff --git a/CHANGELOG b/CHANGELOG
index f5314a8..7eea449 100644
--- a/trytond/trytond/modules/stock_forecast/CHANGELOG
+++ b/trytond/trytond/modules/stock_forecast/CHANGELOG
@@ -1,3 +1,6 @@
+Version 5.2.3 - 2020-08-20
+* Use move's origin as link to forecast lines
+
Version 5.2.2 - 2020-04-04
* Bug fixes (see mercurial logs for details)
diff --git a/__init__.py b/__init__.py
index 59f23c7..0dc3999 100644
--- a/trytond/trytond/modules/stock_forecast/__init__.py
+++ b/trytond/trytond/modules/stock_forecast/__init__.py
@@ -2,17 +2,17 @@
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
-from .forecast import *
+from . import forecast
def register():
Pool.register(
- Forecast,
- ForecastLine,
- ForecastLineMove,
- ForecastCompleteAsk,
- ForecastCompleteChoose,
+ forecast.Move,
+ forecast.Forecast,
+ forecast.ForecastLine,
+ forecast.ForecastCompleteAsk,
+ forecast.ForecastCompleteChoose,
module='stock_forecast', type_='model')
Pool.register(
- ForecastComplete,
+ forecast.ForecastComplete,
module='stock_forecast', type_='wizard')
diff --git a/forecast.py b/forecast.py
index 93d63d5..3f92c93 100644
--- a/trytond/trytond/modules/stock_forecast/forecast.py
+++ b/trytond/trytond/modules/stock_forecast/forecast.py
@@ -15,12 +15,12 @@ from trytond.model.exceptions import AccessError
from trytond.wizard import Wizard, StateView, StateTransition, Button
from trytond.pyson import Not, Equal, Eval, Or, Bool, If
from trytond.transaction import Transaction
-from trytond.pool import Pool
+from trytond.pool import Pool, PoolMeta
from trytond.tools import reduce_ids, grouped_slice
from .exceptions import ForecastValidationError
-__all__ = ['Forecast', 'ForecastLine', 'ForecastLineMove',
+__all__ = ['Forecast', 'Move', 'ForecastLine'
'ForecastCompleteAsk', 'ForecastCompleteChoose', 'ForecastComplete']
STATES = {
@@ -232,20 +232,24 @@ class Forecast(Workflow, ModelSQL, ModelView):
def create_moves(forecasts):
'Create stock moves for the forecast ids'
pool = Pool()
- Line = pool.get('stock.forecast.line')
+ Move = pool.get('stock.move')
to_save = []
for forecast in forecasts:
if forecast.state == 'done':
for line in forecast.lines:
- line.moves += tuple(line.get_moves())
- to_save.append(line)
- Line.save(to_save)
+ to_save += line.get_moves()
+ Move.save(to_save)
@staticmethod
def delete_moves(forecasts):
'Delete stock moves for the forecast ids'
- Line = Pool().get('stock.forecast.line')
- Line.delete_moves([l for f in forecasts for l in f.lines])
+ pool = Pool()
+ Move = pool.get('stock.move')
+
+ Move.delete(Move.search([
+ ('origin.forecast', 'in',
+ [x.id for x in forecasts], 'stock.forecast.line'),
+ ]))
class ForecastLine(ModelSQL, ModelView):
@@ -283,8 +287,7 @@ class ForecastLine(ModelSQL, ModelView):
"Minimal Qty", digits=(16, Eval('unit_digits', 2)), required=True,
domain=[('minimal_quantity', '<=', Eval('quantity'))],
states=_states, depends=['unit_digits', 'quantity'] + _depends)
- moves = fields.Many2Many('stock.forecast.line-stock.move',
- 'line', 'move', 'Moves', readonly=True)
+ moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
forecast = fields.Many2One(
'stock.forecast', 'Forecast', required=True, ondelete='CASCADE',
states={
@@ -369,12 +372,11 @@ class ForecastLine(ModelSQL, ModelView):
Location = pool.get('stock.location')
Uom = pool.get('product.uom')
Forecast = pool.get('stock.forecast')
- LineMove = pool.get('stock.forecast.line-stock.move')
+ ForecastLine = pool.get('stock.forecast.line')
move = Move.__table__()
location_from = Location.__table__()
location_to = Location.__table__()
- line_move = LineMove.__table__()
result = dict((x.id, 0) for x in lines)
key = lambda line: line.forecast.id
@@ -389,8 +391,6 @@ class ForecastLine(ModelSQL, ModelView):
condition=move.from_location == location_from.id
).join(location_to,
condition=move.to_location == location_to.id
- ).join(line_move, 'LEFT',
- condition=move.id == line_move.move
).select(move.product, Sum(move.internal_quantity),
where=red_sql
& (location_from.left >= forecast.warehouse.left)
@@ -398,11 +398,14 @@ class ForecastLine(ModelSQL, ModelView):
& (location_to.left >= forecast.destination.left)
& (location_to.right <= forecast.destination.right)
& (move.state != 'cancel')
+ & (
+ (move.origin == Null)
+ | (~move.origin.like(ForecastLine.__name__ + ',%'))
+ )
& (Coalesce(move.effective_date, move.planned_date)
>= forecast.from_date)
& (Coalesce(move.effective_date, move.planned_date)
- <= forecast.to_date)
- & (line_move.id == Null),
+ <= forecast.to_date),
group_by=move.product))
for product_id, quantity in cursor.fetchall():
line = product2line[product_id]
@@ -461,15 +464,10 @@ class ForecastLine(ModelSQL, ModelView):
move.company = self.forecast.company
move.currency = self.forecast.company.currency
move.unit_price = unit_price
+ move.origin = self
moves.append(move)
return moves
- @classmethod
- def delete_moves(cls, lines):
- 'Delete stock moves of the forecast line'
- Move = Pool().get('stock.move')
- Move.delete([m for l in lines for m in l.moves])
-
def distribute(self, delta, qty):
'Distribute qty over delta'
range_delta = list(range(delta))
@@ -498,16 +496,6 @@ class ForecastLine(ModelSQL, ModelView):
return a
-class ForecastLineMove(ModelSQL):
- 'ForecastLine - Move'
- __name__ = 'stock.forecast.line-stock.move'
- _table = 'forecast_line_stock_move_rel'
- line = fields.Many2One('stock.forecast.line', 'Forecast Line',
- ondelete='CASCADE', select=True, required=True)
- move = fields.Many2One('stock.move', 'Move', ondelete='CASCADE',
- select=True, required=True)
-
-
class ForecastCompleteAsk(ModelView):
'Complete Forecast'
__name__ = 'stock.forecast.complete.ask'
@@ -629,3 +617,11 @@ class ForecastComplete(Wizard):
ForecastLine.save(to_save)
return 'end'
+
+
+class Move(metaclass=PoolMeta):
+ __name__ = 'stock.move'
+
+ @classmethod
+ def _get_origin(cls):
+ return super()._get_origin() + ['stock.forecast.line']
diff --git a/production.py b/production.py
index 431fe37..7506453 100644
--- a/trytond/trytond/modules/production/production.py
+++ b/trytond/trytond/modules/production/production.py
@@ -423,7 +423,7 @@ class Production(Workflow, ModelSQL, ModelView):
move.production_output = self
move.unit_price = Decimal(0)
move.save()
- self._set_move_planned_date()
+ self._set_move_planned_date([self])
return
factor = self.bom.compute_factor(self.product, self.quantity, self.uom)
@@ -445,7 +445,7 @@ class Production(Workflow, ModelSQL, ModelView):
move.production_output = self
move.unit_price = Decimal(0)
move.save()
- self._set_move_planned_date()
+ self._set_move_planned_date([self])
@classmethod
def set_cost(cls, productions):
@@ -506,15 +506,13 @@ class Production(Workflow, ModelSQL, ModelView):
values['number'] = Sequence.get_id(
config.production_sequence.id)
productions = super(Production, cls).create(vlist)
- for production in productions:
- production._set_move_planned_date()
+ cls._set_move_planned_date(productions)
return productions
@classmethod
def write(cls, *args):
super(Production, cls).write(*args)
- for production in sum(args[::2], []):
- production._set_move_planned_date()
+ cls._set_move_planned_date(sum(args[::2], []))
@classmethod
def copy(cls, productions, default=None):
@@ -529,20 +527,36 @@ class Production(Workflow, ModelSQL, ModelView):
"Return the planned dates for input and output moves"
return self.planned_start_date, self.planned_date
- def _set_move_planned_date(self):
+ @classmethod
+ def _set_move_planned_date(cls, productions):
"Set planned date of moves for the shipments"
pool = Pool()
Move = pool.get('stock.move')
- dates = self._get_move_planned_date()
- input_date, output_date = dates
- Move.write([m for m in self.inputs
- if m.state not in ('assigned', 'done', 'cancel')], {
- 'planned_date': input_date,
- })
- Move.write([m for m in self.outputs
- if m.state not in ('assigned', 'done', 'cancel')], {
- 'planned_date': output_date,
- })
+
+ grouped = {}
+ for production in productions:
+ dates = production._get_move_planned_date()
+ input_date, output_date = dates
+ grouped.setdefault(input_date, [])
+ grouped.setdefault(output_date, [])
+ for move in production.inputs:
+ if (move.state not in ('assigned', 'done', 'cancel')
+ and move.planned_date != input_date):
+ grouped[input_date].append(move)
+
+ for move in production.outputs:
+ if (move.state not in ('assigned', 'done', 'cancel')
+ and move.planned_date != output_date):
+ grouped[output_date].append(move)
+
+ to_write = []
+ for planned_date, moves in grouped.items():
+ if moves:
+ to_write.append(moves)
+ to_write.append({'planned_date': planned_date})
+ if to_write:
+ Move.write(*to_write)
+
@classmethod
@ModelView.button