diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index ab48007..1492f5f 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -4233,7 +4233,7 @@ class LotUnship(Wizard): lot = Lot(r.id) if lqt and r.r_lot_type == 'virtual': shipment = None - qt = lqt.lot_quantity + qt = Decimal(str(lqt.lot_quantity or 0)) mode = 'both' if not lqt.lot_p: lot = lqt.lot_s @@ -4246,13 +4246,17 @@ class LotUnship(Wizard): shipment = lqt.lot_shipment_internal elif lqt.lot_shipment_out: shipment = lqt.lot_shipment_out - #Decrease forecasted virtual part shipped - lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s,mode) - #Increase forecasted virtual part non shipped - if lqt.is_planned_transport(): - LotQt.restore_planned_open_quantity(lqt, qt) - elif not lot.updateVirtualPart(qt,None,lqt.lot_s,mode): - lot.createVirtualPart(qt,None,lqt.lot_s,mode) + if not shipment: + continue + # Restore the selected scheduled quantity only once, then + # remove its shipment-linked lot.qt so it cannot be + # unlinked again from the shipment planned tab. + if qt: + if lqt.is_planned_transport(): + LotQt.restore_planned_open_quantity(lqt, qt) + elif not lot.updateVirtualPart(qt,None,lqt.lot_s,mode): + lot.createVirtualPart(qt,None,lqt.lot_s,mode) + LotQt.delete([lqt]) if lqt.lot_p and lqt.lot_p.line: affected_lines.append(lqt.lot_p.line) if lqt.lot_s and lqt.lot_s.sale_line: diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index d6fc592..35c5e33 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -704,6 +704,9 @@ class PurchaseTradeTestCase(ModuleTestCase): lot_quantity=Decimal('40'), lot_p=lot, lot_s=sale_lot, + lot_shipment_in=Mock(), + lot_shipment_internal=None, + lot_shipment_out=None, lot_shipment_origin='stock.shipment.in,12', ) lqt.is_planned_transport.return_value = True @@ -722,14 +725,53 @@ class PurchaseTradeTestCase(ModuleTestCase): state = wizard.transition_start() self.assertEqual(state, 'end') - lot.updateVirtualPart.assert_called_once_with( - Decimal('-40'), 'stock.shipment.in,12', sale_lot, 'both') + lot.updateVirtualPart.assert_not_called() LotQt.restore_planned_open_quantity.assert_called_once_with( lqt, Decimal('40')) + LotQt.delete.assert_called_once_with([lqt]) lot.createVirtualPart.assert_not_called() Lot.assert_lines_quantity_consistency.assert_called_once_with([ purchase_line, sale_line]) + def test_lot_unship_deletes_zero_scheduled_lotqt_without_restore(self): + 'unlinking a residual zero scheduled lot.qt only cleans the shipment tab' + wizard = lot_module.LotUnship() + wizard.records = [Mock(id=10000007, r_lot_type='virtual')] + purchase_line = Mock() + sale_line = Mock() + lot = Mock(line=purchase_line) + sale_lot = Mock(sale_line=sale_line) + lqt = Mock( + lot_quantity=Decimal('0'), + lot_p=lot, + lot_s=sale_lot, + lot_shipment_in=Mock(), + lot_shipment_internal=None, + lot_shipment_out=None, + ) + lqt.is_planned_transport.return_value = True + Lot = Mock() + Lot.return_value = Mock() + Lot.skip_quantity_consistency.return_value.__enter__ = Mock() + Lot.skip_quantity_consistency.return_value.__exit__ = Mock() + LotQt = Mock() + LotQt.return_value = lqt + 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_start() + + self.assertEqual(state, 'end') + LotQt.restore_planned_open_quantity.assert_not_called() + lot.updateVirtualPart.assert_not_called() + lot.createVirtualPart.assert_not_called() + LotQt.delete.assert_called_once_with([lqt]) + Lot.assert_lines_quantity_consistency.assert_called_once_with([ + purchase_line, sale_line]) + def test_add_physical_lots_requires_linked_transport(self): 'physical lots can only be added from a scheduled quantity' LotQt = Pool().get('lot.qt')