From 31e3eb142cea7d9843c8ccc5cb206824487e2772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 9 Feb 2024 16:42:28 +0100 Subject: [PATCH] Unify shipment assignation scheduled jobs The order shipments are assigned is important as it determines which order will be done or not. So we must try to assign all the shipments and productions following a defined order. Closes #13027 --- modules/production/ir.py | 14 +- modules/stock/CHANGELOG | 1 + modules/stock/ir.py | 42 +++++- modules/stock/shipment.py | 9 +- .../scenario_stock_shipment_cron_assign.rst | 137 ++++++++++++++++++ 5 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 modules/stock/tests/scenario_stock_shipment_cron_assign.rst diff --git a/modules/production/ir.py b/modules/production/ir.py index 4caba21423..f4044cc99d 100644 --- a/modules/production/ir.py +++ b/modules/production/ir.py @@ -1,6 +1,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. from trytond.pool import PoolMeta +from trytond.transaction import Transaction class Cron(metaclass=PoolMeta): @@ -12,5 +13,16 @@ class Cron(metaclass=PoolMeta): cls.method.selection.extend([ ('production|set_cost_from_moves', "Set Cost from Moves"), ('production|reschedule', "Reschedule Productions"), - ('production|assign_cron', "Assign Productions"), ]) + + @classmethod + def __register__(cls, module): + table = cls.__table__() + cursor = Transaction().connection.cursor() + + super().__register__(module) + + # Migration from 7.0: replace assign_cron + cursor.execute(*table.update( + [table.method], ['ir.cron|stock_shipment_assign_try'], + where=table.method == 'production|assign_cron')) diff --git a/modules/stock/CHANGELOG b/modules/stock/CHANGELOG index 2d0d8e2cab..4c0edcc965 100644 --- a/modules/stock/CHANGELOG +++ b/modules/stock/CHANGELOG @@ -1,3 +1,4 @@ +* Unify shipment assignation scheduled jobs * Fill shipment number only in waiting state * Manage assignation for customer shipment with same picking and output location * Add pblc argument to assign_try diff --git a/modules/stock/ir.py b/modules/stock/ir.py index e6b83dd701..6a9a20fd94 100644 --- a/modules/stock/ir.py +++ b/modules/stock/ir.py @@ -1,6 +1,13 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.pool import PoolMeta + +from itertools import groupby +from operator import attrgetter + +from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction + +from .shipment import ShipmentAssignMixin class Cron(metaclass=PoolMeta): @@ -18,8 +25,33 @@ class Cron(metaclass=PoolMeta): "Reschedule Supplier Return Shipments"), ('stock.shipment.internal|reschedule', "Reschedule Internal Shipments"), - ('stock.shipment.out|assign_cron', - "Assign Customer Shipments"), - ('stock.shipment.internal|assign_cron', - "Assign Internal Shipments"), + ('ir.cron|stock_shipment_assign_try', "Assign Shipments"), ]) + + @classmethod + def __register__(cls, module): + table = cls.__table__() + cursor = Transaction().connection.cursor() + + super().__register__(module) + + # Migration from 7.0: replace assign_cron + cursor.execute(*table.update( + [table.method], ['ir.cron|stock_shipment_assign_try'], + where=table.method.in_([ + 'stock.shipment.out|assign_cron', + 'stock.shipment.internal|assign_cron', + ]))) + + @classmethod + def stock_shipment_assign_try(cls): + pool = Pool() + records = [] + for _, kls in pool.iterobject(): + if issubclass(kls, ShipmentAssignMixin): + records.extend(kls.to_assign()) + + records.sort(key=attrgetter('assign_order_key')) + + for kls, records in groupby(records, key=attrgetter('__class__')): + kls.assign_try(list(records)) diff --git a/modules/stock/shipment.py b/modules/stock/shipment.py index bf38ee19db..2aa60b48fa 100644 --- a/modules/stock/shipment.py +++ b/modules/stock/shipment.py @@ -210,9 +210,12 @@ class ShipmentAssignMixin(ShipmentMixin): ] @classmethod - def assign_cron(cls): - shipments = cls.search(cls._get_assign_domain()) - cls.assign_try(shipments) + def to_assign(cls): + return cls.search(cls._get_assign_domain()) + + @property + def assign_order_key(self): + return (self.planned_date, self.create_date) def get_partially_assigned(self, name): return (self.state != 'assigned' diff --git a/modules/stock/tests/scenario_stock_shipment_cron_assign.rst b/modules/stock/tests/scenario_stock_shipment_cron_assign.rst new file mode 100644 index 0000000000..254f4d0daa --- /dev/null +++ b/modules/stock/tests/scenario_stock_shipment_cron_assign.rst @@ -0,0 +1,137 @@ +========================== +Stock Shipment Cron Assign +========================== + +Imports:: + + >>> import datetime as dt + >>> from decimal import Decimal + + >>> from proteus import Model + >>> from trytond.modules.company.tests.tools import create_company, get_company + >>> from trytond.tests.tools import activate_modules + + >>> today = dt.date.today() + >>> yesterday = today - dt.timedelta(days=1) + +Activate modules:: + + >>> config = activate_modules('stock') + + >>> Cron = Model.get('ir.cron') + >>> Location = Model.get('stock.location') + >>> Move = Model.get('stock.move') + >>> Party = Model.get('party.party') + >>> ProductTemplate = Model.get('product.template') + >>> ShipmentInternal = Model.get('stock.shipment.internal') + >>> ShipmentOut = Model.get('stock.shipment.out') + >>> UoM = Model.get('product.uom') + +Create company:: + + >>> _ = create_company() + >>> company = get_company() + +Create a product:: + + >>> unit, = UoM.find([('name', '=', "Unit")]) + + >>> template = ProductTemplate() + >>> template.name = "Product" + >>> template.type = 'goods' + >>> template.default_uom = unit + >>> template.save() + >>> product, = template.products + +Create customer:: + + >>> customer = Party(name="Customer") + >>> customer.save() + +Get locations:: + + >>> storage_loc, = Location.find([('code', '=', "STO")]) + >>> supplier_loc, = Location.find([('code', '=', "SUP")]) + >>> lost_loc, = Location.find([('type', '=', 'lost_found')], limit=1) + +Fill storage:: + + >>> move = Move() + >>> move.product = product + >>> move.quantity = 5 + >>> move.from_location = supplier_loc + >>> move.to_location = storage_loc + >>> move.unit_price = Decimal('10.0000') + >>> move.currency = company.currency + >>> move.effective_date = yesterday + >>> move.click('do') + >>> move.state + 'done' + +Create different shipments with different planned dates:: + + >>> shipment_int_1 = ShipmentInternal() + >>> shipment_int_1.planned_date = today + >>> shipment_int_1.from_location = storage_loc + >>> shipment_int_1.to_location = lost_loc + >>> move = shipment_int_1.moves.new() + >>> move.from_location = shipment_int_1.from_location + >>> move.to_location = shipment_int_1.to_location + >>> move.product = product + >>> move.quantity = 2 + >>> shipment_int_1.click('wait') + >>> shipment_int_1.state + 'waiting' + + >>> shipment_out_1 = ShipmentOut() + >>> shipment_out_1.planned_date = today + >>> shipment_out_1.customer = customer + >>> move = shipment_out_1.outgoing_moves.new() + >>> move.from_location = shipment_out_1.warehouse_output + >>> move.to_location = shipment_out_1.customer_location + >>> move.product = product + >>> move.quantity = 2 + >>> move.unit_price = Decimal('10.0000') + >>> move.currency = company.currency + >>> shipment_out_1.click('wait') + >>> shipment_out_1.state + 'waiting' + + >>> shipment_int_2, = shipment_int_1.duplicate() + >>> shipment_int_2.click('wait') + >>> shipment_int_2.state + 'waiting' + + >>> shipment_out_2, = shipment_out_1.duplicate() + >>> shipment_out_2.click('wait') + >>> shipment_out_2.state + 'waiting' + +Run assignation cron:: + + >>> cron = Cron(method='ir.cron|stock_shipment_assign_try') + >>> cron.interval_number = 1 + >>> cron.interval_type = 'days' + >>> cron.click('run_once') + +Check assignations:: + + >>> shipment_int_1.reload() + >>> shipment_int_1.state + 'assigned' + + >>> shipment_out_1.reload() + >>> shipment_out_1.state + 'assigned' + + >>> shipment_int_2.reload() + >>> shipment_int_2.state + 'waiting' + >>> shipment_int_2.partially_assigned + True + + >>> shipment_out_2.reload() + >>> shipment_out_2.state + 'waiting' + >>> shipment_out_2.partially_assigned + False