Add physical + Link

This commit is contained in:
2026-06-22 15:33:43 +02:00
parent 8f676c2396
commit a880535bc4
4 changed files with 391 additions and 92 deletions

View File

@@ -372,6 +372,22 @@ class PurchaseTradeTestCase(ModuleTestCase):
'planned_to_location': 20,
})
def test_lot_shipping_defaults_purchase_locations_without_plan(self):
'link to transport falls back to purchase locations without a plan'
wizard = lot_module.LotShipping()
wizard.records = [Mock(
r_planned_from_location=None,
r_planned_to_location=None,
r_purchase=Mock(
from_location=Mock(id=10),
to_location=Mock(id=20)),
)]
self.assertEqual(wizard.default_ship(None), {
'planned_from_location': 10,
'planned_to_location': 20,
})
def test_lot_shipping_defaults_uniform_planned_locations(self):
'link to transport carries uniform planned locations from selection'
wizard = lot_module.LotShipping()
@@ -509,6 +525,47 @@ class PurchaseTradeTestCase(ModuleTestCase):
Lot.assert_lines_quantity_consistency.assert_called_once_with([
lot.line])
def test_lot_shipping_open_quantity_uses_shared_transport_link(self):
'standalone link action keeps linking open lot.qt quantities'
wizard = lot_module.LotShipping()
shipment = Mock(id=40)
wizard.ship = Mock(
shipment='in',
shipment_in=shipment,
shipment_out=None,
shipment_internal=None,
create_new_shipment=False,
quantity=None,
)
wizard.records = [Mock(
id=10000007,
r_lot_shipment_in=None,
r_lot_quantity=Decimal('125'),
r_lot_matched=Decimal('0'),
)]
purchase_line = Mock()
source = Mock(
lot_p=Mock(line=purchase_line),
lot_s=None,
)
Lot = Mock()
Lot.skip_quantity_consistency.return_value.__enter__ = Mock()
Lot.skip_quantity_consistency.return_value.__exit__ = Mock()
LotQt = Mock(return_value=source)
Move = Mock()
pool = Mock()
pool.get.side_effect = [Lot, LotQt, Move]
with patch('trytond.modules.purchase_trade.lot.Pool',
return_value=pool):
state = wizard.transition_shipping()
self.assertEqual(state, 'end')
LotQt.link_to_transport.assert_called_once_with(
source, Decimal('125.00000'), 'stock.shipment.in,40')
Lot.assert_lines_quantity_consistency.assert_called_once_with([
purchase_line])
def test_lot_unship_restores_planned_open_quantity(self):
'unlinking a planned scheduled lot.qt returns it to planned'
wizard = lot_module.LotUnship()
@@ -648,14 +705,10 @@ class PurchaseTradeTestCase(ModuleTestCase):
def test_lot_shipping_scheduled_lotqt_keeps_planned_locations(self):
'scheduled lot.qt keeps the planned from and to locations'
wizard = lot_module.LotShipping()
wizard.ship = Mock(
shipment='in',
shipment_in=Mock(id=40),
shipment_out=None,
shipment_internal=None,
)
LotQt = Pool().get('lot.qt')
linked = Mock()
lqt = Mock(
lot_quantity=Decimal('125'),
lot_p=Mock(id=1),
lot_s=Mock(id=2),
lot_unit=Mock(id=3),
@@ -669,11 +722,15 @@ class PurchaseTradeTestCase(ModuleTestCase):
planned_to_date=datetime.date(2026, 7, 5),
planned_note='POL to POD',
)
LotQt = Mock()
lqt.is_planned_transport.return_value = True
wizard._create_scheduled_lotqt(LotQt, lqt, Decimal('125'))
with patch.object(
LotQt, 'create', return_value=[linked]) as create, \
patch.object(LotQt, 'save') as save:
result = LotQt.link_to_transport(
lqt, Decimal('125'), 'stock.shipment.in,40')
LotQt.create.assert_called_once_with([{
create.assert_called_once_with([{
'lot_p': 1,
'lot_s': 2,
'lot_quantity': Decimal('125'),
@@ -689,6 +746,124 @@ class PurchaseTradeTestCase(ModuleTestCase):
'planned_note': 'POL to POD',
'lot_shipment_in': 40,
}])
save.assert_called_once_with([lqt])
self.assertEqual(lqt.lot_quantity, 0)
self.assertIs(result, linked)
def test_add_physical_lot_defaults_transport_from_purchase(self):
'add physical lot proposes shipment creation from purchase values'
wizard = lot_module.LotAdding()
purchase = Mock(
party=Mock(id=30),
from_location=Mock(id=10),
to_location=Mock(id=20),
)
lqt = Mock(
lot_p=Mock(line=Mock(
purchase=purchase,
unit=Mock(id=3))),
lot_quantity=Decimal('125'),
lot_shipment_in=None,
lot_shipment_internal=None,
lot_shipment_out=None,
planned_from_location=None,
planned_to_location=None,
)
lqt.lot_p.lot_unit_line = Mock(id=3)
LotQt = Mock(return_value=lqt)
pool = Mock()
pool.get.side_effect = [Mock(), LotQt]
with patch('trytond.modules.purchase_trade.lot.Pool',
return_value=pool), patch(
'trytond.modules.purchase_trade.lot.Transaction'
) as TransactionMock:
TransactionMock.return_value.context = {
'active_ids': [10000007]}
values = wizard.default_add(None)
self.assertTrue(values['transport_missing'])
self.assertEqual(values['shipment_supplier'], 30)
self.assertEqual(values['transport_from_location'], 10)
self.assertEqual(values['transport_to_location'], 20)
def test_add_physical_lot_links_existing_shipment_before_add(self):
'add physical lot can link an existing shipment in one transaction'
wizard = lot_module.LotAdding()
shipment = Mock(id=40)
source = Mock(lot_quantity=Decimal('125'))
source.has_shipment.return_value = False
linked = Mock()
LotQt = Mock(return_value=source)
LotQt.link_to_transport.return_value = linked
Lot = Mock()
Lot.skip_quantity_consistency.return_value.__enter__ = Mock()
Lot.skip_quantity_consistency.return_value.__exit__ = Mock()
pool = Mock()
pool.get.side_effect = [LotQt, Lot]
wizard.records = [Mock(id=10000007)]
wizard.add = Mock(
shipment_in=shipment,
create_new_shipment=False,
lots=[Mock()],
unlink_remainder=True,
)
with patch('trytond.modules.purchase_trade.lot.Pool',
return_value=pool):
state = wizard.transition_adding()
self.assertEqual(state, 'end')
LotQt.link_to_transport.assert_called_once_with(
source, Decimal('125'), 'stock.shipment.in,40')
LotQt.add_physical_lots.assert_called_once_with(
linked, wizard.add.lots, True)
def test_add_physical_lot_creates_shipment_with_optional_bl(self):
'add physical lot creates and links a shipment with optional BL data'
wizard = lot_module.LotAdding()
shipment = Mock(id=40)
source = Mock(lot_quantity=Decimal('125'))
source.has_shipment.return_value = False
linked = Mock()
LotQt = Mock(return_value=source)
LotQt.link_to_transport.return_value = linked
ShipmentIn = Mock()
ShipmentIn.create.return_value = [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 = [LotQt, ShipmentIn, Lot]
wizard.records = [Mock(id=10000007)]
wizard.add = Mock(
shipment_in=None,
create_new_shipment=True,
shipment_supplier=Mock(id=30),
transport_from_location=Mock(id=10),
transport_to_location=Mock(id=20),
shipment_bl_number='BL-2026-01',
shipment_bl_date=datetime.date(2026, 6, 22),
lots=[Mock()],
unlink_remainder=True,
)
with patch('trytond.modules.purchase_trade.lot.Pool',
return_value=pool):
state = wizard.transition_adding()
self.assertEqual(state, 'end')
ShipmentIn.create.assert_called_once_with([{
'supplier': 30,
'from_location': 10,
'to_location': 20,
'bl_number': 'BL-2026-01',
'bl_date': datetime.date(2026, 6, 22),
}])
LotQt.link_to_transport.assert_called_once_with(
source, Decimal('125'), 'stock.shipment.in,40')
LotQt.add_physical_lots.assert_called_once_with(
linked, wizard.add.lots, True)
@with_transaction()
def test_get_mtm_applies_component_ratio_as_percentage(self):
@@ -2604,6 +2779,18 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(shipment.get_shipment_type(), 'inbound')
@with_transaction()
def test_shipment_in_location_domains_follow_trade_direction(self):
'shipment locations exclude invalid trade counterpart directions'
ShipmentIn = Pool().get('stock.shipment.in')
self.assertEqual(
ShipmentIn.from_location.domain,
[('type', '!=', 'customer')])
self.assertEqual(
ShipmentIn.to_location.domain,
[('type', '!=', 'supplier')])
@with_transaction()
def test_lot_report_shipment_type_uses_linked_shipment(self):
'lot report shipment type mirrors the linked shipment_in'