From f6eb3790f3df9fb56b82d51ae22b20eae3588a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 25 Jul 2020 08:19:31 +0200 Subject: [PATCH] Use a single assign wizard issue9422 review299961011 --- CHANGELOG | 1 + __init__.py | 8 +- shipment.py | 268 ++++++++++---------------- shipment.xml | 59 +++--- tests/scenario_stock_shipment_out.rst | 22 +-- view/shipment_assign_partial_form.xml | 8 + 6 files changed, 152 insertions(+), 214 deletions(-) create mode 100644 view/shipment_assign_partial_form.xml diff --git a/CHANGELOG b/CHANGELOG index 88a4d194a9..dfa0c1c68a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use a single assign wizard for all shipments * Add access rules to product quantities by warehouse * Add moves for product quantities by warehouse * Enforce filling cost price of move diff --git a/__init__.py b/__init__.py index 33a0f3cb7c..c117577177 100644 --- a/__init__.py +++ b/__init__.py @@ -30,11 +30,9 @@ def register(): shipment.ShipmentInReturn, shipment.ShipmentOut, shipment.ShipmentOutReturn, - shipment.AssignShipmentOutAssignFailed, shipment.ShipmentInternal, + shipment.AssignPartial, shipment.Address, - shipment.AssignShipmentInternalAssignFailed, - shipment.AssignShipmentInReturnAssignFailed, period.Period, period.Cache, product.Template, @@ -57,9 +55,7 @@ def register(): res.User, module='stock', type_='model') Pool.register( - shipment.AssignShipmentOut, - shipment.AssignShipmentInternal, - shipment.AssignShipmentInReturn, + shipment.Assign, product.OpenProductQuantitiesByWarehouse, product.RecomputeCostPrice, product.ModifyCostPrice, diff --git a/shipment.py b/shipment.py index 19f148e5b5..66b272b91f 100644 --- a/shipment.py +++ b/shipment.py @@ -8,7 +8,7 @@ from functools import partial from sql import Null from trytond.i18n import gettext -from trytond.model import Workflow, ModelView, ModelSQL, fields +from trytond.model import Workflow, ModelView, ModelSQL, fields, dualmethod from trytond.model.exceptions import AccessError from trytond.modules.company import CompanyReport from trytond.wizard import Wizard, StateTransition, StateView, Button @@ -28,6 +28,43 @@ class ShipmentMixin: ] +class ShipmentAssignMixin(ShipmentMixin): + + @classmethod + def assign_wizard(cls, shipments): + raise NotImplementedError + + @property + def assign_moves(self): + raise NotImplementedError + + @dualmethod + @ModelView.button + def assign_try(cls, shipments): + raise NotImplementedError + + @dualmethod + def assign_reset(cls, shipments): + cls.wait(shipments) + + @dualmethod + @ModelView.button + def assign_force(cls, shipments): + cls.assign(shipments) + + @dualmethod + def assign_ignore(cls, shipments): + pool = Pool() + Move = pool.get('stock.move') + Move.write([ + m for s in shipments for m in s.assign_moves + if m.assignation_required + and m.state in {'staging', 'draft'}], { + 'quantity': 0, + }) + cls.assign(shipments) + + class ShipmentIn(ShipmentMixin, Workflow, ModelSQL, ModelView): "Supplier Shipment" __name__ = 'stock.shipment.in' @@ -457,7 +494,7 @@ class ShipmentIn(ShipmentMixin, Workflow, ModelSQL, ModelView): }) -class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): +class ShipmentInReturn(ShipmentAssignMixin, Workflow, ModelSQL, ModelView): "Supplier Return Shipment" __name__ = 'stock.shipment.in.return' _rec_name = 'number' @@ -736,7 +773,7 @@ class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): @set_employee('assigned_by') def assign(cls, shipments): Move = Pool().get('stock.move') - Move.assign([m for s in shipments for m in s.moves]) + Move.assign([m for s in shipments for m in s.assign_moves]) @classmethod @ModelView.button @@ -764,7 +801,11 @@ class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): def assign_wizard(cls, shipments): pass - @classmethod + @property + def assign_moves(self): + return self.moves + + @dualmethod @ModelView.button def assign_try(cls, shipments, with_childs=None): pool = Pool() @@ -772,7 +813,7 @@ class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): to_assign = defaultdict(list) for shipment in shipments: location_type = shipment.from_location.type - for move in shipment.moves: + for move in shipment.assign_moves: if move.assignation_required: to_assign[location_type].append(move) success = True @@ -789,13 +830,8 @@ class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): cls.assign(shipments) return success - @classmethod - @ModelView.button - def assign_force(cls, shipments): - cls.assign(shipments) - -class ShipmentOut(ShipmentMixin, Workflow, ModelSQL, ModelView): +class ShipmentOut(ShipmentAssignMixin, Workflow, ModelSQL, ModelView): "Customer Shipment" __name__ = 'stock.shipment.out' _rec_name = 'number' @@ -1134,6 +1170,9 @@ class ShipmentOut(ShipmentMixin, Workflow, ModelSQL, ModelView): @Workflow.transition('assigned') @set_employee('assigned_by') def assign(cls, shipments): + pool = Pool() + Move = pool.get('stock.move') + Move.assign([m for s in shipments for m in s.assign_moves]) cls._sync_inventory_to_outgoing(shipments, quantity=False) @classmethod @@ -1347,11 +1386,16 @@ class ShipmentOut(ShipmentMixin, Workflow, ModelSQL, ModelView): def assign_wizard(cls, shipments): pass - @classmethod + @property + def assign_moves(self): + return self.inventory_moves + + @dualmethod @ModelView.button def assign_try(cls, shipments): Move = Pool().get('stock.move') - to_assign = [m for s in shipments for m in s.inventory_moves + to_assign = [ + m for s in shipments for m in s.assign_moves if m.assignation_required] if Move.assign_try(to_assign): cls.assign(shipments) @@ -1359,13 +1403,6 @@ class ShipmentOut(ShipmentMixin, Workflow, ModelSQL, ModelView): else: return False - @classmethod - @ModelView.button - def assign_force(cls, shipments): - Move = Pool().get('stock.move') - Move.assign([m for s in shipments for m in s.inventory_moves]) - cls.assign(shipments) - class ShipmentOutReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): "Customer Return Shipment" @@ -1774,56 +1811,7 @@ class ShipmentOutReturn(ShipmentMixin, Workflow, ModelSQL, ModelView): cls.save(shipments) -class AssignShipmentOutAssignFailed(ModelView): - 'Assign Customer Shipment' - __name__ = 'stock.shipment.out.assign.failed' - inventory_moves = fields.Many2Many('stock.move', None, None, - 'Inventory Moves', readonly=True, - help="The inventory moves that were not assigned.") - - @staticmethod - def default_inventory_moves(): - ShipmentOut = Pool().get('stock.shipment.out') - shipment_id = Transaction().context.get('active_id') - if not shipment_id: - return [] - shipment = ShipmentOut(shipment_id) - return [x.id for x in shipment.inventory_moves if x.state == 'draft'] - - -class AssignShipmentOut(Wizard): - 'Assign Customer Shipment' - __name__ = 'stock.shipment.out.assign' - start = StateTransition() - failed = StateView('stock.shipment.out.assign.failed', - 'stock.shipment_out_assign_failed_view_form', [ - Button('Force Assign', 'force', 'tryton-forward', - states={ - 'invisible': ~Id('stock', - 'group_stock_force_assignment').in_( - Eval('context', {}).get('groups', [])), - }), - Button('OK', 'end', 'tryton-ok', True), - ]) - force = StateTransition() - - def transition_start(self): - pool = Pool() - Shipment = pool.get('stock.shipment.out') - - if Shipment.assign_try([Shipment(Transaction().context['active_id'])]): - return 'end' - else: - return 'failed' - - def transition_force(self): - Shipment = Pool().get('stock.shipment.out') - - Shipment.assign_force([Shipment(Transaction().context['active_id'])]) - return 'end' - - -class ShipmentInternal(ShipmentMixin, Workflow, ModelSQL, ModelView): +class ShipmentInternal(ShipmentAssignMixin, Workflow, ModelSQL, ModelView): "Internal Shipment" __name__ = 'stock.shipment.internal' _rec_name = 'number' @@ -2353,7 +2341,9 @@ class ShipmentInternal(ShipmentMixin, Workflow, ModelSQL, ModelView): @Workflow.transition('assigned') @set_employee('assigned_by') def assign(cls, shipments): - pass + pool = Pool() + Move = pool.get('stock.move') + Move.assign([m for s in shipments for m in s.assign_moves]) @classmethod @ModelView.button @@ -2394,11 +2384,16 @@ class ShipmentInternal(ShipmentMixin, Workflow, ModelSQL, ModelView): def assign_wizard(cls, shipments): pass - @classmethod + @property + def assign_moves(self): + return self.outgoing_moves + + @dualmethod @ModelView.button def assign_try(cls, shipments): Move = Pool().get('stock.move') - to_assign = [m for s in shipments for m in s.outgoing_moves + to_assign = [ + m for s in shipments for m in s.assign_moves if m.assignation_required] if not to_assign or Move.assign_try(to_assign): cls.assign(shipments) @@ -2406,13 +2401,6 @@ class ShipmentInternal(ShipmentMixin, Workflow, ModelSQL, ModelView): else: return False - @classmethod - @ModelView.button - def assign_force(cls, shipments): - Move = Pool().get('stock.move') - Move.assign([m for s in shipments for m in s.outgoing_moves]) - cls.assign(shipments) - @property def _move_planned_date(self): ''' @@ -2457,103 +2445,61 @@ class Address(metaclass=PoolMeta): help="Check to send deliveries to the address.") -class AssignShipmentInternalAssignFailed(ModelView): - 'Assign Shipment Internal' - __name__ = 'stock.shipment.internal.assign.failed' - moves = fields.Many2Many('stock.move', None, None, 'Moves', - readonly=True, - help="The moves that were not assigned.") - - @staticmethod - def default_moves(): - ShipmentInternal = Pool().get('stock.shipment.internal') - shipment_id = Transaction().context.get('active_id') - if not shipment_id: - return [] - shipment = ShipmentInternal(shipment_id) - return [x.id for x in shipment.outgoing_moves if x.state == 'draft'] - - -class AssignShipmentInternal(Wizard): - 'Assign Shipment Internal' - __name__ = 'stock.shipment.internal.assign' +class Assign(Wizard): + "Assign Shipment" + __name__ = 'stock.shipment.assign' start = StateTransition() - failed = StateView('stock.shipment.internal.assign.failed', - 'stock.shipment_internal_assign_failed_view_form', [ - Button('Force Assign', 'force', 'tryton-forward', - states={ - 'invisible': ~Id('stock', - 'group_stock_force_assignment').in_( - Eval('context', {}).get('groups', [])), - }), - Button('OK', 'end', 'tryton-ok', True), - ]) - force = StateTransition() - - def transition_start(self): - pool = Pool() - Shipment = pool.get('stock.shipment.internal') - - if Shipment.assign_try([Shipment(Transaction().context['active_id'])]): - return 'end' - else: - return 'failed' - - def transition_force(self): - Shipment = Pool().get('stock.shipment.internal') - - Shipment.assign_force([Shipment(Transaction().context['active_id'])]) - return 'end' - - -class AssignShipmentInReturnAssignFailed(ModelView): - 'Assign Supplier Return Shipment' - __name__ = 'stock.shipment.in.return.assign.failed' - moves = fields.Many2Many('stock.move', None, None, 'Moves', - readonly=True, - help="The moves that were not assigned.") - - @staticmethod - def default_moves(): - ShipmentInternal = Pool().get('stock.shipment.in.return') - shipment_id = Transaction().context.get('active_id') - if not shipment_id: - return [] - shipment = ShipmentInternal(shipment_id) - return [x.id for x in shipment.moves if x.state == 'draft'] - - -class AssignShipmentInReturn(Wizard): - 'Assign Supplier Return Shipment' - __name__ = 'stock.shipment.in.return.assign' - start = StateTransition() - failed = StateView('stock.shipment.in.return.assign.failed', - 'stock.shipment_in_return_assign_failed_view_form', [ - Button('Force Assign', 'force', 'tryton-forward', + partial = StateView( + 'stock.shipment.assign.partial', + 'stock.shipment_assign_partial_view_form', [ + Button("Cancel", 'cancel', 'tryton-cancel'), + Button("Wait", 'end', 'tryton-ok', True), + Button("Ignore", 'ignore', 'tryton-forward'), + Button("Force", 'force', 'tryton-forward', states={ 'invisible': ~Id('stock', 'group_stock_force_assignment').in_( Eval('context', {}).get('groups', [])), }), - Button('OK', 'end', 'tryton-ok', True), ]) + cancel = StateTransition() force = StateTransition() + ignore = StateTransition() def transition_start(self): - pool = Pool() - Shipment = pool.get('stock.shipment.in.return') - - if Shipment.assign_try([Shipment(Transaction().context['active_id'])]): + if self.record.assign_try(): return 'end' else: - return 'failed' + return 'partial' + + def default_partial(self, fields): + values = {} + if 'moves' in fields: + values['moves'] = [ + m.id for m in self.record.assign_moves + if m.state in {'staging', 'draft'}] + return values + + def transition_cancel(self): + self.record.assign_reset() + return 'end' def transition_force(self): - Shipment = Pool().get('stock.shipment.in.return') - - Shipment.assign_force([Shipment(Transaction().context['active_id'])]) + self.record.assign_force() return 'end' + def transition_ignore(self): + self.record.assign_ignore() + return 'end' + + +class AssignPartial(ModelView): + "Assign Shipment" + __name__ = 'stock.shipment.assign.partial' + moves = fields.Many2Many( + 'stock.move', None, None, "Moves", readonly=True, + help="The moves that were not assigned.") + class DeliveryNote(CompanyReport): 'Delivery Note' diff --git a/shipment.xml b/shipment.xml index 92275c3388..70fe33a993 100644 --- a/shipment.xml +++ b/shipment.xml @@ -54,12 +54,6 @@ this repository contains the full copyright notices and license terms. --> - - Assign Purchase Return Shipment - stock.shipment.in.return.assign - stock.shipment.in.return - - stock.shipment.in.return form @@ -122,18 +116,6 @@ this repository contains the full copyright notices and license terms. --> action="act_shipment_in_return_form" id="menu_shipment_in_return_form"/> - - stock.shipment.in.return.assign.failed - form - shipment_in_return_assign_failed_form - - - - Assign Customer Shipment - stock.shipment.out.assign - stock.shipment.out - - stock.shipment.out form @@ -199,18 +181,6 @@ this repository contains the full copyright notices and license terms. --> - - stock.shipment.out.assign.failed - form - shipment_out_assign_failed_form - - - - Assign Shipment Internal - stock.shipment.internal.assign - stock.shipment.internal - - stock.shipment.internal form @@ -288,12 +258,6 @@ this repository contains the full copyright notices and license terms. --> action="act_shipment_internal_form" id="menu_shipment_internal_form"/> - - stock.shipment.internal.assign.failed - form - shipment_internal_assign_failed_form - - stock.shipment.out.return form @@ -1069,5 +1033,28 @@ this repository contains the full copyright notices and license terms. --> pyson="1"/> + + + stock.shipment.assign.partial + form + shipment_assign_partial_form + + + + Assign Supplier Return Shipment + stock.shipment.assign + stock.shipment.in.return + + + Assign Customer Shipment + stock.shipment.assign + stock.shipment.out + + + Assign Internal Shipment + stock.shipment.assign + stock.shipment.internal + + diff --git a/tests/scenario_stock_shipment_out.rst b/tests/scenario_stock_shipment_out.rst index a8a137ba7d..c3f8b25a83 100644 --- a/tests/scenario_stock_shipment_out.rst +++ b/tests/scenario_stock_shipment_out.rst @@ -132,8 +132,10 @@ Make 1 unit of the product available:: Assign the shipment now:: - >>> shipment_out.click('assign_try') - False + >>> shipment_assign = Wizard('stock.shipment.assign', [shipment_out]) + >>> len(shipment_assign.form.moves) + 1 + >>> shipment_assign.execute('end') >>> shipment_out.reload() >>> len(shipment_out.outgoing_moves) 2 @@ -152,14 +154,12 @@ Assign the shipment now:: >>> len(set(planned_dates)) 1 -Delete the draft move, assign and pack shipment:: +Ignore non assigned moves and pack shipment:: - >>> for move in shipment_out.inventory_moves: - ... if move.state == 'draft': - ... break - >>> shipment_out.inventory_moves.remove(move) - >>> shipment_out.click('assign_try') - True + >>> shipment_assign = Wizard('stock.shipment.assign', [shipment_out]) + >>> shipment_assign.execute('ignore') + >>> sorted([m.quantity for m in shipment_out.inventory_moves]) + [0.0, 1.0] >>> shipment_out.assigned_by == employee True >>> shipment_out.packed_by @@ -174,7 +174,7 @@ Delete the draft move, assign and pack shipment:: >>> len(shipment_out.outgoing_moves) 2 >>> len(shipment_out.inventory_moves) - 1 + 2 >>> shipment_out.inventory_moves[0].state 'done' >>> sum([m.quantity for m in shipment_out.inventory_moves]) == \ @@ -199,7 +199,7 @@ Set the state as Done:: >>> len(shipment_out.outgoing_moves) 2 >>> len(shipment_out.inventory_moves) - 1 + 2 >>> shipment_out.inventory_moves[0].state 'done' >>> sum([m.quantity for m in shipment_out.inventory_moves]) == \ diff --git a/view/shipment_assign_partial_form.xml b/view/shipment_assign_partial_form.xml new file mode 100644 index 0000000000..992fc1e0e8 --- /dev/null +++ b/view/shipment_assign_partial_form.xml @@ -0,0 +1,8 @@ + + +
+ + + +