diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 89d2ef2..32b612f 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -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]) diff --git a/modules/purchase_trade/stock.py b/modules/purchase_trade/stock.py index 1fa8525..44c3d39 100755 --- a/modules/purchase_trade/stock.py +++ b/modules/purchase_trade/stock.py @@ -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] diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index a6c5d4d..7214b56 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -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): diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py index fc5a4e3..d08d7e3 100644 --- a/modules/purchase_trade/valuation.py +++ b/modules/purchase_trade/valuation.py @@ -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'), diff --git a/modules/purchase_trade/view/valuation_context_form.xml b/modules/purchase_trade/view/valuation_context_form.xml index c467180..9abc52e 100644 --- a/modules/purchase_trade/view/valuation_context_form.xml +++ b/modules/purchase_trade/view/valuation_context_form.xml @@ -9,6 +9,8 @@