diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py index 8173b7e..a7c8c01 100644 --- a/modules/purchase_trade/valuation.py +++ b/modules/purchase_trade/valuation.py @@ -54,22 +54,23 @@ class ValuationBase(ModelSQL): @classmethod def _base_pnl(cls, *, line, lot, pnl_type, sale=None): Date = Pool().get('ir.date') - pnl = Pool().get('valuation.valuation')() - pnl.purchase = line.purchase.id - pnl.line = line.id - pnl.type = pnl_type - pnl.date = Date.today() - pnl.lot = lot.id + values = { + 'purchase': line.purchase.id, + 'line': line.id, + 'type': pnl_type, + 'date': Date.today(), + 'lot': lot.id, + } if sale: - pnl.sale = sale.id - - return pnl + values['sale'] = sale.id + return values + @classmethod def _build_basis_pnl(cls, *, line, lot, sale_line, pc, sign): - pnl = cls._base_pnl( + values = cls._base_pnl( line=line, lot=lot, sale=sale_line.sale if sale_line else None, @@ -78,38 +79,41 @@ class ValuationBase(ModelSQL): qty = lot.get_current_quantity_converted() - pnl.reference = f"{pc.get_name()} / {pc.ratio}%" - pnl.price = round(pc.price, 4) - pnl.counterparty = sale_line.sale.party if sale_line else line.purchase.party - pnl.product = sale_line.product if sale_line else line.product + values.update({ + 'reference': f"{pc.get_name()} / {pc.ratio}%", + 'price': round(pc.price, 4), + 'counterparty': sale_line.sale.party.id if sale_line else line.purchase.party.id, + 'product': sale_line.product.id if sale_line else line.product.id, + }) # State if pc.unfixed_qt == 0: - pnl.state = 'fixed' + values['state'] = 'fixed' elif pc.fixed_qt == 0: - pnl.state = 'unfixed' + values['state'] = 'unfixed' else: base = sale_line.quantity_theorical if sale_line else line.quantity_theorical - pnl.state = f"part. fixed {round(pc.fixed_qt / Decimal(base) * 100, 0)}%" + values['state'] = f"part. fixed {round(pc.fixed_qt / Decimal(base) * 100, 0)}%" if pc.price and pc.ratio: - pnl.quantity = round(qty, 5) - pnl.amount = round(pc.price * qty * Decimal(sign) * pc.ratio / 100, 4) + amount = round(pc.price * qty * Decimal(sign) * pc.ratio / 100, 4) - mtm = Decimal(0) last_price = pc.get_last_price() - if last_price: - mtm = round(Decimal(last_price) * qty * Decimal(sign), 4) + mtm = round(Decimal(last_price) * qty * Decimal(sign), 4) if last_price else Decimal(0) - pnl.mtm = round(pnl.amount - (mtm * pc.ratio / 100), 4) - pnl.unit = sale_line.unit if sale_line else line.unit - pnl.currency = sale_line.sale.currency if sale_line else line.purchase.currency + values.update({ + 'quantity': round(qty, 5), + 'amount': amount, + 'mtm': round(amount - (mtm * pc.ratio / 100), 4), + 'unit': sale_line.unit.id if sale_line else line.unit.id, + 'currency': sale_line.sale.currency.id if sale_line else line.purchase.currency.id, + }) - return pnl + return values @classmethod def _build_simple_pnl(cls, *, line, lot, sale_line, price, state, sign, pnl_type): - pnl = cls._base_pnl( + values = cls._base_pnl( line=line, lot=lot, sale=sale_line.sale if sale_line else None, @@ -117,19 +121,25 @@ class ValuationBase(ModelSQL): ) qty = lot.get_current_quantity_converted() - pnl.price = round(price, 4) - pnl.quantity = round(qty, 5) - pnl.amount = round(pnl.price * qty * Decimal(sign), 4) - pnl.mtm = Decimal(0) - pnl.state = state - pnl.unit = sale_line.unit if sale_line else line.unit - pnl.currency = sale_line.sale.currency if sale_line else line.purchase.currency - pnl.counterparty = sale_line.sale.party if sale_line else line.purchase.party - pnl.product = sale_line.product if sale_line else line.product - pnl.reference = 'Sale/Physic' if lot.lot_type == 'physic' else 'Sale/Open' if sale_line else 'Purchase/Physic' + values.update({ + 'price': round(price, 4), + 'quantity': round(qty, 5), + 'amount': round(price * qty * Decimal(sign), 4), + 'mtm': Decimal(0), + 'state': state, + 'unit': sale_line.unit.id if sale_line else line.unit.id, + 'currency': sale_line.sale.currency.id if sale_line else line.purchase.currency.id, + 'counterparty': sale_line.sale.party.id if sale_line else line.purchase.party.id, + 'product': sale_line.product.id if sale_line else line.product.id, + 'reference': ( + 'Sale/Physic' if lot.lot_type == 'physic' + else 'Sale/Open' if sale_line + else 'Purchase/Physic' + ), + }) - return pnl + return values @classmethod def create_pnl_price_from_line(cls, line): @@ -138,30 +148,31 @@ class ValuationBase(ModelSQL): for lot in line.lots: - # --- PURCHASE SIDE --- if line.price_type == 'basis': for pc in line.price_summary or []: - pnl = cls._build_basis_pnl(line=line, lot=lot, sale_line=None, pc=pc, sign=-1) - if pnl: - price_lines.append(pnl) + values = cls._build_basis_pnl(line=line, lot=lot, sale_line=None, pc=pc, sign=-1) + if values: + price_lines.append(values) elif line.price_type in ('priced', 'efp') and lot.lot_price: - state = 'fixed' if line.price_type == 'priced' else 'not fixed' price_lines.append( cls._build_simple_pnl( line=line, lot=lot, sale_line=None, price=lot.lot_price, - state=state, + state='fixed' if line.price_type == 'priced' else 'not fixed', sign=-1, pnl_type=f'pur. {line.price_type}' ) ) - # --- SALE SIDE --- sale_lots = [lot] if lot.sale_line else [ - lqt.lot_s for lqt in LotQt.search([('lot_p','=',lot.id),('lot_s','>',0),('lot_quantity','>',0)]) + lqt.lot_s for lqt in LotQt.search([ + ('lot_p', '=', lot.id), + ('lot_s', '>', 0), + ('lot_quantity', '>', 0), + ]) ] for sl in sale_lots: @@ -171,20 +182,18 @@ class ValuationBase(ModelSQL): if sl_line.price_type == 'basis': for pc in sl_line.price_summary or []: - pnl = cls._build_basis_pnl(line=line, lot=sl, sale_line=sl_line, pc=pc, sign=+1) - if pnl: - price_lines.append(pnl) + values = cls._build_basis_pnl(line=line, lot=sl, sale_line=sl_line, pc=pc, sign=+1) + if values: + price_lines.append(values) elif sl_line.price_type in ('priced', 'efp'): - state = 'fixed' if sl_line.price_type == 'priced' else 'not fixed' - price = sl.lot_price_sale price_lines.append( cls._build_simple_pnl( line=line, lot=sl, sale_line=sl_line, - price=price, - state=state, + price=sl.lot_price_sale, + state='fixed' if sl_line.price_type == 'priced' else 'not fixed', sign=+1, pnl_type=f'sale {sl_line.price_type}' ) @@ -192,6 +201,7 @@ class ValuationBase(ModelSQL): return price_lines + @classmethod def group_fees_by_type_supplier(cls,line,fees): grouped = defaultdict(list) @@ -211,101 +221,107 @@ class ValuationBase(ModelSQL): return result @classmethod - def create_pnl_fee_from_line(cls,line): + def create_pnl_fee_from_line(cls, line): fee_lines = [] - #pnl management - Pnl = Pool().get('valuation.valuation') Date = Pool().get('ir.date') Currency = Pool().get('currency.currency') FeeLots = Pool().get('fee.lots') - if line.lots: - for lot in line.lots: - fl = FeeLots.search(['lot','=',lot.id]) - if fl: - fees = [e.fee for e in fl] - sorted_fees = cls.group_fees_by_type_supplier(line,fees) - if sorted_fees: - for sf in sorted_fees: - pnl = Pnl() - pnl.lot = lot.id - if lot.sale_line: - pnl.sale = lot.sale_line.sale.id - pnl.purchase = line.purchase.id - pnl.line = line.id - if sf.line: - pnl.type = 'pur. fee' - if sf.sale_line: - pnl.type = 'sale fee' - if sf.shipment_in: - pnl.type = 'shipment fee' - pnl.date = Date.today() - pnl.price = Decimal(sf.get_price_per_qt()) - if sf.currency != line.purchase.currency: - with Transaction().set_context(date=Date.today()): - pnl.price = Currency.compute(sf.currency,pnl.price, line.purchase.currency) - pnl.counterparty = sf.supplier - str_op = '' - if lot.lot_type == 'physic': - str_op = '/Physic' - else: - str_op = '/Open' - pnl.reference = sf.product.name + str_op - pnl.product = sf.product - pnl.state = sf.type - if sf.p_r == 'pay': - sign = -1 - pnl.amount = round(pnl.price * lot.get_current_quantity_converted() * sign,2) - pnl.mtm = 0 - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.unit = sf.unit if sf.unit else line.unit - pnl.currency = sf.currency - fee_lines.append(pnl) + + for lot in line.lots or []: + fl = FeeLots.search([('lot', '=', lot.id)]) + if not fl: + continue + + fees = [e.fee for e in fl] + for sf in cls.group_fees_by_type_supplier(line, fees): + + price = Decimal(sf.get_price_per_qt()) + if sf.currency != line.purchase.currency: + with Transaction().set_context(date=Date.today()): + price = Currency.compute(sf.currency, price, line.purchase.currency) + + sign = -1 if sf.p_r == 'pay' else 1 + + fee_lines.append({ + 'lot': lot.id, + 'sale': lot.sale_line.sale.id if lot.sale_line else None, + 'purchase': line.purchase.id, + 'line': line.id, + 'type': ( + 'shipment fee' if sf.shipment_in + else 'sale fee' if sf.sale_line + else 'pur. fee' + ), + 'date': Date.today(), + 'price': price, + 'counterparty': sf.supplier.id, + 'reference': f"{sf.product.name}/{'Physic' if lot.lot_type == 'physic' else 'Open'}", + 'product': sf.product.id, + 'state': sf.type, + 'quantity': round(lot.get_current_quantity_converted(), 5), + 'amount': round(price * lot.get_current_quantity_converted() * sign, 2), + 'mtm': Decimal(0), + 'unit': sf.unit.id if sf.unit else line.unit.id, + 'currency': sf.currency.id, + }) return fee_lines - + @classmethod - def create_pnl_der_from_line(cls,line): + def create_pnl_der_from_line(cls, line): + Date = Pool().get('ir.date') der_lines = [] - if line.derivatives: - Pnl = Pool().get('valuation.valuation') - Date = Pool().get('ir.date') - for d in line.derivatives: - pnl = Pnl() - pnl.purchase = line.purchase.id - pnl.line = line.id - pnl.type = 'derivative' - pnl.date = Date.today() - pnl.reference = d.price_index.price_index - pnl.price = round(Decimal(d.price_index.get_price_per_qt(d.price,line.unit,line.purchase.currency)),4) - pnl.counterparty = d.party - pnl.product = d.product - pnl.state = 'fixed' - pnl.amount = round(pnl.price * (d.quantity) * Decimal(-1),4) - mtm = round(Decimal(d.price_index.get_price(Date.today(),line.unit,line.purchase.currency,True)) * d.quantity * Decimal(-1),4) - pnl.mtm = pnl.amount - mtm - pnl.quantity = round(d.quantity,5) - pnl.unit = line.unit - pnl.currency = line.purchase.currency - der_lines.append(pnl) + + for d in line.derivatives or []: + price = Decimal(d.price_index.get_price_per_qt( + d.price, line.unit, line.purchase.currency + )) + + mtm = Decimal(d.price_index.get_price( + Date.today(), line.unit, line.purchase.currency, True + )) + + der_lines.append({ + 'purchase': line.purchase.id, + 'line': line.id, + 'type': 'derivative', + 'date': Date.today(), + 'reference': d.price_index.price_index, + 'price': round(price, 4), + 'counterparty': d.party.id, + 'product': d.product.id, + 'state': 'fixed', + 'quantity': round(d.quantity, 5), + 'amount': round(price * d.quantity * Decimal(-1), 4), + 'mtm': round((price * d.quantity * Decimal(-1)) - (mtm * d.quantity * Decimal(-1)), 4), + 'unit': line.unit.id, + 'currency': line.purchase.currency.id, + }) + return der_lines @classmethod def generate(cls, line): Date = Pool().get('ir.date') - Pnl = Pool().get('valuation.valuation') - pnl = Pnl.search([('line','=',line.id),('date','=',Date.today())]) - if pnl: - Pnl.delete(pnl) - PnlLine = Pool().get('valuation.valuation.line') - pnlline = PnlLine.search(['line','=',line.id]) - if pnlline: - PnlLine.delete(pnlline) - pnl_lines = [] - pnl_lines.extend(cls.create_pnl_fee_from_line(line)) - pnl_lines.extend(cls.create_pnl_price_from_line(line)) - pnl_lines.extend(cls.create_pnl_der_from_line(line)) - Pnl.save(pnl_lines) - PnlLine.save(pnl_lines) + Valuation = Pool().get('valuation.valuation') + ValuationLine = Pool().get('valuation.valuation.line') + + Valuation.delete(Valuation.search([ + ('line', '=', line.id), + ('date', '=', Date.today()), + ])) + + ValuationLine.delete(ValuationLine.search([ + ('line', '=', line.id), + ])) + + values = [] + values.extend(cls.create_pnl_fee_from_line(line)) + values.extend(cls.create_pnl_price_from_line(line)) + values.extend(cls.create_pnl_der_from_line(line)) + + Valuation.create(values) + ValuationLine.create(values) class Valuation(ValuationBase, ModelView): "Valuation"