diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index e42ba3a..251ebb6 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -1,108 +1,108 @@ -# 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 trytond.model import fields -from trytond.pool import Pool, PoolMeta -from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder -from trytond.model import (ModelSQL, ModelView) -from trytond.i18n import gettext -from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction -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 -import datetime -import logging -import json -from trytond.exceptions import UserWarning, UserError -from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words, format_date_en - -logger = logging.getLogger(__name__) - -VALTYPE = [ - ('priced', 'Price'), - ('fee', 'Fee'), - ('market', 'Market'), - ('derivative', 'Derivative'), -] - -class ContractDocumentType(metaclass=PoolMeta): - "Contract - Document Type" - __name__ = 'contract.document.type' - # lc_out = fields.Many2One('lc.letter.outgoing', 'LC out') - # lc_in = fields.Many2One('lc.letter.incoming', 'LC in') - sale = fields.Many2One('sale.sale', "Sale") - -class AnalyticDimensionAssignment(metaclass=PoolMeta): - 'Analytic Dimension Assignment' - __name__ = 'analytic.dimension.assignment' - sale = fields.Many2One('sale.sale', "Sale") - -class Estimated(metaclass=PoolMeta): - "Estimated date" - __name__ = 'pricing.estimated' - - sale_line = fields.Many2One('sale.line',"Line") - -class FeeLots(metaclass=PoolMeta): - - "Fee lots" - __name__ = 'fee.lots' - - sale_line = fields.Many2One('sale.line',"Line") - -class Backtoback(metaclass=PoolMeta): - 'Back To Back' - __name__ = 'back.to.back' - - sale = fields.One2Many('sale.sale','btb', "Sale") - -class OpenPosition(metaclass=PoolMeta): - "Open position" - __name__ = 'open.position' - sale = fields.Many2One('sale.sale',"Sale") - sale_line = fields.Many2One('sale.line',"Sale Line") - client = fields.Many2One('party.party',"Client") - -class SaleStrategy(ModelSQL): - "Sale - Document Type" - __name__ = 'sale.strategy' - sale_line = fields.Many2One('sale.line', 'Sale Line') - strategy = fields.Many2One('mtm.strategy', "Strategy") - -class Component(metaclass=PoolMeta): - "Component" - __name__ = 'pricing.component' - - sale_line = fields.Many2One('sale.line',"Line") - quota_sale = fields.Function(fields.Numeric("Quota",digits='unit'),'get_quota_sale') - unit_sale = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_sale') - - def getDelMonthDateSale(self): - PM = Pool().get('product.month') - if self.sale_line and hasattr(self.sale_line, 'del_period') and self.sale_line.del_period: - pm = PM(self.sale_line.del_period) - if pm: - logger.info("DELMONTHDATE:%s",pm.beg_date) - return pm.beg_date - - def getEstimatedTriggerSale(self,t): - logger.info("GETTRIGGER:%s",t) - if t == 'delmonth': - return self.getDelMonthDateSale() - PE = Pool().get('pricing.estimated') - Date = Pool().get('ir.date') - pe = PE.search([('sale_line','=',self.sale_line),('trigger','=',t)]) - if pe: - return pe[0].estimated_date - else: - return Date.today() - - def get_unit_sale(self, name): - if self.sale_line: - return self.sale_line.unit - +# 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 trytond.model import fields +from trytond.pool import Pool, PoolMeta +from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder +from trytond.model import (ModelSQL, ModelView) +from trytond.i18n import gettext +from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction +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 +import datetime +import logging +import json +from trytond.exceptions import UserWarning, UserError +from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words, format_date_en + +logger = logging.getLogger(__name__) + +VALTYPE = [ + ('priced', 'Price'), + ('fee', 'Fee'), + ('market', 'Market'), + ('derivative', 'Derivative'), +] + +class ContractDocumentType(metaclass=PoolMeta): + "Contract - Document Type" + __name__ = 'contract.document.type' + # lc_out = fields.Many2One('lc.letter.outgoing', 'LC out') + # lc_in = fields.Many2One('lc.letter.incoming', 'LC in') + sale = fields.Many2One('sale.sale', "Sale") + +class AnalyticDimensionAssignment(metaclass=PoolMeta): + 'Analytic Dimension Assignment' + __name__ = 'analytic.dimension.assignment' + sale = fields.Many2One('sale.sale', "Sale") + +class Estimated(metaclass=PoolMeta): + "Estimated date" + __name__ = 'pricing.estimated' + + sale_line = fields.Many2One('sale.line',"Line") + +class FeeLots(metaclass=PoolMeta): + + "Fee lots" + __name__ = 'fee.lots' + + sale_line = fields.Many2One('sale.line',"Line") + +class Backtoback(metaclass=PoolMeta): + 'Back To Back' + __name__ = 'back.to.back' + + sale = fields.One2Many('sale.sale','btb', "Sale") + +class OpenPosition(metaclass=PoolMeta): + "Open position" + __name__ = 'open.position' + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Sale Line") + client = fields.Many2One('party.party',"Client") + +class SaleStrategy(ModelSQL): + "Sale - Document Type" + __name__ = 'sale.strategy' + sale_line = fields.Many2One('sale.line', 'Sale Line') + strategy = fields.Many2One('mtm.strategy', "Strategy") + +class Component(metaclass=PoolMeta): + "Component" + __name__ = 'pricing.component' + + sale_line = fields.Many2One('sale.line',"Line") + quota_sale = fields.Function(fields.Numeric("Quota",digits='unit'),'get_quota_sale') + unit_sale = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_sale') + + def getDelMonthDateSale(self): + PM = Pool().get('product.month') + if self.sale_line and hasattr(self.sale_line, 'del_period') and self.sale_line.del_period: + pm = PM(self.sale_line.del_period) + if pm: + logger.info("DELMONTHDATE:%s",pm.beg_date) + return pm.beg_date + + def getEstimatedTriggerSale(self,t): + logger.info("GETTRIGGER:%s",t) + if t == 'delmonth': + return self.getDelMonthDateSale() + PE = Pool().get('pricing.estimated') + Date = Pool().get('ir.date') + pe = PE.search([('sale_line','=',self.sale_line),('trigger','=',t)]) + if pe: + return pe[0].estimated_date + else: + return Date.today() + + def get_unit_sale(self, name): + if self.sale_line: + return self.sale_line.unit + def get_quota_sale(self, name): if self.sale_line: quantity = getattr(self.sale_line, 'quantity_theorical', None) @@ -111,14 +111,14 @@ class Component(metaclass=PoolMeta): if quantity is not None: nbdays = self.nbdays if self.nbdays and self.nbdays > 0 else 1 return round(Decimal(quantity) / nbdays, 4) - -class Pricing(metaclass=PoolMeta): - "Pricing" - __name__ = 'pricing.pricing' - - sale_line = fields.Many2One('sale.line',"Lines") - unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_sale') - + +class Pricing(metaclass=PoolMeta): + "Pricing" + __name__ = 'pricing.pricing' + + sale_line = fields.Many2One('sale.line',"Lines") + unit = fields.Function(fields.Many2One('product.uom',"Unit"),'get_unit_sale') + def get_unit_sale(self,name): if self.sale_line: return self.sale_line.unit @@ -130,162 +130,162 @@ class Pricing(metaclass=PoolMeta): self.unfixed_qt, self.unfixed_qt_price, ) - -class Summary(ModelSQL,ModelView): - "Pricing summary" - __name__ = 'sale.pricing.summary' - - sale_line = fields.Many2One('sale.line',"Lines") - price_component = fields.Many2One('pricing.component',"Component") - quantity = fields.Numeric("Qt",digits=(1,4)) - fixed_qt = fields.Numeric("Fixed qt",digits=(1,4)) - unfixed_qt = fields.Numeric("Unfixed qt",digits=(1,4)) - 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) - return pi.get_price(Date.today(),self.sale_line.unit,self.sale_line.sale.currency,True) - - @classmethod - def table_query(cls): - SalePricing = Pool().get('pricing.pricing') - sp = SalePricing.__table__() - SaleComponent = Pool().get('pricing.component') - sc = SaleComponent.__table__() - #wh = Literal(True) - context = Transaction().context - group_pnl = context.get('group_pnl') - - if group_pnl: - return None - - query = sp.join(sc,'LEFT',condition=sp.price_component == sc.id).select( - Literal(0).as_('create_uid'), - CurrentTimestamp().as_('create_date'), - Literal(None).as_('write_uid'), - Literal(None).as_('write_date'), - Max(sp.id).as_('id'), - sp.sale_line.as_('sale_line'), - sp.price_component.as_('price_component'), - Max(sp.fixed_qt+sp.unfixed_qt).as_('quantity'), - Max(sp.fixed_qt).as_('fixed_qt'), - (Min(sp.unfixed_qt)).as_('unfixed_qt'), - Max(Case((sp.last, sp.eod_price),else_=0)).as_('price'), - (Max(sp.fixed_qt)/Max(sp.fixed_qt+sp.unfixed_qt)).as_('progress'), - Max(sc.ratio).as_('ratio'), - #where=wh, - group_by=[sp.sale_line,sp.price_component]) - - return query - -class Lot(metaclass=PoolMeta): - __name__ = 'lot.lot' - - sale_line = fields.Many2One('sale.line',"Sale",ondelete='CASCADE') - lot_quantity_sale = fields.Function(fields.Numeric("Net weight",digits='lot_unit'),'get_qt') - lot_gross_quantity_sale = fields.Function(fields.Numeric("Gross weight",digits='lot_unit'),'get_gross_qt') - - def get_qt(self, name): - quantity = self.lot_quantity - if self.lot_hist: - for h in self.lot_hist: - if h.quantity_type.id == 3: - quantity = h.quantity - return quantity - - def get_gross_qt(self, name): - quantity = self.lot_quantity - if self.lot_hist: - for h in self.lot_hist: - if h.quantity_type.id == 3: - quantity = h.quantity - return quantity - - def getClient(self): - if self.sale_line: - return Pool().get('sale.sale')(self.sale_line.sale).party.id - - def getSale(self): - if self.sale_line: - return self.sale_line.sale.id - + +class Summary(ModelSQL,ModelView): + "Pricing summary" + __name__ = 'sale.pricing.summary' + + sale_line = fields.Many2One('sale.line',"Lines") + price_component = fields.Many2One('pricing.component',"Component") + quantity = fields.Numeric("Qt",digits=(1,4)) + fixed_qt = fields.Numeric("Fixed qt",digits=(1,4)) + unfixed_qt = fields.Numeric("Unfixed qt",digits=(1,4)) + 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) + return pi.get_price(Date.today(),self.sale_line.unit,self.sale_line.sale.currency,True) + + @classmethod + def table_query(cls): + SalePricing = Pool().get('pricing.pricing') + sp = SalePricing.__table__() + SaleComponent = Pool().get('pricing.component') + sc = SaleComponent.__table__() + #wh = Literal(True) + context = Transaction().context + group_pnl = context.get('group_pnl') + + if group_pnl: + return None + + query = sp.join(sc,'LEFT',condition=sp.price_component == sc.id).select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + Max(sp.id).as_('id'), + sp.sale_line.as_('sale_line'), + sp.price_component.as_('price_component'), + Max(sp.fixed_qt+sp.unfixed_qt).as_('quantity'), + Max(sp.fixed_qt).as_('fixed_qt'), + (Min(sp.unfixed_qt)).as_('unfixed_qt'), + Max(Case((sp.last, sp.eod_price),else_=0)).as_('price'), + (Max(sp.fixed_qt)/Max(sp.fixed_qt+sp.unfixed_qt)).as_('progress'), + Max(sc.ratio).as_('ratio'), + #where=wh, + group_by=[sp.sale_line,sp.price_component]) + + return query + +class Lot(metaclass=PoolMeta): + __name__ = 'lot.lot' + + sale_line = fields.Many2One('sale.line',"Sale",ondelete='CASCADE') + lot_quantity_sale = fields.Function(fields.Numeric("Net weight",digits='lot_unit'),'get_qt') + lot_gross_quantity_sale = fields.Function(fields.Numeric("Gross weight",digits='lot_unit'),'get_gross_qt') + + def get_qt(self, name): + quantity = self.lot_quantity + if self.lot_hist: + for h in self.lot_hist: + if h.quantity_type.id == 3: + quantity = h.quantity + return quantity + + def get_gross_qt(self, name): + quantity = self.lot_quantity + if self.lot_hist: + for h in self.lot_hist: + if h.quantity_type.id == 3: + quantity = h.quantity + return quantity + + def getClient(self): + if self.sale_line: + return Pool().get('sale.sale')(self.sale_line.sale).party.id + + def getSale(self): + if self.sale_line: + return self.sale_line.sale.id + class Sale(metaclass=PoolMeta): __name__ = 'sale.sale' - - btb = fields.Many2One('back.to.back',"Back to back") - bank_accounts = fields.Function( - fields.Many2Many('bank.account', None, None, "Bank Accounts"), - 'on_change_with_bank_accounts') + + btb = fields.Many2One('back.to.back',"Back to back") + bank_accounts = fields.Function( + fields.Many2Many('bank.account', None, None, "Bank Accounts"), + 'on_change_with_bank_accounts') bank_account = fields.Many2One( 'bank.account', "Bank Account", domain=[('id', 'in', Eval('bank_accounts', []))], depends=['bank_accounts']) our_bank_account = fields.Many2One( 'bank.account', "Our Bank Account") - from_location = fields.Many2One('stock.location', 'From location', required=True,domain=[('type', "!=", 'customer')]) - to_location = fields.Many2One('stock.location', 'To location', required=True,domain=[('type', "!=", 'supplier')]) - shipment_out = fields.Many2One('stock.shipment.out','Sales') - #pnl = fields.One2Many('valuation.valuation', 'sale', 'Pnl') - pnl = fields.One2Many('valuation.valuation.dyn', 'r_sale', 'Pnl',states={'invisible': ~Eval('group_pnl'),}) - pnl_ = fields.One2Many('valuation.valuation.line', 'sale', 'Pnl',states={'invisible': Eval('group_pnl'),}) - group_pnl = fields.Boolean("Group Pnl") - derivatives = fields.One2Many('derivative.derivative', 'sale', 'Derivative') - #plans = fields.One2Many('workflow.plan','sale',"Execution plans") - forex = fields.One2Many('forex.cover.physical.sale','contract',"Forex",readonly=True) - broker = fields.Many2One('party.party',"Broker",domain=[('categories.parent', 'child_of', [4])]) - tol_min = fields.Numeric("Tol - in %", required=True) - tol_max = fields.Numeric("Tol + in %", required=True) - tol_min_qt = fields.Numeric("Tol -") - tol_max_qt = fields.Numeric("Tol +") - certif = fields.Many2One('purchase.certification',"Certification", required=True,states={'invisible': Eval('company_visible'),}) - wb = fields.Many2One('purchase.weight.basis',"Weight basis", required=True) - association = fields.Many2One('purchase.association',"Association", required=True,states={'invisible': Eval('company_visible'),}) - crop = fields.Many2One('purchase.crop',"Crop",states={'invisible': Eval('company_visible'),}) - viewer = fields.Function(fields.Text(""),'get_viewer') - doc_template = fields.Many2One('doc.template',"Template") - required_documents = fields.Many2Many( - 'contract.document.type', 'sale', 'doc_type', 'Required Documents') - analytic_dimensions = fields.One2Many( - 'analytic.dimension.assignment', - 'sale', - 'Analytic Dimensions' - ) + from_location = fields.Many2One('stock.location', 'From location', required=True,domain=[('type', "!=", 'customer')]) + to_location = fields.Many2One('stock.location', 'To location', required=True,domain=[('type', "!=", 'supplier')]) + shipment_out = fields.Many2One('stock.shipment.out','Sales') + #pnl = fields.One2Many('valuation.valuation', 'sale', 'Pnl') + pnl = fields.One2Many('valuation.valuation.dyn', 'r_sale', 'Pnl',states={'invisible': ~Eval('group_pnl'),}) + pnl_ = fields.One2Many('valuation.valuation.line', 'sale', 'Pnl',states={'invisible': Eval('group_pnl'),}) + group_pnl = fields.Boolean("Group Pnl") + derivatives = fields.One2Many('derivative.derivative', 'sale', 'Derivative') + #plans = fields.One2Many('workflow.plan','sale',"Execution plans") + forex = fields.One2Many('forex.cover.physical.sale','contract',"Forex",readonly=True) + broker = fields.Many2One('party.party',"Broker",domain=[('categories.parent', 'child_of', [4])]) + tol_min = fields.Numeric("Tol - in %", required=True) + tol_max = fields.Numeric("Tol + in %", required=True) + tol_min_qt = fields.Numeric("Tol -") + tol_max_qt = fields.Numeric("Tol +") + certif = fields.Many2One('purchase.certification',"Certification", required=True,states={'invisible': Eval('company_visible'),}) + wb = fields.Many2One('purchase.weight.basis',"Weight basis", required=True) + association = fields.Many2One('purchase.association',"Association", required=True,states={'invisible': Eval('company_visible'),}) + crop = fields.Many2One('purchase.crop',"Crop",states={'invisible': Eval('company_visible'),}) + viewer = fields.Function(fields.Text(""),'get_viewer') + doc_template = fields.Many2One('doc.template',"Template") + required_documents = fields.Many2Many( + 'contract.document.type', 'sale', 'doc_type', 'Required Documents') + analytic_dimensions = fields.One2Many( + 'analytic.dimension.assignment', + 'sale', + 'Analytic Dimensions' + ) trader = fields.Many2One( 'party.party', "Trader", domain=[('categories.name', '=', 'TRADER')]) operator = fields.Many2One( 'party.party', "Operator", domain=[('categories.name', '=', 'OPERATOR')]) - our_reference = fields.Char("Our Reference") - company_visible = fields.Function( - fields.Boolean("Visible"), 'on_change_with_company_visible') - lc_date = fields.Date("LC date") - product_origin = fields.Char("Origin") - - @fields.depends('company', '_parent_company.party') - def on_change_with_company_visible(self, name=None): - return bool( - self.company and self.company.party - and self.company.party.name == 'MELYA') - + our_reference = fields.Char("Our Reference") + company_visible = fields.Function( + fields.Boolean("Visible"), 'on_change_with_company_visible') + lc_date = fields.Date("LC date") + product_origin = fields.Char("Origin") + + @fields.depends('company', '_parent_company.party') + def on_change_with_company_visible(self, name=None): + return bool( + self.company and self.company.party + and self.company.party.name == 'MELYA') + def _get_default_bank_account(self): if not self.party or not self.party.bank_accounts: return None party_bank_accounts = list(self.party.bank_accounts) if self.currency: - for account in party_bank_accounts: + for account in party_bank_accounts: if account.currency == self.currency: return account return party_bank_accounts[0] @@ -328,32 +328,32 @@ class Sale(metaclass=PoolMeta): def on_change_company(self): super().on_change_company() self.our_bank_account = self._get_default_our_bank_account() - - @classmethod - def default_wb(cls): - WB = Pool().get('purchase.weight.basis') - wb = WB.search(['id','>',0]) - if wb: - return wb[0].id - - @classmethod - def default_certif(cls): - Certification = Pool().get('purchase.certification') - certification = Certification.search(['id','>',0]) - if certification: - return certification[0].id - - @classmethod - def default_association(cls): - Association = Pool().get('purchase.association') - association = Association.search(['id','>',0]) - if association: - return association[0].id - - @classmethod - def default_tol_min(cls): - return 0 - + + @classmethod + def default_wb(cls): + WB = Pool().get('purchase.weight.basis') + wb = WB.search(['id','>',0]) + if wb: + return wb[0].id + + @classmethod + def default_certif(cls): + Certification = Pool().get('purchase.certification') + certification = Certification.search(['id','>',0]) + if certification: + return certification[0].id + + @classmethod + def default_association(cls): + Association = Pool().get('purchase.association') + association = Association.search(['id','>',0]) + if association: + return association[0].id + + @classmethod + def default_tol_min(cls): + return 0 + @classmethod def default_tol_max(cls): return 0 @@ -761,8 +761,8 @@ class Sale(metaclass=PoolMeta): if len(line.lots)>1: unit = line.lots[1].lot_unit.name return str(int(nb_packing)) + unit - - @property + + @property def report_price(self): line = self._get_report_first_line() if line: @@ -801,8 +801,8 @@ class Sale(metaclass=PoolMeta): price_line = self._format_report_price_line(line) blocks.append((quantity_line, price_line)) return blocks - - @property + + @property def report_delivery(self): del_date = 'PROMPT' if self.lines: @@ -831,7 +831,7 @@ class Sale(metaclass=PoolMeta): if periods: return '\n'.join(periods) return '' - + @property def report_payment_date(self): line = self._get_report_first_line() @@ -888,110 +888,110 @@ class Sale(metaclass=PoolMeta): if line and self.payment_term and self.payment_term.lines: Date = Pool().get('ir.date') return self.payment_term.lines[0].get_date(Date.today(), line) - - @property - def report_shipment(self): - if self.lines: - if len(self.lines[0].lots)>1: - shipment = self.lines[0].lots[1].lot_shipment_in - lot = self.lines[0].lots[1].lot_name - if shipment: - info = '' - if shipment.bl_number: - info += ' B/L ' + shipment.bl_number - if shipment.supplier: - info += ' BY ' + shipment.supplier.name - if shipment.vessel: - info += ' (' + shipment.vessel.vessel_name + ')' - if shipment.container and shipment.container[0].container_no: - id = 1 - for cont in shipment.container: - if id == 1: - info += ' Container(s)' - if cont.container_no: - info += ' ' + cont.container_no - else: - info += ' unnamed' - id += 1 - # info += ' (LOT ' + lot + ')' - if shipment.note: - info += ' ' + shipment.note - return info - else: - return '' - - @classmethod - def default_viewer(cls): - country_start = "Zobiland" - data = { - "highlightedCountryName": country_start - } - return "d3:" + json.dumps(data) - - @fields.depends('doc_template','required_documents') - def on_change_with_required_documents(self): - if self.doc_template: - return self.doc_template.type - - 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) - - @fields.depends('party','from_location','to_location') - def on_change_with_viewer(self): - return self.get_viewer() - + + @property + def report_shipment(self): + if self.lines: + if len(self.lines[0].lots)>1: + shipment = self.lines[0].lots[1].lot_shipment_in + lot = self.lines[0].lots[1].lot_name + if shipment: + info = '' + if shipment.bl_number: + info += ' B/L ' + shipment.bl_number + if shipment.supplier: + info += ' BY ' + shipment.supplier.name + if shipment.vessel: + info += ' (' + shipment.vessel.vessel_name + ')' + if shipment.container and shipment.container[0].container_no: + id = 1 + for cont in shipment.container: + if id == 1: + info += ' Container(s)' + if cont.container_no: + info += ' ' + cont.container_no + else: + info += ' unnamed' + id += 1 + # info += ' (LOT ' + lot + ')' + if shipment.note: + info += ' ' + shipment.note + return info + else: + return '' + + @classmethod + def default_viewer(cls): + country_start = "Zobiland" + data = { + "highlightedCountryName": country_start + } + return "d3:" + json.dumps(data) + + @fields.depends('doc_template','required_documents') + def on_change_with_required_documents(self): + if self.doc_template: + return self.doc_template.type + + 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) + + @fields.depends('party','from_location','to_location') + def on_change_with_viewer(self): + return self.get_viewer() + def getLots(self): if self.lines: if self.lines.lots: @@ -1000,28 +1000,51 @@ class Sale(metaclass=PoolMeta): @classmethod def _check_lines_delivery_period_values(cls, values): line_commands = values.get('lines') or [] + logger.info( + "SALE_PARENT_LINES_PERIOD_CHECK values=%s commands=%s ", + values, + line_commands, + ) if not line_commands: return Line = Pool().get('sale.line') for command in line_commands: action = command[0] + logger.info("SALE_PARENT_LINES_COMMAND action=%s command=%s", + action, command) if action == 'create': for line_values in command[1]: + logger.info( + "SALE_PARENT_LINES_CREATE_VALUES values=%s", + line_values, + ) Line._check_delivery_period_values([Line()], line_values) elif action == 'write': actions = iter(command[1:]) for line_ids, line_values in zip(actions, actions): + logger.info( + "SALE_PARENT_LINES_WRITE_VALUES ids=%s values=%s", + line_ids, + line_values, + ) lines = Line.browse(line_ids) Line._check_delivery_period_values(lines, line_values) elif action == 'add': + logger.info("SALE_PARENT_LINES_ADD ids=%s", command[1]) lines = Line.browse(command[1]) Line._check_delivery_period_values(lines) @classmethod def write(cls, *args): + logger.info("SALE_PARENT_WRITE_START args=%s", args) actions = iter(args) args = [] for sales, values in zip(actions, actions): + logger.info( + "SALE_PARENT_WRITE_GROUP sales=%s values=%s", + [getattr(sale, 'id', None) for sale in sales], + values, + ) cls._check_lines_delivery_period_values(values) args.extend((sales, values)) super().write(*args) @@ -1029,26 +1052,26 @@ class Sale(metaclass=PoolMeta): @classmethod def validate(cls, sales): super(Sale, cls).validate(sales) - Line = Pool().get('sale.line') - Date = Pool().get('ir.date') - for sale in sales: - for line in sale.lines: - if not line.quantity_theorical and line.quantity > 0: - line.quantity_theorical = Decimal(str(line.quantity)).quantize(Decimal("0.00001")) - Line.save([line]) - - if line.lots: - line_p = line.get_matched_lines()#line.lots[0].line - if line_p: - for l in line_p: - #compute pnl - Pnl = Pool().get('valuation.valuation') - Pnl.generate(l.lot_p.line) - - # if line.quantity_theorical: - # OpenPosition = Pool().get('open.position') - # OpenPosition.create_from_sale_line(line) - + Line = Pool().get('sale.line') + Date = Pool().get('ir.date') + for sale in sales: + for line in sale.lines: + if not line.quantity_theorical and line.quantity > 0: + line.quantity_theorical = Decimal(str(line.quantity)).quantize(Decimal("0.00001")) + Line.save([line]) + + if line.lots: + line_p = line.get_matched_lines()#line.lots[0].line + if line_p: + for l in line_p: + #compute pnl + Pnl = Pool().get('valuation.valuation') + Pnl.generate(l.lot_p.line) + + # if line.quantity_theorical: + # OpenPosition = Pool().get('open.position') + # OpenPosition.create_from_sale_line(line) + if line.price_type == 'basis' and line.lots: #line.price_pricing and line.price_components and previous_linked_price = line.linked_price line.sync_linked_price_from_basis() @@ -1057,17 +1080,17 @@ class Sale(metaclass=PoolMeta): Line = Pool().get('sale.line') line.unit_price = unit_price Line.save([line]) - if line.price_type == 'efp': - if line.derivatives: - for d in line.derivatives: - line.unit_price = d.price_index.get_price(Date.today(),line.unit,line.currency,True) - Line.save([line]) - -class PriceComposition(metaclass=PoolMeta): - __name__ = 'price.composition' - - sale_line = fields.Many2One('sale.line',"Sale line") - + if line.price_type == 'efp': + if line.derivatives: + for d in line.derivatives: + line.unit_price = d.price_index.get_price(Date.today(),line.unit,line.currency,True) + Line.save([line]) + +class PriceComposition(metaclass=PoolMeta): + __name__ = 'price.composition' + + sale_line = fields.Many2One('sale.line',"Sale line") + class SaleLine(metaclass=PoolMeta): __name__ = 'sale.line' @@ -1159,59 +1182,59 @@ class SaleLine(metaclass=PoolMeta): self._check_delivery_period() del_period = fields.Many2One('product.month',"Delivery Period") - lots = fields.One2Many('lot.lot','sale_line',"Lots",readonly=True) - fees = fields.One2Many('fee.fee', 'sale_line', 'Fees') + lots = fields.One2Many('lot.lot','sale_line',"Lots",readonly=True) + fees = fields.One2Many('fee.fee', 'sale_line', 'Fees') quantity_theorical = fields.Numeric("Th. quantity", digits='unit', readonly=False) - premium = fields.Numeric("Premium/Discount",digits='unit') - 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') - 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','sale_line',"Components") - mtm = fields.Many2Many('sale.strategy', 'sale_line', 'strategy', 'Mtm Strategy') - derivatives = fields.One2Many('derivative.derivative','sale_line',"Derivatives") - price_pricing = fields.One2Many('pricing.pricing','sale_line',"Pricing") - price_summary = fields.One2Many('sale.pricing.summary','sale_line',"Summary") - estimated_date = fields.One2Many('pricing.estimated','sale_line',"Estimated date") - 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') - certif = fields.Many2One('purchase.certification',"Certification",states={'readonly': (Eval('inherit_cer')),}) - # certification = fields.Selection([ - # (None, ''), - # ('bci', 'BCI'), - # ],"Certification",states={'readonly': (Eval('inherit_cer')),}) - inherit_cer = fields.Boolean("Inherit certification") + premium = fields.Numeric("Premium/Discount",digits='unit') + 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') + 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','sale_line',"Components") + mtm = fields.Many2Many('sale.strategy', 'sale_line', 'strategy', 'Mtm Strategy') + derivatives = fields.One2Many('derivative.derivative','sale_line',"Derivatives") + price_pricing = fields.One2Many('pricing.pricing','sale_line',"Pricing") + price_summary = fields.One2Many('sale.pricing.summary','sale_line',"Summary") + estimated_date = fields.One2Many('pricing.estimated','sale_line',"Estimated date") + 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') + certif = fields.Many2One('purchase.certification',"Certification",states={'readonly': (Eval('inherit_cer')),}) + # certification = fields.Selection([ + # (None, ''), + # ('bci', 'BCI'), + # ],"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')), @@ -1226,46 +1249,46 @@ class SaleLine(metaclass=PoolMeta): 'invisible': (~Eval('enable_linked_currency')), 'required': Eval('enable_linked_currency'), }, depends=['enable_linked_currency']) - premium = fields.Numeric("Premium/Discount",digits='unit') - fee_ = fields.Many2One('fee.fee',"Fee") - - attributes = fields.Dict( - 'product.attribute', 'Attributes', - domain=[ - ('sets', '=', Eval('attribute_set')), - ], - states={ - 'readonly': ~Eval('attribute_set'), - }, - depends=['product', 'attribute_set'], - help="Add attributes to the variant." - ) - - attribute_set = fields.Function( - fields.Many2One('product.attribute.set', "Attribute Set"), - 'on_change_with_attribute_set' - ) - - attributes_name = fields.Function( - fields.Char("Attributes Name"), - 'on_change_with_attributes_name' - ) - - finished = fields.Boolean("Mark as finished") - pricing_rule = fields.Text("Pricing description") - price_composition = fields.One2Many('price.composition','sale_line',"Price composition") - - @classmethod - def default_finished(cls): - return False - - @property - def report_fixing_rule(self): - pricing_rule = '' - if self.pricing_rule: - pricing_rule = self.pricing_rule - return pricing_rule - + premium = fields.Numeric("Premium/Discount",digits='unit') + fee_ = fields.Many2One('fee.fee',"Fee") + + attributes = fields.Dict( + 'product.attribute', 'Attributes', + domain=[ + ('sets', '=', Eval('attribute_set')), + ], + states={ + 'readonly': ~Eval('attribute_set'), + }, + depends=['product', 'attribute_set'], + help="Add attributes to the variant." + ) + + attribute_set = fields.Function( + fields.Many2One('product.attribute.set', "Attribute Set"), + 'on_change_with_attribute_set' + ) + + attributes_name = fields.Function( + fields.Char("Attributes Name"), + 'on_change_with_attributes_name' + ) + + finished = fields.Boolean("Mark as finished") + pricing_rule = fields.Text("Pricing description") + price_composition = fields.One2Many('price.composition','sale_line',"Price composition") + + @classmethod + def default_finished(cls): + return False + + @property + def report_fixing_rule(self): + pricing_rule = '' + if self.pricing_rule: + pricing_rule = self.pricing_rule + return pricing_rule + @property def get_pricing_text(self): parts = [] @@ -1282,78 +1305,78 @@ class SaleLine(metaclass=PoolMeta): if part: parts.append(part) return ' '.join(parts) - - @fields.depends('product') - def on_change_with_attribute_set(self, name=None): - if self.product and self.product.template and self.product.template.attribute_set: - return self.product.template.attribute_set.id - - @fields.depends('product', 'attributes') - def on_change_with_attributes_name(self, name=None): - if not self.product or not self.product.attribute_set or not self.attributes: - return - - def key(attribute): - return getattr(attribute, 'sequence', attribute.name) - - values = [] - for attribute in sorted(self.product.attribute_set.attributes, key=key): - if attribute.name in self.attributes: - value = self.attributes[attribute.name] - values.append(gettext( - 'product_attribute.msg_label_value', - label=attribute.string, - value=attribute.format(value) - )) - return " | ".join(filter(None, values)) - - @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_matched_lines(self): - if self.lots: - LotQt = Pool().get('lot.qt') - return LotQt.search([('lot_s','=',self.lots[0].id),('lot_p','>',0)]) - - def get_date(self,trigger_event): - trigger_date = None - if self.estimated_date: - logger.info("ESTIMATED_DATE:%s",self.estimated_date) - trigger_date = [d.estimated_date for d in self.estimated_date if d.trigger == trigger_event] - logger.info("TRIGGER_DATE:%s",trigger_date) - logger.info("TRIGGER_EVENT:%s",trigger_event) - trigger_date = trigger_date[0] if trigger_date else None - return trigger_date - - def get_tol_min(self,name): - if self.inherit_tol: - if self.sale.tol_min and self.quantity_theorical: - return round((1-(self.sale.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.sale.tol_max and self.quantity_theorical: - return round((1+(self.sale.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) - + + @fields.depends('product') + def on_change_with_attribute_set(self, name=None): + if self.product and self.product.template and self.product.template.attribute_set: + return self.product.template.attribute_set.id + + @fields.depends('product', 'attributes') + def on_change_with_attributes_name(self, name=None): + if not self.product or not self.product.attribute_set or not self.attributes: + return + + def key(attribute): + return getattr(attribute, 'sequence', attribute.name) + + values = [] + for attribute in sorted(self.product.attribute_set.attributes, key=key): + if attribute.name in self.attributes: + value = self.attributes[attribute.name] + values.append(gettext( + 'product_attribute.msg_label_value', + label=attribute.string, + value=attribute.format(value) + )) + return " | ".join(filter(None, values)) + + @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_matched_lines(self): + if self.lots: + LotQt = Pool().get('lot.qt') + return LotQt.search([('lot_s','=',self.lots[0].id),('lot_p','>',0)]) + + def get_date(self,trigger_event): + trigger_date = None + if self.estimated_date: + logger.info("ESTIMATED_DATE:%s",self.estimated_date) + trigger_date = [d.estimated_date for d in self.estimated_date if d.trigger == trigger_event] + logger.info("TRIGGER_DATE:%s",trigger_date) + logger.info("TRIGGER_EVENT:%s",trigger_event) + trigger_date = trigger_date[0] if trigger_date else None + return trigger_date + + def get_tol_min(self,name): + if self.inherit_tol: + if self.sale.tol_min and self.quantity_theorical: + return round((1-(self.sale.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.sale.tol_max and self.quantity_theorical: + return round((1+(self.sale.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('sale.pricing.summary') ps = PS.search(['sale_line','=',self.id]) @@ -1363,7 +1386,7 @@ class SaleLine(metaclass=PoolMeta): if manual: return manual[0].progress or 0 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] @@ -1467,8 +1490,8 @@ class SaleLine(metaclass=PoolMeta): return self.get_price_linked_currency() if self.price_type == 'efp': if hasattr(self, 'derivatives') and self.derivatives: - for d in self.derivatives: - return d.price_index.get_price(Date.today(),self.unit,self.sale.currency,True) + for d in self.derivatives: + return d.price_index.get_price(Date.today(),self.unit,self.sale.currency,True) return self.get_price() @fields.depends( @@ -1516,26 +1539,26 @@ class SaleLine(metaclass=PoolMeta): @fields.depends(methods=['_recompute_trade_price_fields']) def on_change_linked_unit(self): self._recompute_trade_price_fields() - - 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: + + 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')]) @@ -1543,53 +1566,53 @@ class SaleLine(metaclass=PoolMeta): Pricing._sync_manual_values(pricings) Pricing._sync_manual_last(pricings) Pricing._sync_eod_price(pricings) - - if pc.triggers and pc.auto: - prDate = [] - prPrice = [] - apDate = [] - apPrice = [] - for t in pc.triggers: - prD, prP = t.getPricingListDates(pc.calendar) - apD, apP = t.getApplicationListDates(pc.calendar) - prDate.extend(prD) - prPrice.extend(prP) - apDate.extend(apD) - apPrice.extend(apP) - if pc.quota_sale: - 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 - + + if pc.triggers and pc.auto: + prDate = [] + prPrice = [] + apDate = [] + apPrice = [] + for t in pc.triggers: + prD, prP = t.getPricingListDates(pc.calendar) + apD, apP = t.getApplicationListDates(pc.calendar) + prDate.extend(prD) + prPrice.extend(prP) + apDate.extend(apD) + apPrice.extend(apP) + if pc.quota_sale: + 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 = 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) @@ -1608,22 +1631,22 @@ class SaleLine(metaclass=PoolMeta): base_quantity = self._get_pricing_base_quantity() 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.sale_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_sale),4) - price = round(Decimal(self.getnearprice(pl,d,'price',pc.pricing_date)),4) - p.settl_price = price - if price > 0: - cumul_qt += pc.quota_sale - p.fixed_qt = round(Decimal(cumul_qt),4) + dl_sorted = sorted(dl) + for d in dl_sorted: + if pc.pricing_date and d.date() > pc.pricing_date: + break + p = Pricing() + p.sale_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_sale),4) + price = round(Decimal(self.getnearprice(pl,d,'price',pc.pricing_date)),4) + p.settl_price = price + if price > 0: + cumul_qt += pc.quota_sale + p.fixed_qt = round(Decimal(cumul_qt),4) p.fixed_qt_price = round(Decimal(self.getnearprice(pl,d,'avg',pc.pricing_date)),4) #p.fixed_qt_price = p.get_fixed_price() if p.fixed_qt_price == 0: @@ -1635,18 +1658,18 @@ class SaleLine(metaclass=PoolMeta): 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.sale.currency,True)) - pr = round(pr,4) - logger.info("GENERATE_2:%s",pr) - p.unfixed_qt_price = pr - p.eod_price = p.get_eod_price_sale() - 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 - + else: + pr = Decimal(pc.price_index.get_price(p.pricing_date,self.unit,self.sale.currency,True)) + pr = round(pr,4) + logger.info("GENERATE_2:%s",pr) + p.unfixed_qt_price = pr + p.eod_price = p.get_eod_price_sale() + 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 write(cls, *args): logger.info("SALE_LINE_WRITE_START args=%s", args) @@ -1732,43 +1755,43 @@ class SaleLine(metaclass=PoolMeta): lqt.lot_quantity = round( Decimal(lqt.lot_quantity or 0) - decrease, 5) LotQt.save([lqt]) - - @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_s = line.lots[0].getVlot_s() - lqts = LotQt.search([('lot_s','=',vlot_s.id),('lot_p','!=',None),('lot_quantity','>',0)]) - if lqts: - raise UserError("You cannot delete matched sale") - return - lqts = LotQt.search([('lot_s','=',vlot_s.id)]) - LotQt.delete(lqts) - valuations = Valuation.search([('lot','in',line.lots)]) - if valuations: - Valuation.delete(valuations) - # op = OpenPosition.search(['sale_line','=',line.id]) - # if op: - # OpenPosition.delete(op) - - super(SaleLine, cls).delete(lines) - - @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_s = line.lots[0].getVlot_s() + lqts = LotQt.search([('lot_s','=',vlot_s.id),('lot_p','!=',None),('lot_quantity','>',0)]) + if lqts: + raise UserError("You cannot delete matched sale") + return + lqts = LotQt.search([('lot_s','=',vlot_s.id)]) + LotQt.delete(lqts) + valuations = Valuation.search([('lot','in',line.lots)]) + if valuations: + Valuation.delete(valuations) + # op = OpenPosition.search(['sale_line','=',line.id]) + # if op: + # OpenPosition.delete(op) + + super(SaleLine, cls).delete(lines) + + @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 validate(cls, salelines): LotQtHist = Pool().get('lot.qt.hist') @@ -1776,48 +1799,54 @@ class SaleLine(metaclass=PoolMeta): Pnl = Pool().get('valuation.valuation') super(SaleLine, cls).validate(salelines) for line in salelines: + logger.info( + "SALE_LINE_VALIDATE_PERIOD line=%s from=%s to=%s", + getattr(line, 'id', None), + getattr(line, 'from_del', None), + getattr(line, 'to_del', None), + ) if cls._has_invalid_delivery_period(line): raise UserError( "Delivery period From date must be before To date.") 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 - logger.info("FROM_VALIDATE_LINE:%s",line.created_by_code) - if not line.created_by_code: - if not line.lots and line.product.type != 'service' and line.quantity != Decimal(0): - Lot = Pool().get('lot.lot') - lot = Lot() - lot.sale_line = line.id - lot.lot_qt = line.quantity - lot.lot_unit_line = line.unit - lot.lot_quantity = Decimal(str(line.quantity)).quantize(Decimal("0.00001"))#round(line.quantity,5) - 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 - 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),('sale_line','=',line.id)]) - if not fl_check: - fl = FeeLots() - fl.fee = fee.id - fl.lot = lot.id - fl.sale_line = line.id - FeeLots.save([fl]) - + for tr in pc.triggers: + line.check_from_to(tr) + line.check_pricing() + #no lot need to create one with line quantity + logger.info("FROM_VALIDATE_LINE:%s",line.created_by_code) + if not line.created_by_code: + if not line.lots and line.product.type != 'service' and line.quantity != Decimal(0): + Lot = Pool().get('lot.lot') + lot = Lot() + lot.sale_line = line.id + lot.lot_qt = line.quantity + lot.lot_unit_line = line.unit + lot.lot_quantity = Decimal(str(line.quantity)).quantize(Decimal("0.00001"))#round(line.quantity,5) + 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 + 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),('sale_line','=',line.id)]) + if not fl_check: + fl = FeeLots() + fl.fee = fee.id + fl.lot = lot.id + fl.sale_line = line.id + FeeLots.save([fl]) + #generate valuation for purchase and sale LotQt = Pool().get('lot.qt') line = cls(line.id) @@ -1834,177 +1863,177 @@ class SaleLine(metaclass=PoolMeta): Pnl.generate(pl) if line.lots and not generated_purchase_side: Pnl.generate_from_sale_line(line) - -class SaleCreatePurchase(Wizard): - "Create mirror purchase" - __name__ = "sale.create.mirror" - - start = StateTransition() - - purchase = StateView( - 'sale.create.input', - 'purchase_trade.create_purchase_view_form', [ - Button("Cancel", 'end', 'tryton-cancel'), - Button("Create", 'creating', 'tryton-ok', default=True), - ]) - - creating = StateTransition() - - def transition_start(self): - return 'purchase' - - def transition_creating(self): - Purchase = Pool().get('purchase.purchase') - PL = Pool().get('purchase.line') - LotQt = Pool().get('lot.qt') - p = None - pl = None - for r in self.records: - if r.lines: - p = Purchase() - p.party = self.purchase.party - p.incoterm = self.purchase.incoterm - p.payment_term = self.purchase.payment_term - p.from_location = self.purchase.from_location - p.to_location = self.purchase.to_location - Purchase.save([p]) - pl = PL() - pl.quantity = r.lines[0].quantity - pl.unit = r.lines[0].unit - pl.product = r.lines[0].product - pl.unit_price = self.purchase.unit_price - pl.currency = self.purchase.currency - pl.purchase = p.id - PL.save([pl]) - #Match if requested - if self.purchase.match: - #Increase forecasted virtual part matched - if pl: - if pl.lots: - qt = Decimal(pl.quantity) - vlot_p = pl.getVirtualLot() - vlot_s = self.records[0].lines[0].getVirtualLot() - if not vlot_p.updateVirtualPart(None,qt,vlot_p,None,vlot_s): - vlot_p.createVirtualPart(qt,pl.unit,vlot_p,None,vlot_s) - #Decrease forecasted virtual part non matched - lqts = LotQt.search([('lot_p','=',vlot_p)]) - if lqts: - vlot_p.updateVirtualPart(lqts[0],-qt) - lqts = LotQt.search([('lot_s','=',vlot_s)]) - if lqts: - vlot_p.updateVirtualPart(lqts[0],-qt) - return 'end' - - def end(self): - return 'reload' - -class SaleCreatePurchaseInput(ModelView): - "Create purchase mirror" - __name__ = "sale.create.input" - party = fields.Many2One('party.party',"Supplier") - incoterm = fields.Many2One('incoterm.incoterm',"Incoterm",domain=[('location', '=', False)]) - payment_term = fields.Many2One('account.invoice.payment_term', "Payment Term") - from_location = fields.Many2One('stock.location',"From location") - to_location = fields.Many2One('stock.location',"To location") - unit_price = fields.Numeric("Price") - currency = fields.Many2One('currency.currency',"Currency") - match = fields.Boolean("Match open quantity") - -class Derivative(metaclass=PoolMeta): - "Derivative" - __name__ = 'derivative.derivative' - - sale = fields.Many2One('sale.sale',"Sale") - sale_line = fields.Many2One('sale.line',"Line") - -class Valuation(metaclass=PoolMeta): - "Valuation" - __name__ = 'valuation.valuation' - - sale = fields.Many2One('sale.sale',"Sale") - sale_line = fields.Many2One('sale.line',"Line") - -class ValuationLine(metaclass=PoolMeta): - "Last Valuation" - __name__ = 'valuation.valuation.line' - - sale = fields.Many2One('sale.sale',"Sale") - sale_line = fields.Many2One('sale.line',"Line") - -class ValuationReport(metaclass=PoolMeta): - "Valuation Report" - __name__ = 'valuation.report' - - sale = fields.Many2One('sale.sale',"Sale") - sale_line = fields.Many2One('sale.line',"Line") - -class ValuationDyn(metaclass=PoolMeta): - "Valuation" - __name__ = 'valuation.valuation.dyn' - - r_sale = fields.Many2One('sale.sale',"Sale") - r_sale_line = fields.Many2One('sale.line',"Line") - - @classmethod - def table_query(cls): - Valuation = Pool().get('valuation.valuation') - val = Valuation.__table__() - context = Transaction().context - group_pnl = context.get('group_pnl') - wh = (val.id > 0) - query = val.select( - Literal(0).as_('create_uid'), - CurrentTimestamp().as_('create_date'), - Literal(None).as_('write_uid'), - Literal(None).as_('write_date'), - Max(val.id).as_('id'), - Max(val.purchase).as_('r_purchase'), - Max(val.sale).as_('r_sale'), - Max(val.line).as_('r_line'), - Max(val.date).as_('r_date'), - Literal(None).as_('r_type'), - Max(val.reference).as_('r_reference'), - Literal(None).as_('r_counterparty'), - Max(val.product).as_('r_product'), - Literal(None).as_('r_state'), - Avg(val.price).as_('r_price'), - Max(val.currency).as_('r_currency'), - Sum(val.quantity).as_('r_quantity'), - Max(val.unit).as_('r_unit'), + +class SaleCreatePurchase(Wizard): + "Create mirror purchase" + __name__ = "sale.create.mirror" + + start = StateTransition() + + purchase = StateView( + 'sale.create.input', + 'purchase_trade.create_purchase_view_form', [ + Button("Cancel", 'end', 'tryton-cancel'), + Button("Create", 'creating', 'tryton-ok', default=True), + ]) + + creating = StateTransition() + + def transition_start(self): + return 'purchase' + + def transition_creating(self): + Purchase = Pool().get('purchase.purchase') + PL = Pool().get('purchase.line') + LotQt = Pool().get('lot.qt') + p = None + pl = None + for r in self.records: + if r.lines: + p = Purchase() + p.party = self.purchase.party + p.incoterm = self.purchase.incoterm + p.payment_term = self.purchase.payment_term + p.from_location = self.purchase.from_location + p.to_location = self.purchase.to_location + Purchase.save([p]) + pl = PL() + pl.quantity = r.lines[0].quantity + pl.unit = r.lines[0].unit + pl.product = r.lines[0].product + pl.unit_price = self.purchase.unit_price + pl.currency = self.purchase.currency + pl.purchase = p.id + PL.save([pl]) + #Match if requested + if self.purchase.match: + #Increase forecasted virtual part matched + if pl: + if pl.lots: + qt = Decimal(pl.quantity) + vlot_p = pl.getVirtualLot() + vlot_s = self.records[0].lines[0].getVirtualLot() + if not vlot_p.updateVirtualPart(None,qt,vlot_p,None,vlot_s): + vlot_p.createVirtualPart(qt,pl.unit,vlot_p,None,vlot_s) + #Decrease forecasted virtual part non matched + lqts = LotQt.search([('lot_p','=',vlot_p)]) + if lqts: + vlot_p.updateVirtualPart(lqts[0],-qt) + lqts = LotQt.search([('lot_s','=',vlot_s)]) + if lqts: + vlot_p.updateVirtualPart(lqts[0],-qt) + return 'end' + + def end(self): + return 'reload' + +class SaleCreatePurchaseInput(ModelView): + "Create purchase mirror" + __name__ = "sale.create.input" + party = fields.Many2One('party.party',"Supplier") + incoterm = fields.Many2One('incoterm.incoterm',"Incoterm",domain=[('location', '=', False)]) + payment_term = fields.Many2One('account.invoice.payment_term', "Payment Term") + from_location = fields.Many2One('stock.location',"From location") + to_location = fields.Many2One('stock.location',"To location") + unit_price = fields.Numeric("Price") + currency = fields.Many2One('currency.currency',"Currency") + match = fields.Boolean("Match open quantity") + +class Derivative(metaclass=PoolMeta): + "Derivative" + __name__ = 'derivative.derivative' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + +class Valuation(metaclass=PoolMeta): + "Valuation" + __name__ = 'valuation.valuation' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + +class ValuationLine(metaclass=PoolMeta): + "Last Valuation" + __name__ = 'valuation.valuation.line' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + +class ValuationReport(metaclass=PoolMeta): + "Valuation Report" + __name__ = 'valuation.report' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + +class ValuationDyn(metaclass=PoolMeta): + "Valuation" + __name__ = 'valuation.valuation.dyn' + + r_sale = fields.Many2One('sale.sale',"Sale") + r_sale_line = fields.Many2One('sale.line',"Line") + + @classmethod + def table_query(cls): + Valuation = Pool().get('valuation.valuation') + val = Valuation.__table__() + context = Transaction().context + group_pnl = context.get('group_pnl') + wh = (val.id > 0) + query = val.select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + Max(val.id).as_('id'), + Max(val.purchase).as_('r_purchase'), + Max(val.sale).as_('r_sale'), + Max(val.line).as_('r_line'), + Max(val.date).as_('r_date'), + Literal(None).as_('r_type'), + Max(val.reference).as_('r_reference'), + Literal(None).as_('r_counterparty'), + Max(val.product).as_('r_product'), + Literal(None).as_('r_state'), + Avg(val.price).as_('r_price'), + Max(val.currency).as_('r_currency'), + Sum(val.quantity).as_('r_quantity'), + Max(val.unit).as_('r_unit'), Sum(val.amount).as_('r_amount'), Sum(val.base_amount).as_('r_base_amount'), Sum(val.rate).as_('r_rate'), Avg(val.mtm_price).as_('r_mtm_price'), Sum(val.mtm).as_('r_mtm'), Max(val.strategy).as_('r_strategy'), - Max(val.lot).as_('r_lot'), - Max(val.sale_line).as_('r_sale_line'), - where=wh, - group_by=[val.purchase,val.sale]) - - return query - - -class Fee(metaclass=PoolMeta): - "Fee" - __name__ = 'fee.fee' - - sale_line = fields.Many2One('sale.line',"Line") - -class SaleAllocationsWizard(Wizard): - 'Open Allocations report from Sale without modal' - __name__ = 'sale.allocations.wizard' - - start_state = 'open_report' - - open_report = StateAction('purchase_trade.act_lot_report_form') - - def do_open_report(self, action): - sale_id = Transaction().context.get('active_id') - if not sale_id: - raise ValueError("No active sale ID in context") - action['context_model'] = 'lot.context' - action['pyson_context'] = PYSONEncoder().encode({ - 'sale': sale_id, - }) - return action, {} + Max(val.lot).as_('r_lot'), + Max(val.sale_line).as_('r_sale_line'), + where=wh, + group_by=[val.purchase,val.sale]) + + return query + + +class Fee(metaclass=PoolMeta): + "Fee" + __name__ = 'fee.fee' + + sale_line = fields.Many2One('sale.line',"Line") + +class SaleAllocationsWizard(Wizard): + 'Open Allocations report from Sale without modal' + __name__ = 'sale.allocations.wizard' + + start_state = 'open_report' + + open_report = StateAction('purchase_trade.act_lot_report_form') + + def do_open_report(self, action): + sale_id = Transaction().context.get('active_id') + if not sale_id: + raise ValueError("No active sale ID in context") + action['context_model'] = 'lot.context' + action['pyson_context'] = PYSONEncoder().encode({ + 'sale': sale_id, + }) + return action, {}