# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. from functools import wraps from trytond.model import ModelSingleton, ModelSQL, ModelView, fields from trytond.report import Report from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder from trytond.model import (ModelSQL, ModelView) from trytond.tools import (cursor_dict, is_full_text, lstrip_wildcard) from trytond.transaction import Transaction, inactive_records from decimal import getcontext, Decimal, ROUND_HALF_UP from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr from sql.conditionals import Case from sql import Column, Literal from sql.functions import CurrentTimestamp, DateTrunc, Abs from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction from itertools import chain, groupby from operator import itemgetter import datetime import logging import json import jwt from collections import defaultdict from trytond.exceptions import UserWarning, UserError logger = logging.getLogger(__name__) TRIGGERS = [ ('bldate', 'BL date'), ('invdate', 'Invoice date'), ('ctdate', 'Ct. date'), ('prdate', 'Pur. date'), ('cod', 'COD date'), ('border', 'Border crossing date'), ('pump', 'Pump date'), ('discharge', 'Discharge NOR'), ('arrival', 'Arrival date'), ('delmonth', 'Delivery month'), ] class DocType(ModelSQL,ModelView): "Document Type" __name__ = 'document.type' name = fields.Char('Name') class ContractDocumentType(ModelSQL): "Contract - Document Type" __name__ = 'contract.document.type' doc_type = fields.Many2One('document.type', 'Document Type') purchase = fields.Many2One('purchase.purchase', "Purchase") class DocTemplate(ModelSQL,ModelView): "Documents Template" __name__ = 'doc.template' name = fields.Char('Name') type = fields.Many2Many('doc.type.template','template','type',"Document Type") class DocTypeTemplate(ModelSQL): "Template - Document Type" __name__ = 'doc.type.template' template = fields.Many2One('doc.template') type = fields.Many2One('document.type') class PurchaseStrategy(ModelSQL): "Purchase - Document Type" __name__ = 'purchase.strategy' line = fields.Many2One('purchase.line', 'Purchase Line') strategy = fields.Many2One('mtm.strategy', "Strategy") class Estimated(metaclass=PoolMeta): "Estimated date" __name__ = 'pricing.estimated' shipment_in = fields.Many2One('stock.shipment.in') shipment_out = fields.Many2One('stock.shipment.out') shipment_internal = fields.Many2One('stock.shipment.internal') purchase = fields.Many2One('purchase.purchase',"Purchase") line = fields.Many2One('purchase.line',"Line") class Currency(metaclass=PoolMeta): "Currency" __name__ = 'currency.currency' concatenate = fields.Boolean("Concatenate") @classmethod def default_concatenate(cls): return False class Unit(metaclass=PoolMeta): "Unit" __name__ = 'product.uom' concatenate = fields.Boolean("Concatenate") @classmethod def default_concatenate(cls): return False class FeeLots(metaclass=PoolMeta): "Fee lots" __name__ = 'fee.lots' line = fields.Many2One('purchase.line',"Line") class Component(metaclass=PoolMeta): "Component" __name__ = 'pricing.component' line = fields.Many2One('purchase.line',"Line") quota = fields.Function(fields.Numeric("Quota",digits='unit'),'get_quota_purchase') unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_purchase') def getDelMonthDatePurchase(self): PM = Pool().get('product.month') if self.line.del_period: pm = PM(self.line.del_period) if pm: logger.info("DELMONTHDATE:%s",pm.beg_date) return pm.beg_date def getEstimatedTriggerPurchase(self,t): logger.info("GETTRIGGER:%s",t) if t == 'delmonth': return self.getDelMonthDatePurchase() PE = Pool().get('pricing.estimated') Date = Pool().get('ir.date') pe = PE.search([('line','=',self.line),('trigger','=',t)]) if pe: return pe[0].estimated_date else: return Date.today() def get_unit_purchase(self, name): if self.line: return self.line.unit def get_quota_purchase(self, name): if self.line: if self.line.quantity: return round(self.line.quantity_theorical / (self.nbdays if self.nbdays > 0 else 1),5) class Pricing(metaclass=PoolMeta): "Pricing" __name__ = 'pricing.pricing' line = fields.Many2One('purchase.line',"Lines") unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_purchase') def get_unit_purchase(self,name): if self.line: return self.line.unit def get_eod_price_purchase(self): if self.line: return round((self.fixed_qt * self.fixed_qt_price + self.unfixed_qt * self.unfixed_qt_price)/Decimal(self.line.quantity_theorical),4) return Decimal(0) class Summary(ModelSQL,ModelView): "Pricing summary" __name__ = 'purchase.pricing.summary' line = fields.Many2One('purchase.line',"Lines") price_component = fields.Many2One('pricing.component',"Component") quantity = fields.Numeric("Qt",digits=(1,5)) fixed_qt = fields.Numeric("Fixed qt",digits=(1,5)) unfixed_qt = fields.Numeric("Unfixed qt",digits=(1,5)) price = fields.Numeric("Price",digits=(1,4)) progress = fields.Float("Fix. progress") ratio = fields.Numeric("Ratio") def get_name(self): if self.price_component: return self.price_component.get_rec_name() return "" def get_last_price(self): Date = Pool().get('ir.date') if self.price_component: pc = Pool().get('pricing.component')(self.price_component) if pc.price_index: PI = Pool().get('price.price') pi = PI(pc.price_index) logger.info("PURCHASECURRENCY:%s",self.line.purchase.currency) return pi.get_price(Date.today(),self.line.unit,self.line.purchase.currency,True) @classmethod def table_query(cls): PurchasePricing = Pool().get('pricing.pricing') pp = PurchasePricing.__table__() PurchaseComponent = Pool().get('pricing.component') pc = PurchaseComponent.__table__() #wh = Literal(True) context = Transaction().context group_pnl = context.get('group_pnl') if group_pnl: return None query = pp.join(pc,'LEFT',condition=pp.price_component == pc.id).select( Literal(0).as_('create_uid'), CurrentTimestamp().as_('create_date'), Literal(None).as_('write_uid'), Literal(None).as_('write_date'), Literal(None).as_('sale_line'), Max(pp.id).as_('id'), pp.line.as_('line'), pp.price_component.as_('price_component'), Max(pp.fixed_qt+pp.unfixed_qt).as_('quantity'), Max(pp.fixed_qt).as_('fixed_qt'), (Min(pp.unfixed_qt)).as_('unfixed_qt'), Max(Case((pp.last, pp.eod_price),else_=0)).as_('price'), (Max(pp.fixed_qt)/Max(pp.fixed_qt+pp.unfixed_qt)).as_('progress'), Max(pc.ratio).as_('ratio'), #where=wh, group_by=[pp.line,pp.price_component]) return query class StockLocation(metaclass=PoolMeta): __name__ = 'stock.location' lat = fields.Numeric("Latitude") lon = fields.Numeric("Longitude") class PurchaseCertification(ModelSQL,ModelView): "Certification" __name__ = 'purchase.certification' name = fields.Char("Name") class PurchaseCertificationWeightBasis(ModelSQL,ModelView): "Weight basis" __name__ = 'purchase.weight.basis' name = fields.Char("Name") qt_type = fields.Many2One('lot.qt.type',"Associated type to final invoice") class PurchaseAssociation(ModelSQL,ModelView): "Association" __name__ = 'purchase.association' name = fields.Char("Name") class PurchaseCrop(ModelSQL,ModelView): "Crop" __name__ = 'purchase.crop' name = fields.Char("Name") class Purchase(metaclass=PoolMeta): __name__ = 'purchase.purchase' btb = fields.Many2One('back.to.back',"Back to back") from_location = fields.Many2One('stock.location', 'From location',domain=[('type', "!=", 'customer')]) to_location = fields.Many2One('stock.location', 'To location',domain=[('type', "!=", 'supplier')]) shipment_in = fields.Many2One('stock.shipment.in','Purchases') broker = fields.Many2One('party.party',"Broker",domain=[('categories.parent', 'child_of', [4])]) tol_min = fields.Numeric("Tol - in %") tol_max = fields.Numeric("Tol + in %") tol_min_qt = fields.Numeric("Tol -") tol_max_qt = fields.Numeric("Tol +") certif = fields.Many2One('purchase.certification',"Certification") wb = fields.Many2One('purchase.weight.basis',"Weight basis") association = fields.Many2One('purchase.association',"Association") crop = fields.Many2One('purchase.crop',"Crop") pnl = fields.One2Many('valuation.valuation.dyn', 'r_purchase', 'Pnl',states={'invisible': ~Eval('group_pnl'),}) pnl_ = fields.One2Many('valuation.valuation.line', 'purchase', 'Pnl',states={'invisible': Eval('group_pnl'),}) derivatives = fields.One2Many('derivative.derivative', 'purchase', 'Derivative') plans = fields.One2Many('workflow.plan','purchase',"Execution plans") forex = fields.One2Many('forex.cover.physical.contract','contract',"Forex",readonly=True) plan = fields.Many2One('workflow.plan',"Name") estimated_date = fields.One2Many('pricing.estimated','purchase',"Estimated date") group_pnl = fields.Boolean("Group Pnl") viewer = fields.Function(fields.Text(""),'get_viewer') doc_template = fields.Many2One('doc.template',"Template") required_documents = fields.Many2Many( 'contract.document.type', 'purchase', 'doc_type', 'Required Documents') analytic_dimensions = fields.One2Many( 'analytic.dimension.assignment', 'purchase', 'Analytic Dimensions' ) trader = fields.Many2One('party.party',"Trader") operator = fields.Many2One('party.party',"Operator") our_reference = fields.Char("Our Reference") @classmethod def default_viewer(cls): country_start = "Zobiland" data = { "highlightedCountryName": country_start } return "d3:" + json.dumps(data) def get_viewer(self, name=None): country_start = '' dep_name = '' arr_name = '' departure = '' arrival = '' if self.party and self.party.addresses: if self.party.addresses[0].country: country_start = self.party.addresses[0].country.name if self.from_location: lat_from = self.from_location.lat lon_from = self.from_location.lon dep_name = self.from_location.name departure = { "name":dep_name,"lat": str(lat_from), "lon": str(lon_from) } if self.to_location: lat_to = self.to_location.lat lon_to = self.to_location.lon arr_name = self.to_location.name arrival = { "name":arr_name,"lat": str(lat_to), "lon": str(lon_to) } data = { "highlightedCountryNames": [{"name":country_start}], "routePoints": [ { "lon": -46.3, "lat": -23.9 }, { "lon": -30.0, "lat": -20.0 }, { "lon": -30.0, "lat": 0.0 }, { "lon": -6.0, "lat": 35.9 }, { "lon": 15.0, "lat": 38.0 }, { "lon": 29.0, "lat": 41.0 } ], "boats": [ # {"name": "CARIBBEAN 1", # "imo": "1234567", # "lon": -30.0, # "lat": 0.0, # "status": "En route", # "links": [ # { "text": "Voir sur VesselFinder", "url": "https://www.vesselfinder.com" }, # { "text": "Détails techniques", "url": "https://example.com/tech" } # ], # "actions": [ # { "type": "track", "id": "123", "label": "Suivre ce bateau" }, # { "type": "details", "id": "123", "label": "Voir détails" } # ]} ], "cottonStocks": [ # { "name":"Mali","lat": 12.65, "lon": -8.0, "amount": 300 }, # { "name":"Egypte","lat": 30.05, "lon": 31.25, "amount": 500 }, # { "name":"Irak","lat": 33.0, "lon": 44.0, "amount": 150 } ], "departures": [departure], "arrivals": [arrival] } return "d3:" + json.dumps(data) def getLots(self): if self.lines: if self.lines.lots: return [l for l in self.lines.lots] @fields.depends('party','from_location','to_location') def on_change_with_viewer(self): return self.get_viewer() @fields.depends('doc_template','required_documents') def on_change_with_required_documents(self): if self.doc_template: return self.doc_template.type @classmethod def copy(cls, purchases, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('pnl', None) default.setdefault('derivatives', None) default.setdefault('pnl_', None) default.setdefault('plans', None) return super().copy(purchases, default=default) @classmethod def validate(cls, purchases): super(Purchase, cls).validate(purchases) Line = Pool().get('purchase.line') Date = Pool().get('ir.date') for purchase in purchases: for line in purchase.lines: if not line.quantity_theorical and line.quantity > 0: line.quantity_theorical = ( Decimal(str(line.quantity)) .quantize(Decimal("0.00001")) ) Line.save([line]) #compute pnl Pnl = Pool().get('valuation.valuation') Pnl.generate(line) if line.quantity_theorical: OpenPosition = Pool().get('open.position') # OpenPosition.create_from_purchase_line(line) #line unit_price calculation if line.price_type == 'basis' and line.lots: #line.price_pricing and line.price_components and unit_price = line.get_basis_price() logger.info("VALIDATEPURCHASE:%s",unit_price) if unit_price != line.unit_price: line.unit_price = unit_price logger.info("VALIDATEPURCHASE2:%s",line.unit_price) Line.save([line]) if line.price_type == 'efp': if line.derivatives: for d in line.derivatives: line.unit_price = round(Decimal(d.price_index.get_price(Date.today(),line.unit,line.currency,True)),4) logger.info("EFP_PRICE:%s",line.unit_price) Line.save([line]) class Line(metaclass=PoolMeta): __name__ = 'purchase.line' quantity_theorical = fields.Numeric("Th. quantity", digits='unit', readonly=True) price_type = fields.Selection([ ('cash', 'Cash Price'), ('priced', 'Priced'), ('basis', 'Basis'), ('efp', 'EFP'), ], 'Price type') progress = fields.Function(fields.Float("Fix. progress", states={ 'invisible': Eval('price_type') != 'basis', }),'get_progress') del_period = fields.Many2One('product.month',"Delivery Period") from_del = fields.Date("From") to_del = fields.Date("To") period_at = fields.Selection([ (None, ''), ('laycan', 'Laycan'), ('loading', 'Loading'), ('discharge', 'Discharge'), ('crossing_border', 'Crossing Border'), ('title_transfer', 'Title transfer'), ('arrival', 'Arrival'), ],"Period at") concentration = fields.Numeric("Concentration") price_components = fields.One2Many('pricing.component','line',"Components") price_pricing = fields.One2Many('pricing.pricing','line',"Pricing") price_summary = fields.One2Many('purchase.pricing.summary','line',"Summary") estimated_date = fields.One2Many('pricing.estimated','line',"Estimated date") optional = fields.One2Many('optional.scenario','line',"Optionals Scenarios") lots = fields.One2Many('lot.lot','line',"Lots",readonly=True) purchase_line = fields.Many2One('purchase.line',"Lines") fees = fields.One2Many('fee.fee', 'line', 'Fees')#, filter=[('product.type', '=', 'service')]) derivatives = fields.One2Many('derivative.derivative','line',"Derivatives") mtm = fields.Many2Many('purchase.strategy', 'line', 'strategy', 'Mtm Strategy') tol_min = fields.Numeric("Tol - in %",states={ 'readonly': (Eval('inherit_tol')), }) tol_max = fields.Numeric("Tol + in %",states={ 'readonly': (Eval('inherit_tol')), }) tol_min_qt = fields.Numeric("Tol -",states={ 'readonly': (Eval('inherit_tol')), }) tol_max_qt = fields.Numeric("Tol +",states={ 'readonly': (Eval('inherit_tol')), }) inherit_tol = fields.Boolean("Inherit tolerance") tol_min_v = fields.Function(fields.Numeric("Qt min"),'get_tol_min') tol_max_v = fields.Function(fields.Numeric("Qt max"),'get_tol_max') # certification = fields.Selection([ # (None, ''), # ('bci', 'BCI'), # ],"Certification",states={'readonly': (Eval('inherit_cer')),}) certif = fields.Many2One('purchase.certification',"Certification",states={'readonly': (Eval('inherit_cer')),}) inherit_cer = fields.Boolean("Inherit certification") enable_linked_currency = fields.Boolean("Linked currencies") linked_price = fields.Numeric("Price", digits='unit',states={ 'invisible': (~Eval('enable_linked_currency')), }) linked_currency = fields.Many2One('currency.linked',"Currency",states={ 'invisible': (~Eval('enable_linked_currency')), }) linked_unit = fields.Many2One('product.uom', 'Unit',states={ 'invisible': (~Eval('enable_linked_currency')), }) premium = fields.Numeric("Premium/Discount",digits='unit') fee_ = fields.Many2One('fee.fee',"Fee") @classmethod def default_price_type(cls): return 'priced' @classmethod def default_inherit_tol(cls): return True @classmethod def default_enable_linked_currency(cls): return False @classmethod def default_inherit_cer(cls): return True def get_date(self,trigger_event): date = None if self.estimated_date: trigger_date = [d.estimated_date for d in self.estimated_date if d.trigger == trigger_event] trigger_date = trigger_date[0] if trigger_date else None return date def get_tol_min(self,name): if self.inherit_tol: if self.purchase.tol_min and self.quantity_theorical: return round((1-(self.purchase.tol_min/100))*Decimal(self.quantity_theorical),3) else: if self.tol_min and self.quantity_theorical: return round((1-(self.tol_min/100))*Decimal(self.quantity_theorical),3) def get_tol_max(self,name): if self.inherit_tol: if self.purchase.tol_max and self.quantity_theorical: return round((1+(self.purchase.tol_max/100))*Decimal(self.quantity_theorical),3) else: if self.tol_max and self.quantity_theorical: return round((1+(self.tol_max/100))*Decimal(self.quantity_theorical),3) def get_progress(self,name): PS = Pool().get('purchase.pricing.summary') ps = PS.search(['line','=',self.id]) if ps: return sum((e.progress if e.progress else 0) * (e.ratio if e.ratio else 0) / 100 for e in ps) def getVirtualLot(self): if self.lots: return [l for l in self.lots if l.lot_type=='virtual'][0] def get_basis_price(self): price = Decimal(0) for pc in self.price_components: PP = Pool().get('purchase.pricing.summary') pp = PP.search([('price_component','=',pc.id),('line','=',self.id)]) if pp: price += pp[0].price * (pc.ratio / 100) return round(price,4) def get_price(self,lot_premium=0): return (self.unit_price + Decimal(lot_premium)) if self.unit_price else Decimal(0) + (self.premium if self.premium else Decimal(0)) def get_price_linked_currency(self,lot_premium=0): if self.linked_unit: Uom = Pool().get('product.uom') qt = Uom.compute_qty(self.unit, float(1), self.linked_unit) return round(((self.linked_price + Decimal(lot_premium) + (self.premium if self.premium else Decimal(0))) * self.linked_currency.factor) * Decimal(qt), 4) else: return round((self.linked_price + Decimal(lot_premium) + (self.premium if self.premium else Decimal(0))) * self.linked_currency.factor, 4) @fields.depends('id','unit','quantity','unit_price','price_pricing','price_type','price_components','premium','estimated_date','lots','fees','enable_linked_currency','linked_price','linked_currency','linked_unit') def on_change_with_unit_price(self, name=None): Date = Pool().get('ir.date') logger.info("ONCHANGEUNITPRICE:%s",self.unit_price) if self.price_type == 'basis' and self.lots: #self.price_pricing and self.price_components and logger.info("ONCHANGEUNITPRICE_IN:%s",self.get_basis_price()) return self.get_basis_price() if self.enable_linked_currency and self.linked_price and self.linked_currency and self.price_type == 'priced': return self.get_price_linked_currency() if self.price_type == 'efp': if hasattr(self, 'derivatives') and self.derivatives: for d in self.derivatives: return round(d.price_index.get_price(Date.today(),self.unit,self.purchase.currency,True),4) return self.unit_price @classmethod def copy(cls, lines, default=None): if default is None: default = {} else: default = default.copy() default.setdefault('lots', None) default.setdefault('quantity', Decimal(0)) default.setdefault('quantity_theorical', None) default.setdefault('price_pricing', None) return super().copy(lines, default=default) @classmethod def delete(cls, lines): pool = Pool() LotQt = pool.get('lot.qt') Valuation = pool.get('valuation.valuation') OpenPosition = pool.get('open.position') for line in lines: if line.lots: vlot_p = line.lots[0].getVlot_p() lqts = LotQt.search([('lot_p','=',vlot_p.id),('lot_s','!=',None),('lot_quantity','>',0)]) if lqts: raise UserError("You cannot delete matched sale") return lqts = LotQt.search([('lot_p','=',vlot_p.id)]) LotQt.delete(lqts) valuations = Valuation.search([('lot','in',line.lots)]) if valuations: Valuation.delete(valuations) # op = OpenPosition.search(['line','=',line.id]) # if op: # OpenPosition.delete(op) super(Line, cls).delete(lines) @classmethod def validate(cls, lines): super(Line, cls).validate(lines) for line in lines: if line.price_components: for pc in line.price_components: if pc.triggers: for tr in pc.triggers: line.check_from_to(tr) line.check_pricing() #no lot need to create one with line quantity if not line.created_by_code: if not line.lots and line.product.type != 'service' and line.quantity != Decimal(0): FeeLots = Pool().get('fee.lots') LotQtHist = Pool().get('lot.qt.hist') LotQtType = Pool().get('lot.qt.type') Lot = Pool().get('lot.lot') lot = Lot() lot.line = line.id lot.lot_qt = None lot.lot_unit = None lot.lot_unit_line = line.unit lot.lot_quantity = round(Decimal(line.quantity),5) lot.lot_gross_quantity = None lot.lot_status = 'forecast' lot.lot_type = 'virtual' lot.lot_product = line.product lqtt = LotQtType.search([('sequence','=',1)]) if lqtt: lqh = LotQtHist() lqh.quantity_type = lqtt[0] lqh.quantity = lot.lot_quantity logger.info("PURCHASE_VALIDATE:%s",lot.lot_quantity) lqh.gross_quantity = lot.lot_quantity lot.lot_hist = [lqh] if line.quantity > 0: Lot.save([lot]) #check if fees need to be updated if line.fees: for fee in line.fees: fl_check = FeeLots.search([('fee','=',fee.id),('lot','=',lot.id),('line','=',line.id)]) if not fl_check: fl = FeeLots() fl.fee = fee.id fl.lot = lot.id fl.line = line.id FeeLots.save([fl]) #update inherit fee qt # if line.fees: # Fee = Pool().get('fee.fee') # for f in line.fees: # if f.inherit_qt: # f.quantity = round(line.quantity_theorical,4) # f.unit = line.unit # Fee.save([f]) #check if fee purchase is filled on fee if line.fee_: if not line.fee_.purchase: Fee = Pool().get('fee.fee') f = Fee(line.fee_) f.purchase = line.purchase Fee.save([f]) def check_from_to(self,tr): if tr.pricing_period: date_from,date_to, d, include, dates = tr.getDateWithEstTrigger(1) if date_from: tr.from_p = date_from.date() if date_to: tr.to_p = date_to.date() if tr.application_period: date_from,date_to, d, include, dates = tr.getDateWithEstTrigger(2) if date_from: tr.from_a = date_from.date() if date_to: tr.to_a = date_to.date() TR = Pool().get('pricing.trigger') TR.save([tr]) def check_pricing(self): if self.price_components: for pc in self.price_components: if not pc.auto: Pricing = Pool().get('pricing.pricing') pricings = Pricing.search(['price_component','=',pc.id],order=[('pricing_date', 'ASC')]) if pricings: cumul_qt = Decimal(0) index = 0 for pr in pricings: cumul_qt += pr.quantity pr.fixed_qt = cumul_qt pr.fixed_qt_price = pr.get_fixed_price() pr.unfixed_qt = Decimal(pr.line.quantity_theorical) - pr.fixed_qt pr.unfixed_qt_price = pr.fixed_qt_price pr.eod_price = pr.get_eod_price_purchase() if index == len(pricings) - 1: pr.last = True index += 1 Pricing.save([pr]) if pc.triggers and pc.auto: prDate = [] prPrice = [] apDate = [] apPrice = [] for t in pc.triggers: logger.info("CHECK_PRICING:%s",t) prD, prP = t.getPricingListDates(pc.calendar) logger.info("CHECK_PRICING2:%s",prP) apD, apP = t.getApplicationListDates(pc.calendar) prDate.extend(prD) prPrice.extend(prP) apDate.extend(apD) apPrice.extend(apP) if pc.quota: prPrice = self.get_avg(prPrice) self.generate_pricing(pc,apDate,prPrice) def get_avg(self,lprice): l = len(lprice) if l > 0 : cumulprice = float(0) i = 1 for p in lprice: if i > 1: p['avg_minus_1'] = cumulprice / (i-1) cumulprice += p['price'] p['avg'] = cumulprice / i i += 1 return lprice def getnearprice(self,pl,d,t,max_date=None): if pl: pl_sorted = sorted(pl, key=lambda x: x['date']) pminus = pl_sorted[0] if not max_date: max_date = d.date() for p in pl_sorted: if p['date'].date() == d.date(): if p['isAvg'] and t == 'avg': return p[t] if not p['isAvg'] and t == 'avg': return p['price'] elif p['date'].date() > d.date(): if pminus != p: return pminus[t] else: return Decimal(0) pminus = p return pl_sorted[len(pl)-1][t] return Decimal(0) def generate_pricing(self,pc,dl,pl): Pricing = Pool().get('pricing.pricing') pricing = Pricing.search(['price_component','=',pc.id]) if pricing: Pricing.delete(pricing) cumul_qt = 0 index = 0 dl_sorted = sorted(dl) for d in dl_sorted: if pc.pricing_date and d.date() > pc.pricing_date: break p = Pricing() p.line = self.id logger.info("GENEDATE:%s",d) logger.info("TYPEDATE:%s",type(d)) p.pricing_date = d.date() p.price_component = pc.id p.quantity = round(Decimal(pc.quota),5) price = round(Decimal(self.getnearprice(pl,d,'price')),4) p.settl_price = price if price > 0: cumul_qt += pc.quota p.fixed_qt = round(Decimal(cumul_qt),5) p.fixed_qt_price = round(Decimal(self.getnearprice(pl,d,'avg')),4) #p.fixed_qt_price = p.get_fixed_price() if p.fixed_qt_price == 0: p.fixed_qt_price = round(Decimal(self.getnearprice(pl,d,'avg_minus_1')),4) p.unfixed_qt = round(Decimal(self.quantity_theorical) - Decimal(cumul_qt),5) if p.unfixed_qt < 0.001: p.unfixed_qt = Decimal(0) p.fixed_qt = Decimal(self.quantity_theorical) if price > 0: logger.info("GENERATE_1:%s",price) p.unfixed_qt_price = price else: pr = Decimal(pc.price_index.get_price(p.pricing_date,self.unit,self.purchase.currency,True)) pr = round(pr,4) logger.info("GENERATE_2:%s",pr) p.unfixed_qt_price = pr p.eod_price = p.get_eod_price_purchase() if (index == len(dl)-1) or (pc.pricing_date and (index < len(dl)-1 and dl_sorted[index+1].date() > pc.pricing_date)): p.last = True logger.info("GENERATE_3:%s",p.unfixed_qt_price) Pricing.save([p]) index += 1 # @classmethod # def view_attributes(cls): # return super().view_attributes() + [ # ('/tree/field[@name="quantity"]', 'visual', # If(Eval('quantity') & (Eval('quantity', 0) > 0),'success','danger')), # ] class GoToBi(Wizard): __name__ = 'purchase.bi' start_state = 'bi' bi = StateAction('purchase_trade.url_bi') def do_bi(self, action): Configuration = Pool().get('gr.configuration') config = Configuration.search(['id','>',0])[0] ct_number = self.records[0].number action['url'] = config.bi + '/dashboard/6-pnl?lot=&product=&purchase='+ ct_number + '&sale=' return action, {} class PurchaseAllocationsWizard(Wizard): 'Open Allocations report from Purchase without modal' __name__ = 'purchase.allocations.wizard' start_state = 'open_report' open_report = StateAction('purchase_trade.act_lot_report_form') def do_open_report(self, action): purchase_id = Transaction().context.get('active_id') if not purchase_id: raise ValueError("No active purchase ID in context") action['context_model'] = 'lot.context' action['pyson_context'] = PYSONEncoder().encode({ 'purchase': purchase_id, }) return action, {} class PurchaseInvoiceReport( ModelSQL, ModelView): "Purchase invoices" __name__ = 'purchase.invoice.report' r_supplier = fields.Many2One('party.party',"Supplier") r_purchase = fields.Many2One('purchase.purchase', "Purchase") r_line = fields.Many2One('purchase.line',"Line") r_lot = fields.Many2One('lot.lot',"Lot") r_product = fields.Many2One('product.product', "Product") r_pur_invoice = fields.Many2One('account.invoice',"Invoice") r_inv_date = fields.Date("Inv. date") r_pur_payment = fields.Many2Many('account.invoice-account.move.line','invoice', 'line', string='Payment') r_invoice_amount = fields.Numeric("Amount",digits=(1,2)) r_payment_amount = fields.Numeric("Paid",digits=(1,2)) r_left_amount = fields.Numeric("Left",digits=(1,2)) r_curr = fields.Many2One('currency.currency',"Curr") r_reconciliation = fields.Integer("Reconciliation") r_move = fields.Many2One('account.move',"Move") r_status = fields.Selection([ ('not', 'Not'), ('paid', 'Paid'), ('partial', 'Partially'), ], 'Status') @classmethod def table_query(cls): pool = Pool() Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') PurchaseLine = pool.get('purchase.line') Purchase = pool.get('purchase.purchase') Party = pool.get('party.party') InvoicePayment = pool.get('account.invoice-account.move.line') MoveLine = pool.get('account.move.line') Move = pool.get('account.move') Currency = pool.get('currency.currency') Lot = pool.get('lot.lot') ai = Invoice.__table__() ail = InvoiceLine.__table__() pl = PurchaseLine.__table__() pu = Purchase.__table__() pa = Party.__table__() aiaml = InvoicePayment.__table__() aml = MoveLine.__table__() lot = Lot.__table__() cu = Currency.__table__() mo = Move.__table__() context = Transaction().context supplier = context.get('supplier') purchase = context.get('purchase') asof = context.get('asof') todate = context.get('todate') state = context.get('state') wh = Literal(True) wh &= lot.lot_type == 'physic' wh &= ai.type == 'in' if supplier: wh &= (ai.party == supplier) if purchase: wh &= (pu.id == purchase) if asof and todate: wh &= (ai.invoice_date >= asof) & (ai.invoice_date <= todate) query = ( lot .join(ail, 'LEFT', condition=ail.lot == lot.id) .join(ai, 'LEFT', condition=ai.id == ail.invoice) .join(pl, 'LEFT', condition=pl.id == lot.line) .join(pu, 'LEFT', condition=pl.purchase == pu.id) .join(pa, 'LEFT', condition=ai.party == pa.id) .join(cu, 'LEFT', condition=cu.id == ail.currency) .select( Literal(0).as_('create_uid'), CurrentTimestamp().as_('create_date'), Literal(0).as_('write_uid'), Literal(0).as_('write_date'), Max(ail.id).as_('id'), Max(pa.id).as_('r_supplier'), pu.id.as_('r_purchase'), pl.id.as_('r_line'), Max(ail.product).as_('r_product'), Max(lot.id).as_('r_lot'), ai.id.as_('r_pur_invoice'), Max(ai.invoice_date).as_('r_inv_date'), Sum(ail.quantity*ail.unit_price).as_('r_invoice_amount'), Max(cu.id).as_('r_curr'), where=wh, group_by=[pu.id,pl.id,ai.id] ) ) query_alias = query left = Case((Abs(Sum(aml.amount_second_currency))>0,(Max(query_alias.r_invoice_amount)-Sum(aml.amount_second_currency))),else_=(Max(query_alias.r_invoice_amount)-(Sum(aml.debit)-Sum(aml.credit)))) status = Case((left==0, 'paid'),else_=Case((left',0])[0] payload = { "resource": {"dashboard": config.pnl_id}, "params": {}, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30), } token = jwt.encode(payload, config.payload, algorithm="HS256") logger.info("TOKEN:%s",token) if config.dark: url = f"metabase:{config.bi}/embed/dashboard/{token}#theme=night&bordered=true&titled=true" else: url = f"metabase:{config.bi}/embed/dashboard/{token}#bordered=true&titled=true" return url