From 700adb87f963308b7a233cf44d11e5fe954a1b1d Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Sun, 7 Jun 2026 07:53:18 +0200 Subject: [PATCH] Link to transport --- modules/purchase_trade/lot.py | 56 +++++++++++-------- modules/purchase_trade/tests/test_module.py | 41 ++++++++++---- .../view/lot_shipping_start_form.xml | 4 ++ 3 files changed, 68 insertions(+), 33 deletions(-) diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index 18144da..97e1cd8 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -2958,11 +2958,9 @@ class LotShipping(Wizard): 'lot.shipping.start', 'purchase_trade.shipping_view_form', [ Button("Cancel", 'end', 'tryton-cancel'), - Button("New shipment & link", 'create_shipment', 'tryton-add'), Button("Link", 'shipping', 'tryton-ok', default=True), ]) - create_shipment = StateTransition() shipping = StateTransition() def transition_start(self): @@ -2986,25 +2984,21 @@ class LotShipping(Wizard): def _record_id(record): return record.id if record else None - def _unique_supplier(self): - suppliers = {} - for record in self.records: - supplier = getattr(record, 'r_supplier', None) - if supplier: - suppliers[supplier.id] = supplier - if len(suppliers) == 1: - return next(iter(suppliers.values())) - if not suppliers: - raise UserError("Supplier is required to create a shipment.") - raise UserError( - "Please select lots with the same supplier to create a shipment.") + def _selected_shipment(self): + if self.ship.shipment == 'in': + return self.ship.shipment_in + if self.ship.shipment == 'out': + return self.ship.shipment_out + if self.ship.shipment == 'int': + return self.ship.shipment_internal def _shipment_in_create_values(self): if self.ship.shipment != 'in': raise UserError("Shipment creation is only available for Shipment In.") - supplier = self._unique_supplier() + if not self.ship.shipment_supplier: + raise UserError("Shipment supplier is required.") values = { - 'supplier': self._record_id(supplier), + 'supplier': self._record_id(self.ship.shipment_supplier), } if self.ship.planned_from_location: values['from_location'] = self.ship.planned_from_location.id @@ -3012,13 +3006,10 @@ class LotShipping(Wizard): values['to_location'] = self.ship.planned_to_location.id return values - def transition_create_shipment(self): + def _create_shipment_in(self): ShipmentIn = Pool().get('stock.shipment.in') - if self.ship.shipment_in: - return self.transition_shipping() shipment, = ShipmentIn.create([self._shipment_in_create_values()]) self.ship.shipment_in = shipment - return self.transition_shipping() def transition_shipping(self): Lot = Pool().get('lot.lot') @@ -3027,6 +3018,11 @@ class LotShipping(Wizard): affected_lines = [] if not self.ship.shipment: pass + if self.ship.create_new_shipment and self._selected_shipment(): + raise UserError( + "Choose either an existing shipment or create a new shipment.") + if self.ship.create_new_shipment: + self._create_shipment_in() with Lot.skip_quantity_consistency(): for r in self.records: if r.r_lot_shipment_in: @@ -3041,15 +3037,15 @@ class LotShipping(Wizard): shipped_quantity = Decimal(str(r.r_lot_matched)).quantize(Decimal("0.00001")) if self.ship.shipment == 'in': if not self.ship.shipment_in: - UserError("Shipment not known!") + raise UserError("Shipment not known!") shipment_origin = 'stock.shipment.in,'+str(self.ship.shipment_in.id) elif self.ship.shipment == 'out': if not self.ship.shipment_out: - UserError("Shipment not known!") + raise UserError("Shipment not known!") shipment_origin = 'stock.shipment.out,'+str(self.ship.shipment_out.id) elif self.ship.shipment == 'int': if not self.ship.shipment_internal: - UserError("Shipment not known!") + raise UserError("Shipment not known!") shipment_origin = 'stock.shipment.internal,'+str(self.ship.shipment_internal.id) if r.id < 10000000 : l = Lot(r.id) @@ -3137,6 +3133,16 @@ class LotShippingStart(ModelView): shipment_in = fields.Many2One('stock.shipment.in',"Shipment In", states={'invisible': (Eval('shipment') != 'in')}) shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out", states={'invisible': (Eval('shipment') != 'out')}) shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal", states={'invisible': (Eval('shipment') != 'int')}) + create_new_shipment = fields.Boolean( + "Create a new shipment", + states={'invisible': Eval('shipment') != 'in'}) + shipment_supplier = fields.Many2One( + 'party.party', "Shipment supplier", + states={ + 'invisible': ~Bool(Eval('create_new_shipment')), + 'required': Bool(Eval('create_new_shipment')), + }, + depends=['create_new_shipment']) planned_from_location = fields.Many2One( 'stock.location', "Planned From", readonly=True) planned_to_location = fields.Many2One( @@ -3152,6 +3158,10 @@ class LotShippingStart(ModelView): def default_shipment(cls): return 'in' + @classmethod + def default_create_new_shipment(cls): + return False + class LotPlanTransport(Wizard): "Plan lot to transport" diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 7109e2f..56d5f0d 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -219,27 +219,33 @@ class PurchaseTradeTestCase(ModuleTestCase): 'planned_to_location': 20, }) - def test_lot_shipping_create_shipment_links_planned_locations(self): - 'link to transport creates shipment in and links planned locations' + def test_lot_shipping_link_creates_shipment_from_dialog_supplier(self): + 'link to transport creates shipment in from dialog supplier' wizard = lot_module.LotShipping() supplier = Mock(id=30) created_shipment = Mock(id=40) ShipmentIn = Mock() ShipmentIn.create.return_value = [created_shipment] - wizard.records = [Mock(r_supplier=supplier)] + wizard.records = [] wizard.ship = Mock( shipment='in', shipment_in=None, + shipment_out=None, + shipment_internal=None, + create_new_shipment=True, + shipment_supplier=supplier, planned_from_location=Mock(id=10), planned_to_location=Mock(id=20), ) - with patch('trytond.modules.purchase_trade.lot.Pool') as PoolMock: - PoolMock.return_value.get.return_value = ShipmentIn - with patch.object( - wizard, 'transition_shipping', return_value='end' - ) as transition_shipping: - state = wizard.transition_create_shipment() + Lot = Mock() + Lot.skip_quantity_consistency.return_value.__enter__ = Mock() + Lot.skip_quantity_consistency.return_value.__exit__ = Mock() + pool = Mock() + pool.get.side_effect = [Lot, Mock(), Mock(), ShipmentIn] + with patch('trytond.modules.purchase_trade.lot.Pool', + return_value=pool): + state = wizard.transition_shipping() self.assertEqual(state, 'end') ShipmentIn.create.assert_called_once_with([{ @@ -248,7 +254,22 @@ class PurchaseTradeTestCase(ModuleTestCase): 'to_location': 20, }]) self.assertEqual(wizard.ship.shipment_in, created_shipment) - transition_shipping.assert_called_once_with() + Lot.assert_lines_quantity_consistency.assert_called_once_with([]) + + def test_lot_shipping_rejects_existing_and_new_shipment(self): + 'link to transport rejects choosing existing shipment and create new' + wizard = lot_module.LotShipping() + wizard.ship = Mock( + shipment='in', + shipment_in=Mock(id=40), + shipment_out=None, + shipment_internal=None, + create_new_shipment=True, + shipment_supplier=Mock(id=30), + ) + + with self.assertRaises(UserError): + wizard.transition_shipping() @with_transaction() def test_get_mtm_applies_component_ratio_as_percentage(self): diff --git a/modules/purchase_trade/view/lot_shipping_start_form.xml b/modules/purchase_trade/view/lot_shipping_start_form.xml index 33da66f..7361edb 100755 --- a/modules/purchase_trade/view/lot_shipping_start_form.xml +++ b/modules/purchase_trade/view/lot_shipping_start_form.xml @@ -4,6 +4,10 @@