# 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 Mtm(metaclass=PoolMeta):
    "Mtm"
    __name__ = 'mtm.component'
    line = fields.Many2One('purchase.line')

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'

    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 %")
    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')

    @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")
    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.One2Many('mtm.component','line',"Mtm")
    tol_min = fields.Numeric("Tol - in %",states={
            'readonly': (Eval('inherit_tol')),
            })
    tol_max = fields.Numeric("Tol + in %",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]

    # @fields.depends('quantity','quantity_theorical')
    # def on_change_quantity(self):
    #     if not self.quantity_theorical:
    #         self.quantity_theorical = self.quantity
            
    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
                        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<Max(query_alias.r_invoice_amount),'partial'),else_='not'))
        wh = Literal(True)
        if state != 'all':
            wh &= status == state

        query2 = (
            query_alias
            .join(aiaml, 'LEFT', condition=(aiaml.invoice == query_alias.r_pur_invoice))
            .join(aml, 'LEFT', condition=aml.id == aiaml.line)
            .join(mo, 'LEFT', condition=mo.id == aml.move)
            .select(
            Literal(0).as_('create_uid'),
            CurrentTimestamp().as_('create_date'),
            Literal(0).as_('write_uid'),
            Literal(0).as_('write_date'),
            Max(query_alias.id).as_('id'),
            Max(query_alias.r_supplier).as_('r_supplier'),
            query_alias.r_purchase.as_('r_purchase'),
            query_alias.r_line.as_('r_line'),
            Max(query_alias.r_product).as_('r_product'),
            Max(query_alias.r_lot).as_('r_lot'),
            query_alias.r_pur_invoice.as_('r_pur_invoice'),
            Max(query_alias.r_inv_date).as_('r_inv_date'),
            Max(query_alias.r_invoice_amount).as_('r_invoice_amount'),
            Sum(aml.amount_second_currency).as_('r_payment_amount'),
            left.as_('r_left_amount'),
            status.as_('r_status'),
            Max(query_alias.r_curr).as_('r_curr'),
            Max(aml.reconciliation).as_('r_reconciliation'),
            Max(mo.id).as_('r_move'),
            Max(aml.id).as_('r_pur_payment'),
            where=wh,
            group_by=[query_alias.r_purchase,query_alias.r_line,query_alias.r_pur_invoice]
        ))
        
        return query2

class PurchaseInvoiceContext(ModelView):
    "Purchase Invoice Context"
    __name__ = 'purchase.invoice.context'

    asof = fields.Date("As of")
    todate = fields.Date("To")
    supplier = fields.Many2One('party.party',"Supplier")
    purchase =  fields.Many2One('purchase.purchase', "Purchase")
    state = fields.Selection([
        ('all', 'All'),
        ('not', 'Not'),
        ('paid', 'Paid'),
        ('partial', 'Partially')
    ], 'State')
    
    @classmethod
    def default_asof(cls):
        pool = Pool()
        Date = pool.get('ir.date')
        return Date.today().replace(day=1,month=1,year=1999)

    @classmethod
    def default_todate(cls):
        pool = Pool()
        Date = pool.get('ir.date')
        return Date.today()
    
    @classmethod
    def default_state(cls):
        return 'all'

class InvoicePayment(Wizard):
    'Payments'
    __name__ = 'purchase.invoice.payment'
    start_state = 'open_'
    open_ = StateAction('account.act_move_line_form')

    def do_open_(self, action):
        if self.record.r_pur_invoice:
            Payment = Pool().get('account.invoice-account.move.line')
            payments = Payment.search(['invoice','=',self.record.r_pur_invoice.id])
            if payments:
                lines = [e.line.id for e in payments]
                return action, {'res_id': lines}

class PnlReport(Wizard):
    'Pnl report'
    __name__ = 'pnl.report'

    start = StateAction('purchase_trade.act_pnl_bi')

    def do_start(self, action):
        pool = Pool()
        # action['views'].reverse()
        return action, {'res_id': [1]}

class PnlBI(ModelSingleton,ModelSQL, ModelView):
    'Pnl BI'
    __name__ = 'pnl.bi'

    input = fields.Text("BI")
    metabase = fields.Function(fields.Text(""),'get_bi')

    def get_bi(self,name=None):
            Configuration = Pool().get('gr.configuration')
            config = Configuration.search(['id','>',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
