changeset 38e2a76db719 in modules/stock:default
details: https://hg.tryton.org/modules/stock?cmd=changeset;node=38e2a76db719
description:
Improve moves synchronisation on shipments
By using the origin as synchronisation key, the code is simplified but
also it
allows to follow when products are changed.
The same design is applied to the internal shipment which allows to
synchronise
also the lots.
issue8363
review261811002
diffstat:
CHANGELOG | 1 +
shipment.py | 227 +++++++++++++++-------------
tests/scenario_stock_shipment_internal.rst | 12 +-
3 files changed, 135 insertions(+), 105 deletions(-)
diffs (341 lines):
diff -r cfecd4a9939a -r 38e2a76db719 CHANGELOG
--- a/CHANGELOG Fri Aug 09 16:40:04 2019 +0200
+++ b/CHANGELOG Sun Aug 18 19:22:43 2019 +0200
@@ -1,3 +1,4 @@
+* Improve moves synchronisation on shipments
* Allow move as move's origin
* Open product quantities by warehouse to show moves
diff -r cfecd4a9939a -r 38e2a76db719 shipment.py
--- a/shipment.py Fri Aug 09 16:40:04 2019 +0200
+++ b/shipment.py Sun Aug 18 19:22:43 2019 +0200
@@ -1132,7 +1132,7 @@
@Workflow.transition('assigned')
@set_employee('assigned_by')
def assign(cls, shipments):
- cls._sync_inventory_to_outgoing(shipments, create=True, write=False)
+ cls._sync_inventory_to_outgoing(shipments, quantity=False)
@classmethod
@ModelView.button
@@ -1142,95 +1142,84 @@
pool = Pool()
Move = pool.get('stock.move')
Move.do([m for s in shipments for m in s.inventory_moves])
- cls._sync_inventory_to_outgoing(shipments)
+ cls._sync_inventory_to_outgoing(shipments, quantity=True)
Move.assign([m for s in shipments for m in s.outgoing_moves])
- def _get_outgoing_move(self, move):
- 'Return outgoing move for the inventory move'
+ def _sync_move_key(self, move):
+ return (
+ ('product', move.product),
+ ('uom', move.uom),
+ )
+
+ def _sync_outgoing_move(self, template=None):
pool = Pool()
Move = pool.get('stock.move')
- return Move(
- from_location=move.to_location,
- to_location=self.customer.customer_location,
- product=move.product,
- uom=move.uom,
- quantity=move.quantity,
+ move = Move(
+ from_location=self.warehouse_output,
+ to_location=self.customer_location,
+ quantity=0,
shipment=self,
planned_date=self.planned_date,
- company=move.company,
- currency=move.company.currency,
- unit_price=move.unit_price,
+ company=self.company,
)
+ if template:
+ move.origin = template.origin
+ move.currency = template.currency
+ move.unit_price = template.unit_price
+ else:
+ move.currency = self.company.currency
+ move.unit_price = 0
+ return move
@classmethod
- def _sync_inventory_to_outgoing(cls, shipments, create=True, write=True):
- 'Synchronise outgoing moves with inventory moves'
+ def _sync_inventory_to_outgoing(cls, shipments, quantity=True):
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
+
+ def active(move):
+ return move.state != 'cancel'
+
+ moves = []
for shipment in shipments:
if shipment.warehouse_storage == shipment.warehouse_output:
- # Do not create inventory moves
+ # Do not have inventory moves
continue
- # Sum all outgoing quantities
- outgoing_qty = {}
- for move in shipment.outgoing_moves:
- if move.state == 'cancel':
- continue
- quantity = Uom.compute_qty(move.uom, move.quantity,
- move.product.default_uom, round=False)
- outgoing_qty.setdefault(move.product.id, 0.0)
- outgoing_qty[move.product.id] += quantity
- to_create = []
- for move in shipment.inventory_moves:
- if move.state == 'cancel':
- continue
- qty_default_uom = Uom.compute_qty(move.uom, move.quantity,
+ outgoing_moves = {m: m for m in shipment.outgoing_moves}
+ inventory_qty = defaultdict(lambda: defaultdict(float))
+ for move in filter(active, shipment.outgoing_moves):
+ key = shipment._sync_move_key(move)
+ inventory_qty[move][key] = 0
+ for move in filter(active, shipment.inventory_moves):
+ key = shipment._sync_move_key(move)
+ outgoing_move = outgoing_moves.get(move.origin)
+ qty_default_uom = Uom.compute_qty(
+ move.uom, move.quantity,
move.product.default_uom, round=False)
- # Check if the outgoing move doesn't exist already
- if outgoing_qty.get(move.product.id):
- # If it exist, decrease the sum
- if qty_default_uom <= outgoing_qty[move.product.id]:
- outgoing_qty[move.product.id] -= qty_default_uom
+ inventory_qty[outgoing_move][key] += qty_default_uom
+
+ for outgoing_move in inventory_qty:
+ if outgoing_move:
+ outgoing_key = shipment._sync_move_key(outgoing_move)
+ for key, qty in inventory_qty[outgoing_move].items():
+ if not quantity and outgoing_move:
+ # Do not create outgoing move with origin
+ # to allow to reset to draft
continue
- # Else create the complement
+ if outgoing_move and key == outgoing_key:
+ move = outgoing_move
else:
- out_quantity = (qty_default_uom
- - outgoing_qty[move.product.id])
- out_quantity = Uom.compute_qty(
- move.product.default_uom, out_quantity, move.uom)
- outgoing_qty[move.product.id] = 0.0
- else:
- out_quantity = move.quantity
-
- if not out_quantity:
- continue
- unit_price = Uom.compute_price(move.product.default_uom,
- move.product.list_price, move.uom)
- to_create.append(shipment._get_outgoing_move(move))
- to_create[-1].quantity = out_quantity
- to_create[-1].unit_price = unit_price
- if create and to_create:
- Move.save(to_create)
-
- # Re-read the shipment and remove exceeding quantities
- for move in shipment.outgoing_moves:
- if move.state == 'cancel':
- continue
- if outgoing_qty.get(move.product.id, 0.0) > 0.0:
- exc_qty = Uom.compute_qty(move.product.default_uom,
- outgoing_qty[move.product.id], move.uom)
- removed_qty = Uom.compute_qty(move.uom,
- min(exc_qty, move.quantity), move.product.default_uom,
- round=False)
- if write:
- Move.write([move], {
- 'quantity': max(
- 0.0,
- move.uom.round(move.quantity - exc_qty)),
- })
- outgoing_qty[move.product.id] -= removed_qty
+ move = shipment._sync_outgoing_move(outgoing_move)
+ for name, value in key:
+ setattr(move, name, value)
+ qty = Uom.compute_qty(
+ move.product.default_uom, qty,
+ move.uom)
+ if quantity and move.quantity != qty:
+ move.quantity = qty
+ moves.append(move)
+ Move.save(moves)
@classmethod
@ModelView.button
@@ -2192,44 +2181,76 @@
default.setdefault('done_by', None)
return super().copy(shipments, default=default)
+ def _sync_move_key(self, move):
+ return (
+ ('product', move.product),
+ ('uom', move.uom),
+ )
+
+ def _sync_incoming_move(self, template=None):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ move = Move(
+ from_location=self.transit_location,
+ to_location=self.to_location,
+ quantity=0,
+ shipment=self,
+ planned_date=self.planned_date,
+ company=self.company,
+ )
+ if template:
+ move.origin = template.origin
+ move.currency = template.currency
+ move.unit_price = template.unit_price
+ else:
+ move.currency = self.company.currency
+ move.unit_price = 0
+ return move
+
@classmethod
def _sync_moves(cls, shipments):
- 'Synchronise incoming moves with outgoing moves'
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
- to_delete = []
- to_save = []
+
+ def active(move):
+ return move.state != 'cancel'
+
+ moves = []
for shipment in shipments:
if not shipment.transit_location:
continue
- product_qty = defaultdict(int)
- for move in shipment.outgoing_moves:
- if move.state == 'cancel':
- continue
- product_qty[move.product] += Uom.compute_qty(
- move.uom, move.quantity, move.product.default_uom,
- round=False)
+
+ incoming_moves = {m: m for m in shipment.incoming_moves}
+ outgoing_qty = defaultdict(lambda: defaultdict(lambda: 0))
+ for move in filter(active, shipment.incoming_moves):
+ key = shipment._sync_move_key(move)
+ outgoing_qty[move][key] = 0
+ for move in filter(active, shipment.outgoing_moves):
+ key = shipment._sync_move_key(move)
+ incoming_move = incoming_moves.get(move.origin)
+ qty_default_uom = Uom.compute_qty(
+ move.uom, move.quantity,
+ move.product.default_uom, round=False)
+ outgoing_qty[incoming_move][key] += qty_default_uom
- for move in shipment.incoming_moves:
- if move.state == 'cancel':
- continue
- if product_qty[move.product] <= 0:
- to_delete.append(move)
- else:
- quantity = Uom.compute_qty(
- move.uom, move.quantity, move.product.default_uom,
- round=False)
- quantity = min(product_qty[move.product], quantity)
- move.quantity = Uom.compute_qty(
- move.product.default_uom, quantity, move.uom)
- product_qty[move.product] -= quantity
- to_save.append(move)
-
- if to_save:
- Move.save(to_save)
- if to_delete:
- Move.delete(to_delete)
+ for incoming_move in outgoing_qty:
+ if incoming_move:
+ incoming_key = shipment._sync_move_key(incoming_move)
+ for key, qty in outgoing_qty[incoming_move].items():
+ if incoming_move and key == incoming_key:
+ move = incoming_move
+ else:
+ move = shipment._sync_incoming_move(incoming_move)
+ for name, value in key:
+ setattr(move, name, value)
+ qty = Uom.compute_qty(
+ move.product.default_uom, qty,
+ move.uom)
+ if move.quantity != qty:
+ move.quantity = qty
+ moves.append(move)
+ Move.save(moves)
@classmethod
def _set_transit(cls, shipments):
@@ -2245,12 +2266,14 @@
and m.from_location != shipment.transit_location
and m.to_location != shipment.transit_location]
Move.copy(moves, default={
- 'from_location': shipment.transit_location.id,
+ 'to_location': shipment.transit_location.id,
'planned_date': shipment.planned_date,
+ 'origin': lambda data: '%s,%s' % (
+ Move.__name__, data['id']),
})
to_write.append(moves)
to_write.append({
- 'to_location': shipment.transit_location.id,
+ 'from_location': shipment.transit_location.id,
'planned_date': shipment.planned_start_date,
})
if to_write:
diff -r cfecd4a9939a -r 38e2a76db719 tests/scenario_stock_shipment_internal.rst
--- a/tests/scenario_stock_shipment_internal.rst Fri Aug 09 16:40:04
2019 +0200
+++ b/tests/scenario_stock_shipment_internal.rst Sun Aug 18 19:22:43
2019 +0200
@@ -152,7 +152,7 @@
True
>>> move = shipment.moves.new()
>>> move.product = product
- >>> move.quantity = 1
+ >>> move.quantity = 2
>>> move.from_location = internal_loc
>>> move.to_location = storage_loc
>>> shipment.click('wait')
@@ -160,22 +160,28 @@
2
>>> outgoing_move, = shipment.outgoing_moves
>>> outgoing_move.quantity
- 1.0
+ 2.0
>>> outgoing_move.from_location == internal_loc
True
>>> outgoing_move.to_location == shipment.transit_location
True
>>> incoming_move, = shipment.incoming_moves
>>> incoming_move.quantity
- 1.0
+ 2.0
>>> incoming_move.from_location == shipment.transit_location
True
>>> incoming_move.to_location == storage_loc
True
+ >>> outgoing_move.quantity = 1
+ >>> outgoing_move.save()
+
>>> shipment.click('assign_try')
True
>>> shipment.click('ship')
+ >>> incoming_move, = shipment.incoming_moves
+ >>> incoming_move.quantity
+ 1.0
>>> shipment.outgoing_moves[0].state
'done'
>>> shipment.click('done')