# 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.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

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

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:
            if self.sale_line.quantity:
                return round(self.sale_line.quantity_theorical / (self.nbdays if self.nbdays > 0 else 1),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')

    def get_unit_sale(self,name):
        if self.sale_line:
            return self.sale_line.unit

    def get_eod_price_sale(self):
        if self.sale_line:
            return round((self.fixed_qt * self.fixed_qt_price + self.unfixed_qt * self.unfixed_qt_price)/Decimal(self.sale_line.quantity),4)
        return Decimal(0)

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

    from_location = fields.Many2One('stock.location', 'From location',domain=[('type', "!=", 'customer')])
    to_location = fields.Many2One('stock.location', 'To location',domain=[('type', "!=", 'supplier')])
    shipment_out = fields.Many2One('stock.shipment.out','Sales')
    pnl = fields.One2Many('valuation.valuation', 'sale', '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 %")
    tol_max = fields.Numeric("Tol + in %")
    # certification = fields.Selection([
    #     (None, ''),
    #     ('bci', 'BCI'),
    # ],"Certification")
    # weight_basis = fields.Selection([
    #     (None, ''),
    #     ('ncsw', 'NCSW'),
    #     ('nlw', 'NLW'),
    # ], 'Weight basis')
    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")
    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')

    @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:
                return [l for l in self.lines.lots]

    @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 = line.quantity
                    Line.save([line])
                    
                if line.lots:
                    line_p = line.lots[0].line
                    if line_p:
                        #compute pnl
                        Pnl = Pool().get('valuation.valuation')
                        Pnl.generate(line_p)

                    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 
                    unit_price = line.get_basis_price()
                    if unit_price != line.unit_price:
                        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 SaleLine(metaclass=PoolMeta):
    __name__ = 'sale.line'

    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')
    quantity_theorical = fields.Numeric("Th. quantity", digits='unit', readonly=True)
    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")
    price_components = fields.One2Many('pricing.component','sale_line',"Components")
    mtm = fields.One2Many('mtm.component','sale_line',"Mtm")
    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')),
            })
    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')),
            })
    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
 
    # @fields.depends('quantity')
    # def on_change_with_quantity_theorical(self):
    #     if not self.quantity_theorical:
    #         return self.quantity

    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])
        if ps:
            return sum((e.progress if e.progress else 0) * (e.ratio if e.ratio else 0) / 100 for e in ps)

    def getVirtualLot(self):
        if self.lots:
            return [l for l in self.lots if l.lot_type=='virtual'][0]

    def get_price(self,lot_premium=0):
        return (self.unit_price + Decimal(lot_premium) + (self.premium if self.premium else Decimal(0))) if self.unit_price else Decimal(0)
    
    def get_basis_price(self):
        price = Decimal(0)
        for pc in self.price_components:
            PP = Pool().get('sale.pricing.summary')
            pp = PP.search([('price_component','=',pc.id),('sale_line','=',self.id)])
            if pp:
                price += pp[0].price * (pc.ratio / 100)
        return round(price,4)

    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 d.price_index.get_price(Date.today(),self.unit,self.sale.currency,True)
        return self.get_price()

    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.sale_line.quantity_theorical) - pr.fixed_qt
                            pr.unfixed_qt_price = pr.fixed_qt_price
                            pr.eod_price = pr.get_eod_price_sale()
                            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:
                        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 = 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.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:
                p.fixed_qt_price = round(Decimal(self.getnearprice(pl,d,'avg_minus_1',pc.pricing_date)),4)
            p.unfixed_qt = round(Decimal(self.quantity_theorical) - Decimal(cumul_qt),4)
            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.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, records, values):
    #     if 'quantity' in values:
    #         for record in records:
    #             old_qt = record.quantity
    #             new_qt = values['quantity']
    #             logger.info("WRITE_OLD_QT:%s",old_qt)
    #             logger.info("WRITE_NEW_QT:%s",new_qt)
    #             if old_qt != new_qt:
    #                 LotQt = Pool().get('lot.qt')
    #                 lqts = LotQt.search(['lot_s','=',record.lots[0]])
    #                 if len(lqts)>1:
    #                     raise UserError("You cannot changed quantity with open quantities defined !")
    #                     return
    #                 elif len(lqts)==1:
    #                     if lqts[0].lot_p or lqts[0].lot_shipment_origin:
    #                         raise UserError("You cannot changed quantity with open quantities defined !")
    #                         return
    #                     lqts[0].lot_quantity = new_qt
    #                     LotQt.save(lqts)

    #     super().write(records, values)

    @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')
        LotQtType = Pool().get('lot.qt.type')            
        super(SaleLine, cls).validate(salelines)
        for line in salelines:
            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 = line.quantity
                    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')
            if line.lots:
                for lot in line.lots:
                    lqts = LotQt.search([('lot_s','=',lot.id),('lot_p','>',0)])
                    logger.info("VALIDATE_SL:%s",lqts)
                    if lqts:
                        purchase_lines = [e.lot_p.line for e in lqts]
                        if purchase_lines:
                            for pl in purchase_lines:
                                Pnl = Pool().get('valuation.valuation')
                                Pnl.generate(pl)

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'),
        val.type.as_('r_type'),
        Max(val.reference).as_('r_reference'),
        val.counterparty.as_('r_counterparty'),
        Max(val.product).as_('r_product'),
        val.state.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.mtm).as_('r_mtm'),
        Max(val.lot).as_('r_lot'),
        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, {}
