Bug Pnl lot physic
This commit is contained in:
@@ -126,6 +126,8 @@ Owner technique: `a completer`
|
|||||||
`lot.qt` (`lot_p` purchase ouvert -> `lot_s` sale ouvert), les lignes PnL
|
`lot.qt` (`lot_p` purchase ouvert -> `lot_s` sale ouvert), les lignes PnL
|
||||||
purchase-side doivent aussi renseigner `sale` et `sale_line` afin
|
purchase-side doivent aussi renseigner `sale` et `sale_line` afin
|
||||||
d'apparaitre dans l'onglet PnL de la sale matchee
|
d'apparaitre dans l'onglet PnL de la sale matchee
|
||||||
|
- un lot ouvert / virtuel avec quantite courante a zero ne doit pas generer
|
||||||
|
de lignes de fees PnL residuelles
|
||||||
- si plusieurs sales differentes sont matchees au meme lot ouvert, ne pas
|
- si plusieurs sales differentes sont matchees au meme lot ouvert, ne pas
|
||||||
attacher arbitrairement une sale unique aux lignes purchase-side
|
attacher arbitrairement une sale unique aux lignes purchase-side
|
||||||
- Priorite:
|
- Priorite:
|
||||||
|
|||||||
@@ -474,6 +474,64 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
self.assertEqual(values[0]['sale'], sale.id)
|
self.assertEqual(values[0]['sale'], sale.id)
|
||||||
self.assertEqual(values[0]['sale_line'], sale_line.id)
|
self.assertEqual(values[0]['sale_line'], sale_line.id)
|
||||||
|
|
||||||
|
def test_purchase_open_pnl_fee_skips_zero_virtual_lot(self):
|
||||||
|
'purchase-side fee pnl ignores open virtual lots with no quantity'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
currency = Mock(id=1)
|
||||||
|
unit = Mock(id=2)
|
||||||
|
purchase_lot = Mock(id=8, sale_line=None, lot_type='virtual')
|
||||||
|
purchase_lot.get_current_quantity_converted.return_value = Decimal('0')
|
||||||
|
line = Mock(
|
||||||
|
id=9,
|
||||||
|
finished=False,
|
||||||
|
lots=[purchase_lot],
|
||||||
|
get_matched_lines=Mock(return_value=[]),
|
||||||
|
purchase=Mock(id=10, currency=currency),
|
||||||
|
unit=unit,
|
||||||
|
)
|
||||||
|
fee_lots = Mock()
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.side_effect = lambda name: {
|
||||||
|
'ir.date': Mock(today=Mock(return_value=datetime.date(2026, 4, 29))),
|
||||||
|
'currency.currency': Mock(),
|
||||||
|
'fee.lots': fee_lots,
|
||||||
|
'lot.qt': Mock(),
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
values = Valuation.create_pnl_fee_from_line(line)
|
||||||
|
|
||||||
|
self.assertEqual(values, [])
|
||||||
|
fee_lots.search.assert_not_called()
|
||||||
|
|
||||||
|
def test_sale_open_pnl_fee_skips_zero_virtual_lot(self):
|
||||||
|
'sale-side fee pnl ignores open virtual lots with no quantity'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
currency = Mock(id=1)
|
||||||
|
unit = Mock(id=2)
|
||||||
|
sale_lot = Mock(id=7, lot_type='virtual')
|
||||||
|
sale_lot.get_current_quantity_converted.return_value = Decimal('0')
|
||||||
|
sale_line = Mock(
|
||||||
|
id=6,
|
||||||
|
finished=False,
|
||||||
|
lots=[sale_lot],
|
||||||
|
sale=Mock(id=5, currency=currency),
|
||||||
|
unit=unit,
|
||||||
|
)
|
||||||
|
fee_lots = Mock()
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.side_effect = lambda name: {
|
||||||
|
'ir.date': Mock(today=Mock(return_value=datetime.date(2026, 4, 29))),
|
||||||
|
'currency.currency': Mock(),
|
||||||
|
'fee.lots': fee_lots,
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
values = Valuation.create_pnl_fee_from_sale_line(sale_line)
|
||||||
|
|
||||||
|
self.assertEqual(values, [])
|
||||||
|
fee_lots.search.assert_not_called()
|
||||||
|
|
||||||
def test_sale_report_crop_name_handles_missing_crop(self):
|
def test_sale_report_crop_name_handles_missing_crop(self):
|
||||||
'sale report crop name returns an empty string when crop is missing'
|
'sale report crop name returns an empty string when crop is missing'
|
||||||
Sale = Pool().get('sale.sale')
|
Sale = Pool().get('sale.sale')
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ class ValuationBase(ModelSQL):
|
|||||||
def _ignore_finished_open_lot(cls, line, lot):
|
def _ignore_finished_open_lot(cls, line, lot):
|
||||||
return cls._is_finished(line) and lot.lot_type != 'physic'
|
return cls._is_finished(line) and lot.lot_type != 'physic'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _lot_quantity(cls, lot):
|
||||||
|
return Decimal(str(lot.get_current_quantity_converted() or 0))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _ignore_empty_open_fee_lot(cls, lot):
|
||||||
|
return lot.lot_type != 'physic' and cls._lot_quantity(lot) == 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_matched_sale_line_from_purchase_lot(cls, lot, LotQt=None):
|
def _get_matched_sale_line_from_purchase_lot(cls, lot, LotQt=None):
|
||||||
sale_line = getattr(lot, 'sale_line', None)
|
sale_line = getattr(lot, 'sale_line', None)
|
||||||
@@ -707,6 +715,8 @@ class ValuationBase(ModelSQL):
|
|||||||
)
|
)
|
||||||
all_lots = cls._valuation_lots(line) + sale_open_lots
|
all_lots = cls._valuation_lots(line) + sale_open_lots
|
||||||
for lot in all_lots:
|
for lot in all_lots:
|
||||||
|
if cls._ignore_empty_open_fee_lot(lot):
|
||||||
|
continue
|
||||||
if lot.sale_line and cls._ignore_finished_open_lot(lot.sale_line, lot):
|
if lot.sale_line and cls._ignore_finished_open_lot(lot.sale_line, lot):
|
||||||
continue
|
continue
|
||||||
matched_sale_line = cls._get_matched_sale_line_from_purchase_lot(
|
matched_sale_line = cls._get_matched_sale_line_from_purchase_lot(
|
||||||
@@ -768,6 +778,8 @@ class ValuationBase(ModelSQL):
|
|||||||
FeeLots = Pool().get('fee.lots')
|
FeeLots = Pool().get('fee.lots')
|
||||||
|
|
||||||
for lot in cls._valuation_lots(sale_line):
|
for lot in cls._valuation_lots(sale_line):
|
||||||
|
if cls._ignore_empty_open_fee_lot(lot):
|
||||||
|
continue
|
||||||
fl = FeeLots.search([('lot', '=', lot.id)])
|
fl = FeeLots.search([('lot', '=', lot.id)])
|
||||||
if not fl:
|
if not fl:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user