Pnl shipment
This commit is contained in:
@@ -2788,6 +2788,7 @@ class ValuationDyn(metaclass=PoolMeta):
|
||||
Max(val.strategy).as_('r_strategy'),
|
||||
Max(val.lot).as_('r_lot'),
|
||||
Max(val.sale_line).as_('r_sale_line'),
|
||||
Max(val.shipment_in).as_('r_shipment_in'),
|
||||
where=wh,
|
||||
group_by=[val.purchase,val.sale])
|
||||
|
||||
|
||||
@@ -1815,11 +1815,26 @@ class ShipmentIn(metaclass=PoolMeta):
|
||||
|
||||
def get_pnl_lines(self, name=None):
|
||||
lot_ids = self._pnl_lot_ids()
|
||||
if not lot_ids:
|
||||
if not lot_ids and not self.id:
|
||||
return []
|
||||
ValuationLine = Pool().get('valuation.valuation.line')
|
||||
if self.id and lot_ids:
|
||||
domain = ['OR',
|
||||
[('shipment_in', '=', self.id)],
|
||||
[
|
||||
('shipment_in', '=', None),
|
||||
('lot', 'in', lot_ids),
|
||||
],
|
||||
]
|
||||
elif self.id:
|
||||
domain = [('shipment_in', '=', self.id)]
|
||||
else:
|
||||
domain = [
|
||||
('shipment_in', '=', None),
|
||||
('lot', 'in', lot_ids),
|
||||
]
|
||||
lines = ValuationLine.search(
|
||||
[('lot', 'in', lot_ids)],
|
||||
domain,
|
||||
order=[('date', 'DESC'), ('id', 'DESC')])
|
||||
return [line.id for line in lines]
|
||||
|
||||
|
||||
@@ -639,9 +639,11 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
sale_line=sale_line)
|
||||
sale_lot.get_current_quantity_converted.return_value = Decimal('2000')
|
||||
purchase_line.lots = [purchase_lot]
|
||||
shipment = Mock(id=70)
|
||||
lotqt = Mock(
|
||||
lot_p=purchase_lot, lot_s=sale_lot,
|
||||
lot_quantity=Decimal('500'), lot_unit=unit)
|
||||
lot_quantity=Decimal('500'), lot_unit=unit,
|
||||
lot_shipment_in=shipment)
|
||||
lotqt_model = Mock()
|
||||
lotqt_model.search.return_value = [lotqt]
|
||||
pool = Mock()
|
||||
@@ -653,9 +655,11 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(len(lines), 2)
|
||||
self.assertEqual(lines[0]['type'], 'pur. priced')
|
||||
self.assertEqual(lines[0]['quantity'], Decimal('500.00000'))
|
||||
self.assertEqual(lines[0]['shipment_in'], shipment.id)
|
||||
self.assertEqual(lines[0]['amount'], Decimal('-22500.00'))
|
||||
self.assertEqual(lines[1]['type'], 'sale priced')
|
||||
self.assertEqual(lines[1]['quantity'], Decimal('500.00000'))
|
||||
self.assertEqual(lines[1]['shipment_in'], shipment.id)
|
||||
self.assertEqual(lines[1]['amount'], Decimal('50000.00'))
|
||||
|
||||
@with_transaction()
|
||||
@@ -2981,6 +2985,7 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
'shipment PnL uses valuation lines from physical and open lot links'
|
||||
ShipmentIn = Pool().get('stock.shipment.in')
|
||||
shipment = ShipmentIn()
|
||||
shipment.id = 99
|
||||
physical_lot = Mock(id=1)
|
||||
open_purchase_lot = Mock(id=2)
|
||||
open_sale_lot = Mock(id=3)
|
||||
@@ -3000,7 +3005,13 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(shipment.get_pnl_lines(None), [10, 11])
|
||||
|
||||
valuation_model.search.assert_called_once_with(
|
||||
[('lot', 'in', [1, 2, 3])],
|
||||
['OR',
|
||||
[('shipment_in', '=', 99)],
|
||||
[
|
||||
('shipment_in', '=', None),
|
||||
('lot', 'in', [1, 2, 3]),
|
||||
],
|
||||
],
|
||||
order=[('date', 'DESC'), ('id', 'DESC')])
|
||||
|
||||
def test_sale_and_purchase_tolerance_option_defaults_to_empty(self):
|
||||
|
||||
@@ -63,6 +63,7 @@ class ValuationBase(ModelSQL):
|
||||
mtm = fields.Numeric("Mtm",digits=(16,2))
|
||||
strategy = fields.Many2One('mtm.strategy',"Strategy")
|
||||
lot = fields.Many2One('lot.lot',"Lot")
|
||||
shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
|
||||
base_amount = fields.Numeric("Base Amount",digits=(16,2))
|
||||
rate = fields.Numeric("Rate", digits=(16,6))
|
||||
|
||||
@@ -98,6 +99,18 @@ class ValuationBase(ModelSQL):
|
||||
def _lot_quantity(cls, lot):
|
||||
return Decimal(str(lot.get_current_quantity_converted() or 0))
|
||||
|
||||
@classmethod
|
||||
def _lot_shipment_in_id(cls, lot):
|
||||
shipment = getattr(lot, 'lot_shipment_in', None)
|
||||
shipment_id = cls._record_id(shipment) if shipment else None
|
||||
return shipment_id if isinstance(shipment_id, int) else None
|
||||
|
||||
@classmethod
|
||||
def _lotqt_shipment_in_id(cls, lqt):
|
||||
shipment = getattr(lqt, 'lot_shipment_in', None)
|
||||
shipment_id = cls._record_id(shipment) if shipment else None
|
||||
return shipment_id if isinstance(shipment_id, int) else None
|
||||
|
||||
@classmethod
|
||||
def _lotqt_quantity(cls, lqt, line):
|
||||
quantity = abs(Decimal(str(getattr(lqt, 'lot_quantity', 0) or 0)))
|
||||
@@ -130,6 +143,7 @@ class ValuationBase(ModelSQL):
|
||||
'sale_quantity': None,
|
||||
'sale_lot': sale_lot,
|
||||
'sale_line': getattr(sale_lot, 'sale_line', None),
|
||||
'shipment_in': cls._lot_shipment_in_id(lot),
|
||||
}]
|
||||
|
||||
lot_id = getattr(lot, 'id', None)
|
||||
@@ -139,6 +153,7 @@ class ValuationBase(ModelSQL):
|
||||
'sale_quantity': None,
|
||||
'sale_lot': None,
|
||||
'sale_line': None,
|
||||
'shipment_in': cls._lot_shipment_in_id(lot),
|
||||
}]
|
||||
|
||||
lqts = LotQt.search([
|
||||
@@ -153,6 +168,7 @@ class ValuationBase(ModelSQL):
|
||||
'sale_quantity': None,
|
||||
'sale_lot': None,
|
||||
'sale_line': matched_sale_line,
|
||||
'shipment_in': cls._lot_shipment_in_id(lot),
|
||||
}]
|
||||
|
||||
segments = []
|
||||
@@ -166,6 +182,7 @@ class ValuationBase(ModelSQL):
|
||||
if sale_line else None),
|
||||
'sale_lot': sale_lot,
|
||||
'sale_line': sale_line,
|
||||
'shipment_in': cls._lotqt_shipment_in_id(lqt),
|
||||
})
|
||||
return segments
|
||||
|
||||
@@ -268,6 +285,7 @@ class ValuationBase(ModelSQL):
|
||||
value.get('product'),
|
||||
value.get('state'),
|
||||
value.get('strategy'),
|
||||
value.get('shipment_in'),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -292,6 +310,7 @@ class ValuationBase(ModelSQL):
|
||||
('product', '=', value.get('product')),
|
||||
('state', '=', value.get('state')),
|
||||
('strategy', '=', value.get('strategy')),
|
||||
('shipment_in', '=', value.get('shipment_in')),
|
||||
]
|
||||
if value.get('sale_line'):
|
||||
domain.append(('sale_line', '=', value.get('sale_line')))
|
||||
@@ -384,7 +403,8 @@ class ValuationBase(ModelSQL):
|
||||
ValuationLine.delete(valuation_lines)
|
||||
|
||||
@classmethod
|
||||
def _base_pnl(cls, *, line, lot, pnl_type, sale=None, sale_line=None):
|
||||
def _base_pnl(cls, *, line, lot, pnl_type, sale=None, sale_line=None,
|
||||
shipment_in=None):
|
||||
Date = Pool().get('ir.date')
|
||||
|
||||
values = {
|
||||
@@ -393,6 +413,7 @@ class ValuationBase(ModelSQL):
|
||||
'type': pnl_type,
|
||||
'date': Date.today(),
|
||||
'lot': lot.id,
|
||||
'shipment_in': shipment_in or cls._lot_shipment_in_id(lot),
|
||||
}
|
||||
|
||||
if sale:
|
||||
@@ -411,6 +432,7 @@ class ValuationBase(ModelSQL):
|
||||
'type': pnl_type,
|
||||
'date': Date.today(),
|
||||
'lot': lot.id,
|
||||
'shipment_in': cls._lot_shipment_in_id(lot),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -606,7 +628,7 @@ class ValuationBase(ModelSQL):
|
||||
|
||||
@classmethod
|
||||
def _build_basis_pnl(cls, *, line, lot, sale_line, pc, sign,
|
||||
extra_price=Decimal(0), quantity=None):
|
||||
extra_price=Decimal(0), quantity=None, shipment_in=None):
|
||||
Currency = Pool().get('currency.currency')
|
||||
Date = Pool().get('ir.date')
|
||||
values = cls._base_pnl(
|
||||
@@ -614,7 +636,8 @@ class ValuationBase(ModelSQL):
|
||||
lot=lot,
|
||||
sale=sale_line.sale if sale_line else None,
|
||||
sale_line=sale_line if sale_line else None,
|
||||
pnl_type='sale priced' if sale_line else 'pur. priced'
|
||||
pnl_type='sale priced' if sale_line else 'pur. priced',
|
||||
shipment_in=shipment_in,
|
||||
)
|
||||
|
||||
qty = (Decimal(str(quantity)) if quantity is not None
|
||||
@@ -671,7 +694,7 @@ class ValuationBase(ModelSQL):
|
||||
|
||||
@classmethod
|
||||
def _build_simple_pnl(cls, *, line, lot, sale_line, price, state, sign,
|
||||
pnl_type, quantity=None):
|
||||
pnl_type, quantity=None, shipment_in=None):
|
||||
Currency = Pool().get('currency.currency')
|
||||
Date = Pool().get('ir.date')
|
||||
values = cls._base_pnl(
|
||||
@@ -679,7 +702,8 @@ class ValuationBase(ModelSQL):
|
||||
lot=lot,
|
||||
sale=sale_line.sale if sale_line else None,
|
||||
sale_line=sale_line if sale_line else None,
|
||||
pnl_type=pnl_type
|
||||
pnl_type=pnl_type,
|
||||
shipment_in=shipment_in,
|
||||
)
|
||||
|
||||
qty = (Decimal(str(quantity)) if quantity is not None
|
||||
@@ -744,6 +768,7 @@ class ValuationBase(ModelSQL):
|
||||
segment):
|
||||
matched_sale_line = segment['sale_line']
|
||||
quantity = segment['quantity']
|
||||
shipment_in = segment.get('shipment_in')
|
||||
|
||||
if line.price_type == 'basis':
|
||||
premium_delta = cls._get_basis_premium_delta(line)
|
||||
@@ -758,6 +783,7 @@ class ValuationBase(ModelSQL):
|
||||
sign=-1,
|
||||
pnl_type='pur. priced',
|
||||
quantity=quantity,
|
||||
shipment_in=shipment_in,
|
||||
)
|
||||
cls._set_matched_sale_values(values, matched_sale_line)
|
||||
cls._append_pnl_values(price_lines, values, line)
|
||||
@@ -765,7 +791,8 @@ class ValuationBase(ModelSQL):
|
||||
for pc in summaries:
|
||||
values = cls._build_basis_pnl(
|
||||
line=line, lot=lot, sale_line=None, pc=pc, sign=-1,
|
||||
extra_price=premium_delta, quantity=quantity)
|
||||
extra_price=premium_delta, quantity=quantity,
|
||||
shipment_in=shipment_in)
|
||||
cls._set_matched_sale_values(values, matched_sale_line)
|
||||
cls._append_pnl_values(price_lines, values, line)
|
||||
|
||||
@@ -779,6 +806,7 @@ class ValuationBase(ModelSQL):
|
||||
sign=-1,
|
||||
pnl_type=f'pur. {line.price_type}',
|
||||
quantity=quantity,
|
||||
shipment_in=shipment_in,
|
||||
)
|
||||
cls._set_matched_sale_values(values, matched_sale_line)
|
||||
cls._append_pnl_values(price_lines, values, line)
|
||||
@@ -800,7 +828,8 @@ class ValuationBase(ModelSQL):
|
||||
values = cls._build_basis_pnl(
|
||||
line=line, lot=sale_lot, sale_line=sale_line, pc=pc,
|
||||
sign=+1, extra_price=premium_delta,
|
||||
quantity=segment.get('sale_quantity', quantity))
|
||||
quantity=segment.get('sale_quantity', quantity),
|
||||
shipment_in=shipment_in)
|
||||
cls._append_pnl_values(price_lines, values, sale_line)
|
||||
|
||||
elif sale_line.price_type in ('priced', 'efp'):
|
||||
@@ -813,6 +842,7 @@ class ValuationBase(ModelSQL):
|
||||
sign=+1,
|
||||
pnl_type=f'sale {sale_line.price_type}',
|
||||
quantity=segment.get('sale_quantity', quantity),
|
||||
shipment_in=shipment_in,
|
||||
)
|
||||
cls._append_pnl_values(price_lines, values, sale_line)
|
||||
|
||||
@@ -1086,6 +1116,9 @@ class ValuationBase(ModelSQL):
|
||||
'sale_line': matched_sale_line.id if matched_sale_line else None,
|
||||
'purchase': line.purchase.id,
|
||||
'line': line.id,
|
||||
'shipment_in': (
|
||||
sf.shipment_in.id if sf.shipment_in
|
||||
else cls._lot_shipment_in_id(lot)),
|
||||
'type': (
|
||||
'shipment fee' if sf.shipment_in
|
||||
else 'sale fee' if sf.sale_line
|
||||
@@ -1152,6 +1185,9 @@ class ValuationBase(ModelSQL):
|
||||
'lot': lot.id,
|
||||
'sale': sale_line.sale.id,
|
||||
'sale_line': sale_line.id,
|
||||
'shipment_in': (
|
||||
sf.shipment_in.id if sf.shipment_in
|
||||
else cls._lot_shipment_in_id(lot)),
|
||||
'type': (
|
||||
'shipment fee' if sf.shipment_in
|
||||
else 'sale fee'
|
||||
@@ -1377,6 +1413,7 @@ class ValuationDyn(ModelSQL,ModelView):
|
||||
r_mtm = fields.Numeric("Mtm",digits='r_unit')
|
||||
r_strategy = fields.Many2One('mtm.strategy',"Strategy")
|
||||
r_lot = fields.Many2One('lot.lot',"Lot")
|
||||
r_shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
|
||||
|
||||
@classmethod
|
||||
def table_query(cls):
|
||||
@@ -1411,6 +1448,7 @@ class ValuationDyn(ModelSQL,ModelView):
|
||||
Sum(val.mtm).as_('r_mtm'),
|
||||
Max(val.strategy).as_('r_strategy'),
|
||||
Max(val.lot).as_('r_lot'),
|
||||
Max(val.shipment_in).as_('r_shipment_in'),
|
||||
where=wh,
|
||||
group_by=[val.type,val.counterparty,val.state])
|
||||
|
||||
@@ -1428,11 +1466,14 @@ class ValuationReport(ValuationBase, ModelView):
|
||||
context = Transaction().context
|
||||
valuation_date = context.get('valuation_date')
|
||||
strategy = context.get('strategy')
|
||||
shipment_in = context.get('shipment_in')
|
||||
if not valuation_date:
|
||||
valuation_date = Date.today()
|
||||
wh = (val.date == valuation_date)
|
||||
if strategy:
|
||||
wh &= (val.strategy == strategy)
|
||||
if shipment_in:
|
||||
wh &= (val.shipment_in == shipment_in)
|
||||
|
||||
query = val.select(
|
||||
Literal(0).as_('create_uid'),
|
||||
@@ -1464,6 +1505,7 @@ class ValuationReport(ValuationBase, ModelView):
|
||||
val.mtm.as_('mtm'),
|
||||
val.strategy.as_('strategy'),
|
||||
val.lot.as_('lot'),
|
||||
val.shipment_in.as_('shipment_in'),
|
||||
where=wh)
|
||||
|
||||
return query
|
||||
@@ -1478,6 +1520,7 @@ class ValuationReportContext(ModelView):
|
||||
product = fields.Many2One('product.product',"Product")
|
||||
purchase = fields.Many2One('purchase.purchase', "Purchase")
|
||||
sale = fields.Many2One('sale.sale',"Sale")
|
||||
shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
|
||||
state = fields.Selection([
|
||||
('all', 'All'),
|
||||
('open', 'Open'),
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<field name="purchase"/>
|
||||
<label name="sale"/>
|
||||
<field name="sale"/>
|
||||
<label name="shipment_in"/>
|
||||
<field name="shipment_in"/>
|
||||
<label name="product"/>
|
||||
<field name="product"/>
|
||||
<label name="state"/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<field name="lot"/>
|
||||
<field name="purchase"/>
|
||||
<field name="sale"/>
|
||||
<field name="shipment_in" optional="1"/>
|
||||
<field name="type"
|
||||
widget="badge"
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="lot"/>
|
||||
<field name="shipment_in" optional="1"/>
|
||||
<field name="type"
|
||||
widget="badge"
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="r_lot"/>
|
||||
<field name="r_shipment_in" optional="1"/>
|
||||
<field name="r_type"/>
|
||||
<field name="r_reference"/>
|
||||
<field name="r_counterparty"/>
|
||||
|
||||
Reference in New Issue
Block a user