diff --git a/modules/purchase_trade/fee.py b/modules/purchase_trade/fee.py
index 7e824e9..8557467 100755
--- a/modules/purchase_trade/fee.py
+++ b/modules/purchase_trade/fee.py
@@ -482,17 +482,24 @@ class Fee(ModelSQL,ModelView):
if quantity is None:
quantity = self.get_quantity()
return Decimal(quantity or 0)
-
+
+ @staticmethod
+ def _unsigned_amount(amount):
+ if amount is None:
+ return None
+ return abs(Decimal(amount))
+
def get_amount(self,name=None):
sign = Decimal(1)
if self.price:
# if self.p_r:
- # if self.p_r == 'pay':
- # sign = -1
- if self.mode == 'lumpsum':
- return self.price * sign
- elif self.mode == 'ppack':
- return round(self.price * self.quantity,2)
+ # if self.p_r == 'pay':
+ # sign = -1
+ if self.mode == 'lumpsum':
+ return self._unsigned_amount(self.price * sign)
+ elif self.mode == 'ppack':
+ return self._unsigned_amount(
+ round(self.price * self.quantity,2))
elif self.mode == 'rate':
if self.line:
if self.line.estimated_date:
@@ -503,7 +510,9 @@ class Fee(ModelSQL,ModelView):
Decimal(self.price) / Decimal(100)
* Decimal(est_line.fin_int_delta) / Decimal(360)
)
- return round(factor * self.line.unit_price * self._get_amount_quantity() * sign,2)
+ return self._unsigned_amount(round(
+ factor * self.line.unit_price
+ * self._get_amount_quantity() * sign,2))
if self.sale_line:
if self.sale_line.estimated_date:
est_lines = [
@@ -516,38 +525,38 @@ class Fee(ModelSQL,ModelView):
Decimal(self.price) / Decimal(100)
* Decimal(est_line.fin_int_delta) / Decimal(360)
)
- return round(
+ return self._unsigned_amount(round(
factor * self.sale_line.unit_price
- * self._get_amount_quantity() * sign, 2)
-
- elif self.mode == 'perqt':
- if self.shipment_in:
- StockMove = Pool().get('stock.move')
- sm = StockMove.search(['shipment','=','stock.shipment.in,'+str(self.shipment_in.id)])
- if sm:
- unique_lots = {e.lot for e in sm if e.lot}
- return round(self.price * Decimal(sum([e.get_current_quantity_converted(0,self.unit) for e in unique_lots])) * sign,2)
- LotQt = Pool().get('lot.qt')
- lqts = LotQt.search(['lot_shipment_in','=',self.shipment_in.id])
- if lqts:
- return round(self.price * Decimal(lqts[0].lot_quantity) * sign,2)
-
- return round((self.quantity if self.quantity else 0) * self.price * sign,2)
- elif self.mode == 'pprice':
- if self.line:
- return round(self.price / 100 * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2)
- if self.sale_line:
- return round(self.price / 100 * self.sale_line.unit_price * (self.quantity if self.quantity else 0) * sign,2)
- if self.shipment_in:
- StockMove = Pool().get('stock.move')
- sm = StockMove.search(['shipment','=','stock.shipment.in,'+str(self.shipment_in.id)])
- if sm:
- if sm[0].lot:
- return round(self.price * Decimal(sum([e.lot.get_lot_price() for e in sm if e.lot])) / 100 * self.quantity * sign,2)
- LotQt = Pool().get('lot.qt')
- lqts = LotQt.search(['lot_shipment_in','=',self.shipment_in.id])
- if lqts:
- return round(self.price * Decimal(lqts[0].lot_p.get_lot_price()) / 100 * lqts[0].lot_quantity * sign,2)
+ * self._get_amount_quantity() * sign, 2))
+
+ elif self.mode == 'perqt':
+ if self.shipment_in:
+ StockMove = Pool().get('stock.move')
+ sm = StockMove.search(['shipment','=','stock.shipment.in,'+str(self.shipment_in.id)])
+ if sm:
+ unique_lots = {e.lot for e in sm if e.lot}
+ return self._unsigned_amount(round(self.price * Decimal(sum([e.get_current_quantity_converted(0,self.unit) for e in unique_lots])) * sign,2))
+ LotQt = Pool().get('lot.qt')
+ lqts = LotQt.search(['lot_shipment_in','=',self.shipment_in.id])
+ if lqts:
+ return self._unsigned_amount(round(self.price * Decimal(lqts[0].lot_quantity) * sign,2))
+
+ return self._unsigned_amount(round((self.quantity if self.quantity else 0) * self.price * sign,2))
+ elif self.mode == 'pprice':
+ if self.line:
+ return self._unsigned_amount(round(self.price / 100 * self.line.unit_price * (self.quantity if self.quantity else 0) * sign,2))
+ if self.sale_line:
+ return self._unsigned_amount(round(self.price / 100 * self.sale_line.unit_price * (self.quantity if self.quantity else 0) * sign,2))
+ if self.shipment_in:
+ StockMove = Pool().get('stock.move')
+ sm = StockMove.search(['shipment','=','stock.shipment.in,'+str(self.shipment_in.id)])
+ if sm:
+ if sm[0].lot:
+ return self._unsigned_amount(round(self.price * Decimal(sum([e.lot.get_lot_price() for e in sm if e.lot])) / 100 * self.quantity * sign,2))
+ LotQt = Pool().get('lot.qt')
+ lqts = LotQt.search(['lot_shipment_in','=',self.shipment_in.id])
+ if lqts:
+ return self._unsigned_amount(round(self.price * Decimal(lqts[0].lot_p.get_lot_price()) / 100 * lqts[0].lot_quantity * sign,2))
@classmethod
def copy(cls, fees, default=None):
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index 8737c1b..2b13d22 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -416,6 +416,53 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(fee.get_amount(), Decimal('3.33'))
+ def test_sale_rate_fee_amount_stays_positive_with_negative_delta(self):
+ 'sale rate fee display amount is unsigned even when financing delta is negative'
+ Fee = Pool().get('fee.fee')
+ fee = Fee()
+ fee.mode = 'rate'
+ fee.price = Decimal('12')
+ fee.fee_date = None
+ fee.quantity = Decimal('10')
+ fee.unit = Mock()
+ fee.shipment_in = None
+ fee.line = None
+ fee.sale_line = Mock(
+ unit_price=Decimal('100'),
+ lots=[],
+ estimated_date=[
+ Mock(
+ trigger='bldate',
+ estimated_date=datetime.date(2026, 5, 23),
+ fin_int_delta=-10,
+ ),
+ ],
+ )
+
+ self.assertEqual(fee.get_amount(), Decimal('3.33'))
+
+ def test_fee_pnl_applies_pay_sign_to_unsigned_rate_amount(self):
+ 'fee pnl sign is applied once from PAY/REC'
+ Valuation = Pool().get('valuation.valuation')
+ lot = Mock()
+ lot.get_current_quantity_converted.return_value = Decimal('10')
+ fee = Mock(
+ mode='rate',
+ price=Decimal('12'),
+ unit=Mock(),
+ line=None,
+ sale_line=Mock(
+ unit_price=Decimal('100'),
+ estimated_date=[
+ Mock(trigger='bldate', fin_int_delta=-10),
+ ],
+ ),
+ )
+
+ self.assertEqual(
+ Valuation._fee_amount_for_lot_or_zero(fee, lot),
+ Decimal('3.33'))
+
def test_create_pnl_price_from_line_keeps_finished_physical_sale_line(self):
'purchase valuation keeps finished sale-side pnl on physical lots'
Valuation = Pool().get('valuation.valuation')
diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py
index 1c490d1..2e4be3c 100644
--- a/modules/purchase_trade/valuation.py
+++ b/modules/purchase_trade/valuation.py
@@ -811,15 +811,15 @@ class ValuationBase(ModelSQL):
@classmethod
def _fee_amount_or_zero(cls, fee):
amount = fee.get_amount() if hasattr(fee, 'get_amount') else fee.amount
- return Decimal(amount or 0)
+ return abs(Decimal(amount or 0))
@classmethod
def _fee_amount_for_lot_or_zero(cls, fee, lot):
if fee.mode == 'ppack' and getattr(lot, 'lot_qt', None):
- return round(
+ return abs(round(
Decimal(fee.price or 0)
* Decimal(str(lot.lot_qt or 0)),
- 2)
+ 2))
if fee.mode == 'rate':
line = fee.line or getattr(fee, 'sale_line', None)
if line and line.estimated_date:
@@ -833,9 +833,9 @@ class ValuationBase(ModelSQL):
* Decimal(est_line.fin_int_delta) / Decimal(360))
quantity = Decimal(
lot.get_current_quantity_converted(0, fee.unit) or 0)
- return round(
+ return abs(round(
factor * Decimal(line.unit_price or 0) * quantity,
- 2)
+ 2))
return Decimal(0)
return cls._fee_amount_or_zero(fee)
diff --git a/modules/purchase_trade/view/contract_detail_tree.xml b/modules/purchase_trade/view/contract_detail_tree.xml
index 67b318f..e249a3d 100755
--- a/modules/purchase_trade/view/contract_detail_tree.xml
+++ b/modules/purchase_trade/view/contract_detail_tree.xml
@@ -11,7 +11,6 @@
-