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