# 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
from trytond.model import (ModelSQL, ModelView, dualmethod)
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, Union, Select
from sql.conditionals import Coalesce
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 json
import logging
from trytond.exceptions import UserWarning, UserError

logger = logging.getLogger(__name__)

class LotAccountingGraph(ModelSQL,ModelView):
    "Lot accounting graph"
    __name__ = 'lot.accounting'

    lot = fields.Many2One('lot.lot',"Lot")
    name = fields.Char("Name")
    graph = fields.Text("Graph")
    flow_graph = fields.Function(fields.Text("Flux comptable (graph)"),
                                 'get_flow_graph')

    def get_flow_graph(self, name=None):
        return self.lot.get_flow_graph()

class LotMove(ModelSQL,ModelView):
    "Lots moves"
    __name__ = 'lot.move'

    lot = fields.Many2One('lot.lot', "Lot", ondelete='CASCADE')
    move = fields.Many2One('stock.move', "Move", ondelete='CASCADE')
    sequence = fields.Integer("Sequence")

class Lot(metaclass=PoolMeta):
    __name__ = 'lot.lot'

    line = fields.Many2One('purchase.line',"Purchase")
    move = fields.Function(fields.Many2One('stock.move',"Move"),'get_current_move')
    lot_move = fields.One2Many('lot.move','lot',"Move")
    invoice_line = fields.Many2One('account.invoice.line',"Purch.Invoice line")
    invoice_line_prov = fields.Many2One('account.invoice.line',"Purch. Invoice line prov")
    sale_invoice_line = fields.Many2One('account.invoice.line',"Sale Invoice line")
    sale_invoice_line_prov = fields.Many2One('account.invoice.line',"Sale Invoice line prov")
    delta_qt = fields.Numeric("Delta Qt")
    delta_pr = fields.Numeric("Delta Pr")
    delta_amt = fields.Numeric("Delta Amt")
    warrant_nb = fields.Char("Warrant Nb")
    #fees = fields.Many2Many('fee.lots', 'lot', 'fee',"Fees")
    dashboard = fields.Many2One('purchase.dashboard',"Dashboard")
    pivot = fields.Function(
        fields.Text('Pivot'), 'get_pivot'
    )
    vis_data = fields.Function(
        fields.Text('Graphe JSON'), 'get_vis_data'
    )

    flow_graph = fields.Function(fields.Text("Flux comptable (graph)"),
                                 'get_flow_graph')

    def get_pivot(self,name=None):
        AccountMoveLine = Pool().get('account.move.line')
        Account = Pool().get('account.account')
        Stock = Pool().get('stock.move')
        Invoice = Pool().get('account.invoice')
        ForexP = Pool().get('forex.cover.physical.contract')
        ForexS = Pool().get('forex.cover.physical.sale')
        InvoiceLine = Pool().get('account.invoice.line')
        Currency = Pool().get('currency.currency')
        aml = AccountMoveLine.search([('lot','=',self.id)])
        logger.info("GET_PIVOT:%s",aml)
        forexP = None
        forexS = None
        if self.line:
            forexP = ForexP.search(['contract','=',self.line.purchase.id])
            logger.info("GET_PIVOT:%s",forexP)
        if self.sale_line:
            forexS = ForexS.search(['contract','=',self.sale_line.sale.id])
        if forexP:
            forex = AccountMoveLine.search([('origin','=','forex.forex,'+str(forexP[0].forex.id))])
            if forex:
                aml += AccountMoveLine.search(['move','=',forex[0].move.id])
                aml_ = AccountMoveLine.search([('revaluate','=',forex[1].id)])
                if aml_:
                    aml += AccountMoveLine.search(['move','=',aml_[0].move.id])
                logger.info("GET_PIVOT:%s",aml_)
        pivot_data = {
            "data": [],
            "options": None
        }
        if aml:
            for l in aml:
                acc= Account(l.account)
                ct_type = 'Purchase'
                pr_type = 'Goods'
                acc_label = 'Account event'
                move_line = str(l.id)
                journal = l.move.journal.id
                product = self.lot_product.name
                ct_number = ''
                amount = l.debit if l.debit != 0 else -l.credit
                second_amount = ''
                rate = 1
                if l.amount_second_currency:
                    second_amount = float(abs(l.amount_second_currency))
                    rate = round(abs(amount / abs(l.amount_second_currency)),4)
                second_curr = None
                if l.second_currency:
                    second_curr = Currency(l.second_currency.id).symbol
                else:
                    second_curr = Currency(l.move.company.currency.id).symbol
                
                # if l.account.type.payable and l.account.type.statement == 'income':
                #     amount = -amount
                ori = None #l.origin
                if not ori:
                    ori = l.move.origin
                if ori:
                    origin, id = str(ori).split(',')
                    if origin == 'stock.move':
                        acc_label = '1-Reception'
                        sm = Stock(int(id))
                        if sm.to_location.type == 'customer':
                            ct_type = 'Sale'
                            if self.sale_line:
                                ct_number = self.sale_line.sale.number
                        else:
                            ct_type = 'Purchase'
                            if self.line:
                                ct_number = self.line.purchase.number
                    elif origin == 'account.invoice':
                        inv = Invoice(int(id))
                        product = None
                        if inv.lines[0].product:
                            product = inv.lines[0].product.name
                        if inv.type == 'out':
                            ct_type = 'Sale'
                            if self.sale_line:
                                ct_number = self.sale_line.sale.number
                        else:
                            ct_type = 'Purchase'
                            if self.line:
                                ct_number = self.line.purchase.number
                        if (journal == 1 and (l.account.id == 824 or l.account.id == 766)):
                            acc_label = 'COG Goods'
                        elif journal == 3:
                            acc_label = '4-Payment'
                        else:
                            if "Payment of invoice" in (l.move.description if l.move.description else ""):
                                acc_label = '4-Payment'
                            else:
                                if 'Prepayment' in (inv.description if inv.description else ""):
                                    acc_label = '0-Prepayment'
                                else:
                                    if inv.reference == 'Provisional':
                                        acc_label = '2-Prov'
                                        if l.move.description == 'Adjustment':
                                            acc_label = '2-Adjustment'
                                    else:
                                        acc_label = '3-Final'
                else:
                    if journal == 6:
                        # rate = 1
                        if l.move.IsBankMove():
                            acc_label = '4-Forex reval'
                        else:    
                            acc_label = '4-Inv reval'
                    elif journal == 8:
                        acc_label = '4-Payment Fx'

                pivot_data["data"].append(
                    {
                        "lot": self.lot_name,
                        "ct type": ct_type,
                        "pr type": pr_type,
                        "ct number": ct_number, 
                        "event": acc_label,
                        "product": product,
                        "amount": str(amount),
                        "account": str(acc.code),
                        "move": l.move.number,
                        "move line": move_line,
                        "journal": journal,
                        "event_date": str(l.move.date),
                        "rate": str(rate),
                        "tr_amount": str(second_amount),
                        "Curr": second_curr
                    }
                )
        
            pivot_data['options'] = {
                "rows": ["lot","ct type","event_date","event","move","curr","rate"],
                "cols": ["account"],                
                "aggregatorName": "Sum",
                "vals": ["amount"]
            }
            return "pivotjson:" + json.dumps(pivot_data)

    def get_flow_graph(self, name=None):
        AccountMove = Pool().get('account.move')
        colorBox = '#AABDAF'
        colorBoxB = '#5C665E'
        colorBoxH = '#C2D7C7'
        colorBoxHB = '#7A877D'
        colorCircle = '#C4D9C9'
        colorCircleB = '#617052'
        colorCircleH = '#DFF7E5'
        colorCircleHB = '#8EA67A'
        data = {
            'nodes': [
                {
                    'id': 'lot.lot ' + str(self.id),
                    'label': 'Lot\n' + self.lot_name,
                    'data_model': 'lot.lot',
                    'data_id': self.id,
                    'x': -300,
                    'y': 0,
                    'color': {
                        'background': colorBox,
                        'border': colorBoxB,
                        'highlight': {
                            'background':colorBoxH,
                            'border': colorBoxHB
                        }
                    },
                    'fixed': {
                        'x': False,
                        'y': True
                    }
                }
            ],
            'edges': []
        }

        data['nodes'].append({
            'id': 'purchase.purchase ' + str(self.line.purchase.id),
            'label': 'Purchase\n' + self.line.purchase.number + '\n' + self.line.purchase.party.name,
            'data_model': 'purchase.purchase',
            'data_id': self.line.purchase.id,
            'x': -900,
            'y': 0,
            'color': {
                'background': colorBox,
                'border': colorBoxB,
                'highlight': {
                    'background':colorBoxH,
                    'border': colorBoxHB
                }
            },
            'fixed': {
                'x': False,
                'y': True
            }
        })

        data['nodes'].append({
            'id': 'purchase.line ' + str(self.line.id),
            'label': 'Line\n' + self.line.product.name + '\n' + str(self.line.quantity),
            'data_model': 'purchase.line',
            'data_id': self.line.id,
            'x': -600,
            'y': 0,
            'color': {
                'background': colorBox,
                'border': colorBoxB,
                'highlight': {
                    'background':colorBoxH,
                    'border': colorBoxHB
                }
            },
            'fixed': {
                'x': False,
                'y': True
            }
        })

        data['edges'].append({
            'from': 'purchase.purchase ' + str(self.line.purchase.id),
            'to': 'purchase.line ' + str(self.line.id),
            'label': '',
            'length': 300,
            'color': {
                'color': '#267F82',
                'highlight': '#1D5F62',
                'hover': '#1D5F62'
            }
        })

        data['edges'].append({
            'from': 'purchase.line ' + str(self.line.id),
            'to': 'lot.lot ' + str(self.id),
            'label': '',
            'length': 300,
            'color': {
                'color': '#267F82',
                'highlight': '#1D5F62',
                'hover': '#1D5F62'
            }
        })

        labLines = ''
        if self.lot_move:
            stock_node = {
                "id": "stock.move " + str(self.lot_move[0].move.id),
                "label": "Move\n"
                + self.lot_move[0].move.from_location.name
                + "\n"
                + self.lot_move[0].move.to_location.name,
                "data_model": "stock.move",
                "data_id": self.lot_move[0].move.id,
                "shape": "circle",
                "color": {
                    "background": colorCircle, 
                    "border": colorCircleB,
                    'highlight': {
                        'background':colorCircleH,
                        'border': colorCircleHB
                    }
                },
            }

            data['nodes'].append(stock_node)
            origin = 'stock.move,' + str(self.lot_move[0].move.id)
            am = AccountMove.search([('origin','=',origin)])
            if am:
                for l in am[0].lines:
                    labLines += 'Debit ' if l.debit != 0 else 'Credit '
                    labLines += str(l.account.code) + "\n"
                data["edges"].append(
                    {
                        "from": "lot.lot " + str(self.id),
                        "to": "stock.move " + str(self.lot_move[0].move.id),
                        "label": labLines,
                        "data_model": "account.move",
                        "data_id": am[0].id,
                        "length": 300,
                        'color': {
                            'color': '#267F82',
                            'highlight': '#1D5F62',
                            'hover': '#1D5F62'
                        }
                    }
                )

            
        if self.lot_shipment_in:
            shipment_node = {
                "id": "stock.shipment.in " + str(self.lot_shipment_in.id),
                "label": "Shipment\n" + self.lot_shipment_in.reference,
                "data_model": "stock.shipment.in",
                "data_id": self.lot_shipment_in.id,
                "title": "Vessel in transit",
                "x": 0,
                "y": 0,
                'color': {
                    'background': colorBox,
                    'border': colorBoxB,
                    'highlight': {
                        'background':colorBoxH,
                        'border': colorBoxHB
                    }
                },
                "fixed": {"x": False, "y": True},
            }

            data['nodes'].append(shipment_node)
            if self.lot_move:
                data["edges"].append(
                    {
                        "from": "lot.lot " + str(self.id),
                        "to": "stock.move " + str(self.lot_move[0].move.id),
                        "label": labLines,
                        "length": 300,
                        'color': {
                            'color': '#267F82',
                            'highlight': '#1D5F62',
                            'hover': '#1D5F62'
                        }
                    }
                )
                data["edges"].append(
                    {
                        "from": "stock.move " + str(self.lot_move[0].move.id),
                        "to": "stock.shipment.in " + str(self.lot_shipment_in.id),
                        "length": 300,
                        'color': {
                            'color': '#267F82',
                            'highlight': '#1D5F62',
                            'hover': '#1D5F62'
                        }
                    }
                )

            else:
                data["edges"].append(
                    {
                        "from": "lot.lot " + str(self.id),
                        "to": "stock.shipment.in " + str(self.lot_shipment_in.id),
                        "length": 300,
                        'color': {
                            'color': '#267F82',
                            'highlight': '#1D5F62',
                            'hover': '#1D5F62'
                        }
                    }
                )


        if self.invoice_line_prov:
            prov_node = {
                "id": "account.invoice " + str(self.invoice_line_prov.invoice.id),
                "label": "Prov.\n" + self.invoice_line_prov.invoice.number,
                "data_model": "account.invoice",
                "data_id": self.invoice_line_prov.invoice.id,
                "length": 300,
                "shape": "circle",
                "color": {
                    "background": colorCircle, 
                    "border": colorCircleB,
                    'highlight': {
                        'background':colorCircleH,
                        'border': colorCircleHB
                    }
                }
            }

            data['nodes'].append(prov_node)
            origin = 'account.invoice,' + str(self.invoice_line_prov.invoice.id)
            am = AccountMove.search([('origin','=',origin)])
            if am:
                labLines = ''
                for l in am[0].lines:
                    labLines += 'Debit ' if l.debit != 0 else 'Credit '
                    labLines += str(l.account.code) + "\n"
                data["edges"].append(
                    {
                        "from": "account.invoice " + str(self.invoice_line_prov.invoice.id),
                        "to": "lot.lot " + str(self.id),
                        "label": labLines,
                        "data_model": "account.move",
                        "data_id": am[0].id,
                        "length": 300,
                        'color': {
                            'color': '#267F82',
                            'highlight': '#1D5F62',
                            'hover': '#1D5F62'
                        }
                    }
                )
        
        return "visjson:" + json.dumps(data)

    def get_vis_data(self, name):
        data = {
            "nodes": [{"id": self.id, "label": self.lot_name}],
            "edges": [{"from": self.id, "to": self.id}]
        }
        return 'visjson:' + json.dumps(data)

    def get_forex_rate(self, amount):
        """
        Calcule le taux de change moyen applicable à un montant donné,
        en fonction des contrats de couverture Forex liés à l'achat (lot).
        
        Retourne :
            rate (float): taux moyen pondéré appliqué
            left_amount (float): montant restant à couvrir (si non totalement couvert)
        """
        rate = 0.0
        left_amount = amount

        Forex = Pool().get('forex.forex')
        ForexP = Pool().get('forex.cover.physical.contract')

        # On récupère les contrats forex physiques liés au contrat d'achat
        forex_contracts = ForexP.search([
            ('contract', '=', self.line.purchase.id)
        ])

        if not forex_contracts:
            return None, amount  # Aucun contrat forex trouvé

        total_covered = sum([fc.amount for fc in forex_contracts])
        weighted_sum = Decimal(0)

        # Cas 1 : Montant totalement couvert ou surcouvert
        if total_covered >= amount:
            remaining_to_cover = amount
            for fc in forex_contracts:
                if remaining_to_cover <= 0:
                    break

                covered_part = min(fc.amount, remaining_to_cover)
                forex = Forex(fc.forex.id)
                rate_part = forex.rate

                weighted_sum += covered_part * rate_part
                remaining_to_cover -= covered_part

            rate = weighted_sum / amount
            left_amount = Decimal(0)

        # Cas 2 : Montant partiellement couvert
        else:
            for fc in forex_contracts:
                forex = Forex(fc.forex)
                weighted_sum += fc.amount * forex.rate

            rate = weighted_sum / total_covered if total_covered > 0 else None
            left_amount = amount - total_covered
        logger.info("GET_FOREX:%s",rate)
        logger.info("GET_FOREX2:%s",left_amount)
        return rate, left_amount

    def get_cog(self):
        MoveLine = Pool().get('account.move.line')
        Currency = Pool().get('currency.currency')
        Date = Pool().get('ir.date')
        AccountConfiguration = Pool().get('account.configuration')
        account_configuration = AccountConfiguration(1)
        Uom = Pool().get('product.uom')
        ml = MoveLine.search([
            ('lot', '=', self.id),
            ('fee', '=', None),
            ('account', '=', self.lot_product.account_stock_in_used.id),
            ('origin', 'ilike', '%stock.move%'),
        ])

        logger.info("GET_COG:%s",ml)
        if ml:
            return round(Decimal(sum([e.credit-e.debit for e in ml if e.description != 'Delivery'])),2)
        else:
            if self.lot_type == 'physic':
                sup_mov = self.get_last_supplier_move()
                if sup_mov:
                    logger.info("GET_COG_MOV:%s",sup_mov)
                    unit_price = sup_mov.unit_price
                    unit_price = Uom.compute_price(
                        self.lot_product.default_uom, unit_price, sup_mov.unit)
                    amount = sup_mov.company.currency.round(
                            Decimal(str(sup_mov.quantity)) * unit_price)
                    if account_configuration.stock_fx_forex == 'forex':
                        frate,amt = self.get_forex_rate(amount)
                        if frate:
                            amount = round(frate * amount,2)
                            with Transaction().set_context(date=Date.today()):
                                amount += Currency.compute(sup_mov.currency,
                                    amt, sup_mov.company.currency)
                            amount_converted = round(amount,2)
                        else:
                            with Transaction().set_context(date=sup_mov.effective_date):
                                amount_converted = Currency.compute(sup_mov.currency,
                                amount, sup_mov.company.currency)
                    else:
                        with Transaction().set_context(date=sup_mov.effective_date):
                            amount_converted = Currency.compute(sup_mov.currency,
                            amount, sup_mov.company.currency)
                    return round(amount_converted,2)
            return Decimal(0)

    def IsDelivered(self):
        if self.lot_move:
            lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            for m in lm:
                if m.move.to_location.type == 'customer' and m.move.state == 'done':
                    return True
        return False
    
    def get_received_move(self):
        if self.lot_move:
            lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            for m in lm:
                if m.move.from_location.type == 'supplier' and m.move.state == 'done':
                    return m.move
        return None
    
    def GetShipment(self,type):
        if type == 'in':
            m = self.get_current_supplier_move()
            if m and m.shipment:
                origin,id = str(m.shipment).split(',')
                if origin == 'stock.shipment.in':
                    return id 

    def get_current_customer_move(self):
        if self.lot_move:
            lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            for m in lm:
                if m.move.to_location.type == 'customer' and m.move.state != 'done' and m.move.from_location.type != 'supplier':
                    return m.move
    
    def get_current_supplier_move(self):
        if self.lot_move:
            lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            for m in lm:
                if m.move.from_location.type == 'supplier' and m.move.state != 'done':
                    return m.move
                
    def get_last_supplier_move(self):
        if self.lot_move:
            lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            for m in lm:
                if m.move.from_location.type == 'supplier':
                    return m.move

    def get_current_move(self,name):
        return self.get_current_move_by_seq()

    def get_current_move_by_seq(self,seq=-1):
        if self.lot_move:
            if seq == -1:
                lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=True)
            if seq == 0:
                lm = sorted(self.lot_move, key=lambda x: x.sequence, reverse=False)
            if lm:
                return lm[0].move

    def getSupplier(self):
        if self.line:
            return Pool().get('purchase.purchase')(self.line.purchase).party.id

    def getPurchase(self):
        if self.line:
            return self.line.purchase.id

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

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

    def getLotQt(self):
        LotQt = Pool().get('lot.qt')
        if self.sale_line and not self.line:
            return LotQt.search(['lot_s','=',self.id])
        else:
            return LotQt.search(['lot_p','=',self.id])

    @classmethod
    def default_dashboard(cls):
        return 1

    @classmethod
    def _recompute_virtual_lot(cls, line):
        LotHist = Pool().get('lot.qt.hist')

        if not line:
            return

        lots = list(line.lots or [])
        physical_lots = [l for l in lots if l.lot_type == 'physic']
        virtual_lots = [l for l in lots if l.lot_type == 'virtual']

        total_physic = sum(l.get_current_quantity_converted() for l in physical_lots)
        theorical = line.quantity_theorical if hasattr(line, 'quantity_theorical') else line.quantity
        virtual_qty = round(theorical - total_physic, 5)
        logger.info("RECOMPUTE_VIRTUAL_LOT:%s",virtual_qty)
        if virtual_lots:
            vlot = virtual_lots[0]
            lh = vlot.lot_hist[0]
            lh.quantity = virtual_qty
            lh.gross_quantity = virtual_qty
            LotHist.save([lh])
        
    @classmethod
    def validate(cls, lots):
        super(Lot, cls).validate(lots)
        LotQt = Pool().get('lot.qt')
        StockMove = Pool().get('stock.move')
        for lot in lots:
            if lot.lot_type == 'virtual':
                if lot.line:
                    lqt = LotQt.search([('lot_p','=',lot.id)])
                    if len(lqt)==0 and not lot.line.created_by_code:
                        lot.createVirtualPart(lot.lot_quantity,None,None)    
                    
                if lot.sale_line:
                    lqt = LotQt.search([('lot_s','=',lot.id)])
                    if len(lqt)==0 and not lot.sale_line.created_by_code:
                        lot.createVirtualPart(lot.lot_quantity,None,lot.id,'only sale')
                
            #Recalculate total line quantity
            if lot.lot_type == 'physic':
                if lot.line:
                    cls._recompute_virtual_lot(Pool().get('purchase.line')(lot.line))
                    cls._recalc_line_quantity(lot.line,True)
                    sup_mov = lot.get_current_supplier_move()
                    if sup_mov:
                        sup_mov.quantity = lot.get_current_quantity_converted()
                        sup_mov.unit_price = lot.get_lot_price()
                        StockMove.save([sup_mov])
                    
                if lot.sale_line:
                    cls._recompute_virtual_lot(Pool().get('sale.line')(lot.sale_line))
                    cls._recalc_line_quantity(lot.sale_line,False)
                    cus_mov = lot.get_current_customer_move()
                    if cus_mov:                        
                        cus_mov.quantity = lot.get_current_quantity_converted()
                        cus_mov.unit_price = lot.get_lot_sale_price()
                        StockMove.save([cus_mov])
                    #if matched check that a move exist sale side
                    if lot.line:
                        if not lot.IsDelivered() and not lot.get_current_customer_move():
                            from_ = lot.sale_line.sale.from_location
                            to_ = lot.sale_line.sale.to_location
                            if from_ and to_ and not (from_.type=='supplier' and to_.type=='customer'):
                                Move = Pool().get('stock.move')
                                nm = Move()
                                nm.from_location = from_
                                nm.to_location = to_
                                nm.product = lot.lot_product
                                nm.unit = lot.sale_line.unit
                                nm.quantity = lot.get_current_quantity_converted()
                                nm.origin = lot.sale_line
                                nm.lot = lot
                                nm.shipment = None
                                nm.currency = lot.sale_line.currency
                                nm.unit_price = lot.get_lot_sale_price()
                                Move.save([nm])
                            
            #check that lot with shipment are on this shipment fees
            if lot.lot_shipment_in:
                if lot.lot_shipment_in.fees:
                    for f in lot.lot_shipment_in.fees:
                        FeeLots = Pool().get('fee.lots')
                        fl = FeeLots.search(['fee','=',f.id])
                        if not fl:
                            fl = FeeLots()
                            fl.fee = f.id
                            fl.lot = lot.id
                            FeeLots.save([fl])
            
    def createVirtualPart(self,qt=0,sh=None,lot_s=None,mode='both'):
        lqt = LotQt()
        if self.lot_type == 'physic':
            lqt.lot_p = self.getVlot_p()
            lqt.lot_s = lot_s
        else:
            if mode =='both':
                lqt.lot_p = self.id
                lqt.lot_s = lot_s
            else:
                lqt.lot_s = lot_s
                lqt.lot_p = None
        if sh:
            logger.info("IDSHIP:%s",sh)
            str_sh = str(sh)
            index = str_sh.rfind(",")
            if index != -1:
                id = int(str_sh[index+1:].strip())
            if 'stock.shipment.in' in str_sh:
                lqt.lot_shipment_in = id
            elif 'stock.shipment.out' in str_sh:
                lqt.lot_shipment_out = id
            elif 'stock.shipment.internal' in str_sh:
                lqt.lot_shipment_internal = id
        lqt.lot_move = None
        lqt.lot_av = 'available'
        lqt.lot_status = 'forecast'
        lqt.lot_quantity = qt
        if self.line:
            lqt.lot_unit = self.line.unit
        if self.sale_line:
            lqt.lot_unit = self.sale_line.unit
        LotQt.save([lqt])

    def updateVirtualPart(self,qt=0,sh=None,lot_s=None,mode='both'):
        LotQt = Pool().get('lot.qt')
        vlot = self
        if self.lot_type == 'physic':
            vlot = self.getVlot_p()

        if  mode == 'only sale':
            vlot = None
        
        lqts = None
        if sh:
            str_sh = str(sh)
            index = str_sh.rfind(",")
            if index != -1:
                id = int(str_sh[index+1:].strip())
            if 'stock.shipment.in' in str_sh:
                lqts = LotQt.search([('lot_p','=',vlot),('lot_shipment_in','=',id),('lot_s','=',lot_s)])
            elif 'stock.shipment.internal' in str_sh:
                lqts = LotQt.search([('lot_p','=',vlot),('lot_shipment_internal','=',id),('lot_s','=',lot_s)])
            elif 'stock.shipment.out' in str_sh:
                lqts = LotQt.search([('lot_p','=',vlot),('lot_shipment_out','=',id),('lot_s','=',lot_s)])
        else:
            lqts = LotQt.search([('lot_p','=',vlot),('lot_s','=',lot_s),('lot_shipment_in','=',None),('lot_shipment_internal','=',None),('lot_shipment_out','=',None)])
        if lqts:
            logger.info("UVP_QT:%s",qt)
            logger.info("UVP_LQ:%s",lqts[0].lot_quantity)
            lqts[0].lot_quantity += qt
            if lqts[0].lot_quantity < 0:
                lqts[0].lot_quantity = 0
            LotQt.save(lqts)
            return True
        return False

    def createMove(self,r,qt,sh):
        Move = Pool().get('stock.move')
        nm = Move()
        nm.from_location = r.lot_p.line.purchase.from_location
        nm.to_location = r.lot_p.line.purchase.to_location
        nm.product = r.lot_p.lot_product
        nm.unit = r.lot_unit
        nm.quantity = qt
        nm.origin = r.lot_p.line
        nm.shipment = sh
        nm.currency = r.lot_p.line.currency
        nm.unit_price = r.lot_p.line.unit_price
        Move.save([nm])
        return nm.id
                    
    @classmethod
    def create(cls, vlist):
        vlist = [x.copy() for x in vlist]
        L = Pool().get('lot.lot')
        Uom = Pool().get('product.uom')
        for values in vlist:
            parent = values.get('lot_parent')
            if parent:
                #no split for virtual lot
                if values['lot_type']=='virtual':
                    return
                l = L(parent)
                balenw = round(Decimal((l.lot_quantity if l.lot_quantity else 0)/Decimal(l.lot_qt)),2)
                balegw = round(Decimal((l.lot_gross_quantity if l.lot_gross_quantity else 0)/Decimal(l.lot_qt)),2)
                nw = Decimal(int(values.get('lot_qt')) * balenw)
                gw = Decimal(int(values.get('lot_qt')) * balegw)
                if l.lot_quantity:
                    l.lot_quantity -= nw
                if l.lot_gross_quantity:
                    l.lot_gross_quantity -= gw
                if l.lot_qt:
                    l.lot_qt -= values.get('lot_qt')
                values['lot_quantity'] = nw
                values['lot_type'] = 'physic'
                values['lot_gross_quantity'] = gw
                values['line'] = l.line
                L.save([l])
            
        records = super().create(vlist)
        for record in records:
            if not record.lot_name:
                record.lot_name = str(record.id)
        cls.save(records)
        return records

    @classmethod
    def delete(cls, lots):
        pool = Pool()
        LL = pool.get('lot.lot')
        FeeLots = pool.get('fee.lots')
        lines_to_update = set()
        sale_lines_to_update = set()
        logger.info("DELETE_FROM_LOTS:%s",lots)
        for lot in lots:
            if lot.lot_type == 'physic':
                if lot.line:
                    lines_to_update.add(lot.line.id)
                if lot.sale_line:
                    sale_lines_to_update.add(lot.sale_line.id)
            if lot.lot_parent:
                pa = LL(lot.lot_parent)
                if pa:
                    pa.lot_qt += lot.lot_qt
                    pa.lot_quantity += lot.lot_quantity
                    pa.lot_gross_quantity += lot.lot_gross_quantity
                    LL.save([pa])
            logger.info("DELETE_FROM_LOTS2:%s",lot)
            fls = FeeLots.search(['lot','=',lot.id])
            if fls:
                FeeLots.delete(fls)
        
        super(Lot, cls).delete(lots)

        for line_id in lines_to_update:
            cls._recompute_virtual_lot(Pool().get('purchase.line')(line_id))
            cls._recalc_line_quantity(line_id,True)

        for line_id in sale_lines_to_update:
            cls._recompute_virtual_lot(Pool().get('sale.line')(line_id))
            cls._recalc_line_quantity(line_id,False)

    @classmethod
    def _recalc_line_quantity(cls, line_id,purchase=True):
        PL = Pool().get('purchase.line')
        SL = Pool().get('sale.line')
        if purchase:
            line = PL(line_id)
        else:
            line = SL(line_id)
        qt = Decimal(0)
        lots = list(line.lots or [])
        logger.info("DELETE_RECALC:%s",lots)
        for lot in lots:
            if len(lots) > 1:
                if lot.lot_type == 'physic':
                    qt += lot.get_current_quantity_converted()
            elif len(lots) == 1:
                qt = lot.get_current_quantity_converted()

        line.quantity = round(float(qt), 5)
        if purchase:
            PL.save([line])
        else:
            SL.save([line])
    
    # @classmethod
    # def write(cls, *args):
    #     Lot = Pool().get('lot.lot')
    #     super().write(*args)
    #     lots = sum(args[::2], [])
    
class LotQt(
        ModelSQL, ModelView):
    "Lots Qt"
    __name__ = 'lot.qt'

    lot_p = fields.Many2One('lot.lot',"Pur. lot")
    lot_s = fields.Many2One('lot.lot',"Sale lot")
    lot_quantity = fields.Numeric("Qt",digits=(1,5))
    lot_unit = fields.Many2One('product.uom', "Unit")
    lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In")
    lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
    lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal")
    lot_move = fields.Many2One('stock.move',"Move")
    lot_av = fields.Selection([
        ('available', 'Available'),
        ('reserved', 'Reserved'),
        ('locked', 'Locked'),
        ('prov', 'Prov. inv'),
        ('invoiced', 'Invoiced')
    ], 'State')
    lot_status = fields.Selection([
        ('forecast', 'Forecast'),
        ('loading', 'Loading'),
        ('transit', 'Transit'),
        ('destination', 'Destination'),
        ('stock', 'Stock'),
        ('delivered', 'Delivered')
    ], 'Where')
    lot_visible = fields.Boolean("Visible")
    lot_shipment_origin = fields.Function(
        fields.Reference(
            selection=[
                ("stock.shipment.in", "In"),
                ("stock.shipment.out", "Out"),
                ("stock.shipment.internal", "Internal"),
            ],
            string="Shipment",
        ),
        "get_shipment_origin",
    )
    
    def get_shipment_origin(self, name):
        if self.lot_shipment_in:
            return 'stock.shipment.in,' + str(self.lot_shipment_in.id)
        elif self.lot_shipment_out:
            return 'stock.shipment.out,' + str(self.lot_shipment_out.id)
        elif self.lot_shipment_internal:
            return 'stock.shipment.internal,' + str(self.lot_shipment_internal.id)
        return None

    @classmethod
    def default_lot_av(cls):
        return 'available'

    @classmethod
    def default_lot_visible(cls):
        return True

    @classmethod
    def default_lot_status(cls):
        return 'forecast'

    def get_rec_name(self, name):
        return self.id

    def isVirtualP(self):
        if self.lot_p:
            Lot = Pool().get('lot.lot')
            l = Lot(self.lot_p)
            return  (l.lot_type == 'virtual')
        return False

    def isVirtualS(self):
        if self.lot_s:
            Lot = Pool().get('lot.lot')
            l = Lot(self.lot_s)
            return  (l.lot_type == 'virtual')
        return False

    @classmethod
    def match_lots(cls,lot_p,lot_s):
        logger.info("MATCH_LOTS:%s",lot_p)
        logger.info("MATCH_LOTS:%s",lot_s)
        LotQt = Pool().get('lot.qt')
        Lot = Pool().get('lot.lot')
        for lp in lot_p:
            qt_p = lp.lot_matched_qt
            if qt_p == 0:
                continue
            for ls in lot_s:
                l = Lot(lp.lot_id)
                qt = Decimal(0)
                if qt_p >= ls.lot_matched_qt:
                    qt = ls.lot_matched_qt
                    left_qt = qt_p - qt
                else:
                    qt = qt_p
                    left_qt = 0
                lqt=LotQt(lp.lot_r_id)
                lqt2 =LotQt(ls.lot_r_id)
                if l.lot_type == 'physic':
                    Sale = Pool().get('sale.sale')
                    s = Sale(ls.lot_sale)
                    ll = s.lines[0].lots[0]    
                    #Decrease forecasted virtual parts matched
                    vlot_p = l.getVlot_p()
                    vlot_s = ll.getVlot_s()
                    logger.info("SALEVIRTUALLOT:%s",vlot_p)
                    logger.info("SALEVIRTUALLOT2:%s",vlot_s)
                    #l.updateVirtualPart(-qt,l.lot_shipment_origin,l.getVlot_s())
                    #Increase forecasted virtual part non matched
                    #if not l.updateVirtualPart(qt,l.lot_shipment_origin,None):
                    #    l.createVirtualPart(qt,l.lot_shipment_origin,None)
                    l.updateVirtualPart(-qt,None,ll,'only sale')
                    lqt.lot_s = ll
                    lqt.lot_av = 'reserved'
                    LotQt.save([lqt])
                    l.sale_line = s.lines[0]
                    Lot.save([l])
                    #ll.lot_gross_quantity -= qt
                    ll.lot_quantity -= qt
                    ll.lot_qt -= float(qt)
                    Lot.save([ll])
                else:
                    #Increase forecasted virtual part matched
                    if not l.updateVirtualPart(qt,lqt.lot_shipment_origin,lqt2.lot_s):
                        l.createVirtualPart(qt,lqt.lot_shipment_origin,lqt2.lot_s)
                    #Decrease forecasted virtual part non matched
                    l.updateVirtualPart(-qt,lqt.lot_shipment_origin,None)
                    l.updateVirtualPart(-qt,None,lqt2.lot_s,'only sale')
                if left_qt == 0:
                    break
                qt_p = left_qt

    @classmethod
    def add_physical_lots(cls,lqt,vlots):
        Purchase = Pool().get('purchase.purchase')
        LotQt = Pool().get('lot.qt')
        purchase = lqt.lot_p.line.purchase
        lots = []
        tot_qt = 0
        for l in vlots:
            lot,qt = lqt.add_physical_lot(l)
            lots.append(lot)
            tot_qt += qt
                
        #create move for lots
        Purchase._process_shipment([purchase],lots) 
        
        #update LotQt
        lqt.lot_quantity -= round(tot_qt,5)
        if lqt.lot_quantity < 0:
            lqt.lot_quantity = 0
        LotQt.save([lqt])
        
        if lots:
            return lots[0].id

    def add_physical_lot(self,l):
        Lot = Pool().get('lot.lot')
        FeeLots = Pool().get('fee.lots')
        Purchase = Pool().get('purchase.purchase')
        newlot = Lot()
        newlot.line = self.lot_p.line
        newlot.lot_shipment_in = self.lot_shipment_in
        newlot.lot_shipment_internal = self.lot_shipment_internal
        newlot.lot_shipment_out = self.lot_shipment_out
        newlot.lot_product = self.lot_p.line.product
        if self.lot_s:
            newlot.sale_line = self.lot_s.sale_line if self.lot_s.sale_line else None
        newlot.lot_type = 'physic'
        newlot.lot_qt = l.lot_qt
        newlot.lot_unit = l.lot_unit
        newlot.lot_unit_line = l.lot_unit_line
        newlot.lot_premium = l.lot_premium
        if newlot.lot_shipment_in or newlot.lot_shipment_internal or newlot.lot_shipment_out:
            newlot.lot_status = 'transit'
        newlot.set_current_quantity(l.lot_quantity,l.lot_gross_quantity,1)
        if (newlot.lot_shipment_in or newlot.lot_shipment_internal or newlot.lot_shipment_out):
            newlot.set_current_quantity(l.lot_quantity,l.lot_gross_quantity,2)
        Lot.save([newlot])
        #add lot to services
        if newlot.line.fees:
            for f in newlot.line.fees:
                exist = FeeLots.search([('fee','=',f.id),('lot','=',newlot.id)])
                if not exist:
                    fl =FeeLots()
                    fl.fee = f
                    fl.lot = newlot
                    fl.line = newlot.line
                    FeeLots.save([fl])
                #update ordered fee with physical lot quantity
                f.adjust_purchase_values()

        if newlot.sale_line and newlot.sale_line.fees:
            for f in newlot.sale_line.fees:
                exist = FeeLots.search([('fee','=',f.id),('lot','=',newlot.id)])
                if not exist:
                    fl =FeeLots()
                    fl.fee = f
                    fl.lot = newlot
                    fl.sale_line = newlot.sale_line
                    FeeLots.save([fl])
                #update ordered fee with physical lot quantity
                f.adjust_purchase_values()

        if newlot.lot_shipment_in and newlot.lot_shipment_in.fees:
            for f in newlot.lot_shipment_in.fees:
                exist = FeeLots.search([('fee','=',f.id),('lot','=',newlot.id)])
                if not exist:
                    fl =FeeLots()
                    fl.fee = f
                    fl.lot = newlot
                    FeeLots.save([fl])
                #update ordered fee with physical lot quantity
                f.adjust_purchase_values()

        return newlot, newlot.get_current_quantity_converted()

    def getMove(self,sh):
        if sh:
            if self.lot_p:
                Lot = Pool().get('lot.lot')
                LotQt = Pool().get('lot.qt')
                lp = Lot(self.lot_p)
                vlot_p = lp.getVlot_p()
                if 'stock.shipment.in' in sh:
                    lqts = LotQt.search([('lot_p','=',vlot_p),('lot_shipment_in','=',sh),('lot_move','!=',None)])
                elif 'stock.shipment.internal' in sh:
                    lqts = LotQt.search([('lot_p','=',vlot_p),('lot_shipment_internal','=',sh),('lot_move','!=',None)])
                elif 'stock.shipment.out' in sh:
                    lqts = LotQt.search([('lot_p','=',vlot_p),('lot_shipment_out','=',sh),('lot_move','!=',None)])
                if lqts:
                    return lqts[0].lot_move
                else:
                    return lp.createMove(self,self.lot_quantity,sh)
    
    @classmethod
    def updateMove(cls,move):
        if move:
            Move = Pool().get('stock.move')
            move = Move(move)
            if move:
                move.quantity = round(sum([(e.lot_quantity if e.lot_quantity else 0) for e in move.lotqt]),5)
                Move.save([move])

    @classmethod
    def validate(cls, lotqts):
        super(LotQt, cls).validate(lotqts)
        Date = Pool().get('ir.date')
        #Update Move
        for lqt in lotqts:
            cls.updateMove(lqt.lot_move)
            #generate valuation for purchase and sale
            logger.info("VALIDATE_LQT_LS:%s",lqt.lot_s)
            logger.info("VALIDATE_LQT_LP:%s",lqt.lot_p)
            if lqt.lot_p and lqt.lot_quantity > 0:
                pl = lqt.lot_p.line
                logger.info("VALIDATE_LQT_PL:%s",pl)
                # Pnl = Pool().get('valuation.valuation')
                # pnl = Pnl.search([('line','=',pl.id),('date','=',Date.today())])
                # if pnl:
                #     Pnl.delete(pnl)
                # pnl_lines = []
                # pnl_lines.extend(pl.get_pnl_fee_lines())
                # pnl_lines.extend(pl.get_pnl_price_lines())
                # pnl_lines.extend(pl.get_pnl_der_lines())  
                # Pnl.save(pnl_lines)

                #Open position update
                if pl.quantity_theorical:
                    OpenPosition = Pool().get('open.position')
                    OpenPosition.create_from_purchase_line(pl)

    @classmethod
    def getQuery(cls,purchase=None,sale=None,shipment=None,type=None,state=None,qttype=None,supplier=None,client=None,ps=None,lot_status=None,group=None,product=None,location=None,origin=None):
        pool = Pool()
        LotQt = pool.get('lot.qt')
        lqt = LotQt.__table__()
        LotQtHis = pool.get('lot.qt.hist')
        lqh = LotQtHis.__table__()
        LotP = pool.get('lot.lot')
        lp = LotP.__table__()
        LotS = pool.get('lot.lot')
        ls = LotS.__table__()
        Purchase = pool.get('purchase.purchase')
        pu = Purchase.__table__()
        PurchaseLine = pool.get('purchase.line')
        pl = PurchaseLine.__table__()
        Sale = pool.get('sale.sale')
        sa = Sale.__table__()
        SaleLine = pool.get('sale.line')
        sl = SaleLine.__table__()
        ShipmentIn = pool.get('stock.shipment.in')
        si = ShipmentIn.__table__()
        ShipmentInternal = pool.get('stock.shipment.internal')
        sin = ShipmentInternal.__table__()
        Prov = pool.get('account.invoice')
        prov = Prov.__table__()
        ProvLine = pool.get('account.invoice.line')
        prov_line = ProvLine.__table__()
        Final = pool.get('account.invoice')
        final = Final.__table__()
        FinalLine = pool.get('account.invoice.line')
        final_line = FinalLine.__table__()
        Prov_s = pool.get('account.invoice')
        prov_s = Prov_s.__table__()
        ProvLine_s = pool.get('account.invoice.line')
        prov_line_s = ProvLine_s.__table__()
        Final_s = pool.get('account.invoice')
        final_s = Final_s.__table__()
        FinalLine_s = pool.get('account.invoice.line')
        final_line_s = FinalLine_s.__table__()
        ProductUom = pool.get('product.uom')
        puom = ProductUom.__table__()
        ProductUomLine = pool.get('product.uom')
        puoml = ProductUomLine.__table__()
        
        wh = ((lqt.lot_quantity > 0) & ((lp.lot_type == 'virtual') | (ls.lot_type == 'virtual')))

        #wh &= (((lqt.create_date >= asof) & ((lqt.create_date-datetime.timedelta(1)) <= todate)))
        if ps == 'P':
            wh &= ((lqt.lot_p != None) & (lqt.lot_s == None))
        elif ps == 'S':
            wh &= (((lqt.lot_s != None) & (lqt.lot_p == None)) | ((lqt.lot_s != None) & (lqt.lot_p != None) & (lp.lot_type == 'virtual')))
        if purchase:
            wh &= (pu.id == purchase)
        if sale:
            wh &= (sa.id == sale)
        if state != 'all':
            wh &= (lqt.lot_av == state)
        if shipment:
            wh &= (lqt.lot_shipment_in == shipment)
        if product:
            wh &= (lqt.lot_product == product)
        if location:
            wh &= (Case((lqt.lot_shipment_in > 0, si.to_location),else_=Case((lqt.lot_shipment_internal > 0, sin.to_location),else_=pu.to_location)) == location)
        if type=='matched':
            wh &= ((ls.sale_line > 0) & (lp.line > 0))
        elif type=='not matched':
            wh &= (((ls.sale_line > 0) & (lp.line == None)) | ((ls.sale_line == None) & (lp.line > 0)))
        elif type=='shipped':
            wh &= (lqt.lot_shipment_in > 0)
        if qttype=='virtual':
            wh &= ((lp.lot_type == 'virtual') | (ls.lot_type == 'virtual'))
        elif qttype=='physic':
            wh &= ((lp.lot_type == 'physic') | (ls.lot_type == 'physic'))
        if lot_status:
            if lot_status == 'forecast':
                wh &= (lqt.lot_status == 'forecast')
            elif lot_status == 'loading':
                wh &= (lqt.lot_status == 'loading')
            elif lot_status == 'destination':
                wh &= (lqt.lot_status == 'destination')
            elif lot_status == 'stock':
                wh &= (lqt.lot_status == 'stock')
            elif lot_status == 'delivered':
                wh &= (lqt.lot_status == 'delivered')
        if supplier and pu:
            wh &= (pu.party == supplier)
        if client and sa:
            wh &= (sa.party == client)
        
        AvQt = Case(((lqt.lot_p>0) & (lqt.lot_s>0), 0),else_=Case((lqt.lot_p>0,lqt.lot_quantity),else_=-lqt.lot_quantity))
        MaQt = Case(((lqt.lot_p>0) & (lqt.lot_s>0), lqt.lot_quantity),else_=0)
        query = (
            lqt.join(lp,'LEFT',condition=lqt.lot_p == lp.id)
            .join(pl,'LEFT',condition=lp.line == pl.id)
            .join(pu,'LEFT', condition=pl.purchase == pu.id)
            .join(ls,'LEFT',condition=lqt.lot_s == ls.id)
            .join(sl,'LEFT',condition=ls.sale_line == sl.id)
            .join(sa,'LEFT', condition=sl.sale == sa.id)
            .join(si,'LEFT',condition=lqt.lot_shipment_in == si.id)
            .join(sin,'LEFT',condition=lqt.lot_shipment_internal == sin.id)
            .select(
                Literal(0).as_('create_uid'),
                CurrentTimestamp().as_('create_date'),
                Literal(None).as_('write_uid'),
                Literal(None).as_('write_date'),
                (lqt.id + 10000000).as_('id'),
                lqt.lot_p.as_('r_lot_p'),
                lqt.lot_s.as_('r_lot_s'),
                Case((lp.id>0, lp.lot_name),else_=ls.lot_name).as_('r_lot_name'),
                Case((lp.id>0, lp.number),else_=ls.number).as_('r_number'),
                Case((lp.id>0, lp.lot_qt),else_=ls.lot_qt).as_('r_lot_qt'),
                Case((lp.id>0, lp.lot_unit),else_=ls.lot_unit).as_('r_lot_unit'),
                AvQt.as_('r_lot_quantity'),
                MaQt.as_('r_lot_matched'),
                Case((lp.id>0, lp.lot_product),else_=ls.lot_product).as_('r_lot_product'),
                Case((lp.id>0, lp.lot_type),else_=ls.lot_type).as_('r_lot_type'),
                lqt.lot_shipment_in.as_('r_lot_shipment_in'),
                lqt.lot_shipment_out.as_('r_lot_shipment_out'),
                lqt.lot_shipment_internal.as_('r_lot_shipment_internal'),
                lqt.lot_move.as_('r_lot_move'),
                lqt.lot_status.as_('r_lot_status'),
                Case((lp.lot_type=='physic',lp.lot_av),else_=lqt.lot_av).as_('r_lot_av'),
                Case((lp.id>0, lp.lot_premium),else_=ls.lot_premium).as_('r_lot_premium'),
                Case((lp.id>0, lp.lot_premium_sale),else_=ls.lot_premium_sale).as_('r_lot_premium_sale'),
                Case((lp.id>0, lp.lot_parent),else_=ls.lot_parent).as_('r_lot_parent'),
                Case((lp.id>0, lp.lot_himself),else_=ls.lot_himself).as_('r_lot_himself'),
                Case((lp.id>0, lp.lot_container),else_=ls.lot_container).as_('r_lot_container'),
                Case((lp.id>0, lp.line),else_=None).as_('r_line'),
                Case((pu.id>0, pu.id),else_=None).as_('r_purchase'),
                Case((sa.id>0, sa.id),else_=None).as_('r_sale'),
                Case((ls.id>0, ls.sale_line),else_=None).as_('r_sale_line'),
                (MaQt + AvQt).as_('r_tot'),
                pu.party.as_('r_supplier'),
                sa.party.as_('r_client'),
                Case((lqt.lot_shipment_in > 0, si.from_location),else_=Case((lqt.lot_shipment_internal > 0, sin.from_location),else_=pu.from_location)).as_('r_from_l'),
                Case((lqt.lot_shipment_in > 0, si.to_location),else_=Case((lqt.lot_shipment_internal > 0, sin.to_location),else_=pu.to_location)).as_('r_from_t'),
                Literal(None).as_('r_lot_pur_inv_state'),
                Literal(None).as_('r_lot_sale_inv_state'),
                Literal(None).as_('r_lot_pur_inv'),
                Literal(None).as_('r_lot_sal_inv'),
                Literal(None).as_('r_lot_state'),
                where=wh
            )
        )

        wh2 = (lp.lot_type == 'physic')
        if ps == 'P':
            wh2 &= (lp.sale_line == None)
        elif ps == 'S':
            wh2 &= (lp.sale_line != None)
        if purchase:
            wh2 &= (pu.id == purchase)
        if sale:
            wh2 &= (sa.id == sale)
        if state != 'all':
            wh2 &= (lp.lot_av == state)
        if shipment:
            wh2 &= (lp.lot_shipment_in == shipment)
        if product:
            wh2 &= (lp.lot_product == product)
        if location:
            wh2 &= (Case((lp.lot_shipment_in > 0, si.to_location), else_=Case((lp.lot_shipment_internal > 0, sin.to_location), else_=pu.to_location)) == location)
        if type=='matched':
            wh2 &= (lp.sale_line != None)
        elif type=='not matched':
            wh2 &= (lp.sale_line == None)
        elif type=='shipped':
            wh2 &= (lp.lot_shipment_in > 0)
        if qttype=='virtual':
            wh2 &= False
        if lot_status:
            if lot_status == 'forecast':
                wh2 &= (lp.lot_status == 'forecast')
            elif lot_status == 'loading':
                wh2 &= (lp.lot_status == 'loading')
            elif lot_status == 'destination':
                wh2 &= (lp.lot_status == 'destination')
            elif lot_status == 'stock':
                wh2 &= (lp.lot_status == 'stock')
            elif lot_status == 'delivered':
                wh2 &= (lp.lot_status == 'delivered')
        if supplier and pu:
            wh2 &= (pu.party == supplier)
        if client and sa:
            wh2 &= (sa.party == client)

        AvQt2 = Case((lp.sale_line>0, 0),else_=lqh.quantity * (puom.factor / puoml.factor))
        MaQt2 = Case((lp.sale_line>0, lqh.quantity * (puom.factor / puoml.factor)),else_=0)
        
        query2 = (
            lp.join(lqh, "LEFT", condition=(lp.id == lqh.lot) & (lp.lot_state == lqh.quantity_type))
            .join(pl, "INNER", condition=lp.line == pl.id)
            .join(pu, "INNER", condition=pl.purchase == pu.id)
            .join(sl, "LEFT", condition=lp.sale_line == sl.id)
            .join(sa, "LEFT", condition=sl.sale == sa.id)
            .join(prov_line, "LEFT", condition=lp.invoice_line_prov == prov_line.id)
            .join(prov, "LEFT", condition=prov_line.invoice == prov.id)
            .join(prov_line_s, "LEFT", condition=lp.sale_invoice_line_prov == prov_line_s.id)
            .join(prov_s, "LEFT", condition=prov_line_s.invoice == prov_s.id)
            .join(final_line, "LEFT", condition=lp.invoice_line == final_line.id)
            .join(final, "LEFT", condition=final_line.invoice == final.id)
            .join(final_line_s, "LEFT", condition=lp.sale_invoice_line == final_line_s.id)
            .join(final_s, "LEFT", condition=final_line_s.invoice == final_s.id)
            .join(si,'LEFT',condition=lp.lot_shipment_in == si.id)
            .join(sin,'LEFT',condition=lp.lot_shipment_internal == sin.id)
            .join(puom,'LEFT',condition=lp.lot_unit_line == puom.id)
            .join(puoml,'LEFT',condition=pl.unit == puoml.id)
            .select(
                Literal(0).as_("create_uid"),
                CurrentTimestamp().as_("create_date"),
                Literal(None).as_("write_uid"),
                Literal(None).as_("write_date"),
                (lp.id).as_("id"),
                lp.id.as_("r_lot_p"),
                Literal(None).as_("r_lot_s"),
                lp.lot_name.as_("r_lot_name"),
                lp.number.as_("r_number"),
                lp.lot_qt.as_("r_lot_qt"),
                lp.lot_unit.as_("r_lot_unit"),
                AvQt2.as_("r_lot_quantity"),
                MaQt2.as_("r_lot_matched"),
                lp.lot_product.as_("r_lot_product"),
                lp.lot_type.as_("r_lot_type"),
                lp.lot_shipment_in.as_("r_lot_shipment_in"),
                lp.lot_shipment_out.as_('r_lot_shipment_out'),
                lp.lot_shipment_internal.as_('r_lot_shipment_internal'),
                lp.move.as_("r_lot_move"),
                lp.lot_status.as_("r_lot_status"),
                lp.lot_av.as_("r_lot_av"),
                lp.lot_premium.as_("r_lot_premium"),
                lp.lot_premium_sale.as_("r_lot_premium_sale"),
                lp.lot_parent.as_("r_lot_parent"),
                lp.lot_himself.as_("r_lot_himself"),
                lp.lot_container.as_("r_lot_container"),
                lp.line.as_("r_line"),
                Case((pu.id > 0, pu.id), else_=None).as_("r_purchase"),
                Case((sa.id > 0, sa.id), else_=None).as_("r_sale"),
                lp.sale_line.as_("r_sale_line"),
                (MaQt2 + Abs(AvQt2)).as_("r_tot"),
                pu.party.as_("r_supplier"),
                sa.party.as_("r_client"),
                Case((lp.lot_shipment_in > 0, si.from_location), else_=Case((lp.lot_shipment_internal > 0, sin.from_location), else_=pu.from_location)).as_("r_from_l"),
                Case((lp.lot_shipment_in > 0, si.to_location), else_=Case((lp.lot_shipment_internal > 0, sin.to_location), else_=pu.to_location)).as_("r_from_t"),
                lp.lot_pur_inv_state.as_('r_lot_pur_inv_state'),
                lp.lot_sale_inv_state.as_('r_lot_sale_inv_state'),
                Case((final.id > 0, final.id), else_=prov.id).as_('r_lot_pur_inv'),
                Case((final_s.id > 0, final_s.id), else_=prov_s.id).as_('r_lot_sal_inv'),
                lp.lot_state.as_('r_lot_state'),
                where=wh2,
            )
        )

        if group=='by_physic':
            ship_group = Case((lp.lot_shipment_in>0, lp.lot_shipment_in),else_=Case((lp.lot_shipment_internal>0, lp.lot_shipment_internal),else_=Case((lp.lot_shipment_out>0, lp.lot_shipment_out),else_=None)))
            query2 = (
            lp.join(lqh, "LEFT", condition=(lp.id == lqh.lot) & (lp.lot_state == lqh.quantity_type))
            .join(pl, "INNER", condition=lp.line == pl.id)
            .join(pu, "INNER", condition=pl.purchase == pu.id)
            .join(sl, "LEFT", condition=lp.sale_line == sl.id)
            .join(sa, "LEFT", condition=sl.sale == sa.id)
            .join(prov_line, "LEFT", condition=lp.invoice_line_prov == prov_line.id)
            .join(prov, "LEFT", condition=prov_line.invoice == prov.id)
            .join(prov_line_s, "LEFT", condition=lp.sale_invoice_line_prov == prov_line_s.id)
            .join(prov_s, "LEFT", condition=prov_line_s.invoice == prov_s.id)
            .join(final_line, "LEFT", condition=lp.invoice_line == final_line.id)
            .join(final, "LEFT", condition=final_line.invoice == final.id)
            .join(final_line_s, "LEFT", condition=lp.sale_invoice_line == final_line_s.id)
            .join(final_s, "LEFT", condition=final_line_s.invoice == final_s.id)
            .join(si,'LEFT',condition=lp.lot_shipment_in == si.id)
            .join(sin,'LEFT',condition=lp.lot_shipment_internal == sin.id)
            .join(puom,'LEFT',condition=lp.lot_unit_line == puom.id)
            .join(puoml,'LEFT',condition=pl.unit == puoml.id)
            .select(
                Literal(0).as_("create_uid"),
                CurrentTimestamp().as_("create_date"),
                Literal(None).as_("write_uid"),
                Literal(None).as_("write_date"),
                Max(lp.id).as_("id"),
                Max(lp.id).as_("r_lot_p"),
                Literal(None).as_("r_lot_s"),
                Max(lp.lot_name).as_("r_lot_name"),
                Max(lp.number).as_("r_number"),
                Sum(lp.lot_qt).as_("r_lot_qt"),
                Max(lp.lot_unit).as_("r_lot_unit"),
                Sum(AvQt2).as_("r_lot_quantity"),
                Sum(MaQt2).as_("r_lot_matched"),
                Max(lp.lot_product).as_("r_lot_product"),
                Max(lp.lot_type).as_("r_lot_type"),
                lp.lot_shipment_in.as_("r_lot_shipment_in"),
                lp.lot_shipment_internal.as_('r_lot_shipment_internal'),
                lp.lot_shipment_out.as_('r_lot_shipment_out'),
                Max(lp.move).as_("r_lot_move"),
                Max(lp.lot_status).as_("r_lot_status"),
                Max(lp.lot_av).as_("r_lot_av"),
                Avg(lp.lot_premium).as_("r_lot_premium"),
                Literal(None).as_("r_lot_premium_sale"),
                Literal(None).as_("r_lot_parent"),
                Literal(None).as_("r_lot_himself"),
                Max(lp.lot_container).as_("r_lot_container"),
                lp.line.as_("r_line"),
                Max(Case((pu.id > 0, pu.id), else_=None)).as_("r_purchase"),
                Max(Case((sa.id > 0, sa.id), else_=None)).as_("r_sale"),
                lp.sale_line.as_("r_sale_line"),
                Sum(MaQt2 + Abs(AvQt2)).as_("r_tot"),
                Max(pu.party).as_("r_supplier"),
                Max(sa.party).as_("r_client"),
                Max(Case((lp.lot_shipment_in > 0, si.from_location), else_=Case((lp.lot_shipment_internal > 0, si.from_location), else_=pu.from_location))).as_("r_from_l"),
                Max(Case((lp.lot_shipment_in > 0, si.to_location), else_=Case((lp.lot_shipment_internal > 0, si.to_location), else_=pu.to_location))).as_("r_from_t"),
                Max(lp.lot_pur_inv_state).as_('r_lot_pur_inv_state'),
                Max(lp.lot_sale_inv_state).as_('r_lot_sale_inv_state'),
                Max(Case((final.id > 0, final.id), else_=prov.id)).as_('r_lot_pur_inv'),
                Max(Case((final_s.id > 0, final_s.id), else_=prov_s.id)).as_('r_lot_sal_inv'),
                Max(lp.lot_state).as_('r_lot_state'),
                where=wh2,
                group_by=[lp.line,lp.lot_shipment_in,lp.lot_shipment_internal,lp.lot_shipment_out,lp.sale_line]
                )
            )

        if origin == 'physic':
            union = Union(query2,query2,all_=False)
        elif origin == 'open':
            union = Union(query,query,all_=False)
        else:
            union = Union(query,query2,all_=True) 
        
        ordered_union = union.select(
            Literal(0).as_("create_uid"),
            CurrentTimestamp().as_("create_date"),
            Literal(None).as_("write_uid"),
            Literal(None).as_("write_date"),
            union.id.as_("id"),
            union.r_lot_p.as_("r_lot_p"),
            Literal(None).as_("r_lot_s"),
            union.r_lot_name.as_("r_lot_name"),
            union.r_number.as_("r_number"),
            union.r_lot_qt.as_("r_lot_qt"),
            union.r_lot_unit.as_("r_lot_unit"),
            union.r_lot_quantity.as_("r_lot_quantity"),
            union.r_lot_matched.as_("r_lot_matched"),
            union.r_lot_product.as_("r_lot_product"),
            union.r_lot_type.as_("r_lot_type"),
            union.r_lot_shipment_in.as_("r_lot_shipment_in"),
            union.r_lot_shipment_out.as_('r_lot_shipment_out'),
            union.r_lot_shipment_internal.as_('r_lot_shipment_internal'),
            union.r_lot_move.as_("r_lot_move"),
            union.r_lot_status.as_("r_lot_status"),
            union.r_lot_av.as_("r_lot_av"),
            union.r_lot_premium.as_("r_lot_premium"),
            union.r_lot_premium_sale.as_("r_lot_premium_sale"),
            union.r_lot_parent.as_("r_lot_parent"),
            union.r_lot_himself.as_("r_lot_himself"),
            union.r_lot_container.as_("r_lot_container"),
            union.r_line.as_("r_line"),
            union.r_purchase.as_("r_purchase"),
            union.r_sale.as_("r_sale"),
            union.r_sale_line.as_("r_sale_line"),
            union.r_tot.as_("r_tot"),
            union.r_supplier.as_("r_supplier"),
            union.r_client.as_("r_client"),
            union.r_from_l.as_("r_from_l"),
            union.r_from_t.as_("r_from_t"),
            union.r_lot_pur_inv_state.as_('r_lot_pur_inv_state'),
            union.r_lot_sale_inv_state.as_('r_lot_sale_inv_state'),
            union.r_lot_pur_inv.as_('r_lot_pur_inv'),
            union.r_lot_sal_inv.as_('r_lot_sal_inv'),
            union.r_lot_state.as_('r_lot_state'),
            order_by=[union.r_line.desc])
        return ordered_union

class LotReport(
        ModelSQL, ModelView):
    "Lots management"
    __name__ = 'lot.report'
    r_line = fields.Many2One('purchase.line',"P. line")
    r_purchase = fields.Many2One('purchase.purchase', "Purchase")
    r_lot_p =fields.Many2One('lot.lot',"lot P")
    r_lot_s =fields.Many2One('lot.lot',"lot S")
    r_lot_name = fields.Char("Lot")
    r_number = fields.Char("Number")
    r_lot_qt = fields.Float("Quantity")
    r_lot_unit = fields.Many2One('product.uom', "Unit")
    r_lot_product = fields.Many2One('product.product', "Product")
    r_lot_type = fields.Selection([
        ('virtual', 'Open'),
        ('physic', 'Physic'),
        ('loss', 'Loss'),
    ], 'Qt type')
    r_lot_status = fields.Selection([
        (None, ''),
        ('forecast', 'Forecast'),
        ('loading', 'Loading'),
        ('transit', 'Transit'),
        ('destination', 'Destination'),
        ('stock', 'Stock'),
        ('delivered', 'Delivered')
    ], 'Where')
    r_lot_av = fields.Selection([
        ('available', 'Available'),
        ('reserved', 'Reserved'),
        ('locked', 'Locked'),
    ], 'State')
    r_lot_pur_inv_state = fields.Selection([
        (None, ''),
        ('prov', 'Prov'),
        ('invoiced', 'Final')
    ], 'Pur. inv')
    r_lot_pur_inv = fields.Many2One('account.invoice',"Pur. inv")
    r_lot_sal_inv = fields.Many2One('account.invoice',"Sale inv")
    r_lot_sale_inv_state = fields.Selection([
        (None, ''),
        ('prov', 'Prov'),
        ('invoiced', 'Final')
    ], 'Sale inv')
    r_lot_state = fields.Many2One('lot.qt.type',"Qt state")
    r_lot_quantity = fields.Numeric("Avail. Qt",digits='r_lot_unit_line')
    r_lot_matched = fields.Numeric("Matched Qt",digits='r_lot_unit_line')
    r_lot_premium = fields.Numeric("Prem/Disc", digits='r_lot_unit_line')
    r_lot_premium_sale = fields.Numeric("Prem/Disc", digits='r_lot_unit_line')
    r_lot_shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
    r_lot_shipment_out = fields.Many2One('stock.shipment.out', "Shipment Out")
    r_lot_shipment_internal = fields.Many2One('stock.shipment.internal', "Shipment Internal")
    r_lot_move = fields.Many2One('stock.move', "Move")
    r_lot_parent = fields.Many2One('lot.lot',"Parent")
    r_lot_himself = fields.Many2One('lot.lot',"Lot")
    r_lot_container = fields.Char("Container")
    r_lot_unit_line = fields.Function(fields.Many2One('product.uom', "Unit"),'get_unit')
    r_lot_price = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_price')
    r_lot_price_sale = fields.Function(fields.Numeric("Price", digits='r_lot_unit_line'),'get_lot_sale_price')
    r_sale_line = fields.Many2One('sale.line',"S. line")
    r_sale = fields.Many2One('sale.sale',"Sale")
    r_tot = fields.Numeric("Qt tot", digits='r_lot_unit_line')
    r_supplier = fields.Many2One('party.party',"Supplier")
    r_client = fields.Many2One('party.party',"Client")
    r_from_l = fields.Many2One('stock.location', 'From')
    r_from_t = fields.Many2One('stock.location', 'To')
    sh_icon = fields.Function(fields.Char("Shipment Icon"), 'on_change_with_sh_icon')
    qt_icon = fields.Function(fields.Char("Qt Icon"), 'on_change_with_qt_icon')
    r_shipment_origin = fields.Function(
        fields.Reference(
            selection=[
                ("stock.shipment.in", "In"),
                ("stock.shipment.out", "Out"),
                ("stock.shipment.internal", "Internal"),
            ],
            string="Shipment",
        ),
        "get_shipment_origin",
    )
    vis_data = fields.Function(
        fields.Text('Graphe JSON'), 'get_vis_data'
    )

    def get_vis_data(self, name):
        AccountMoveLine = Pool().get('account.move.line')
        Account = Pool().get('account.account')
        Stock = Pool().get('stock.move')
        Lot = Pool().get('lot.lot')
        Invoice = Pool().get('account.invoice')
        InvoiceLine = Pool().get('account.invoice.line')
        aml = AccountMoveLine.search([('lot','>',0)])
        pivot_data = {
            "data": [],
            "options": None
        }
        if aml:
            for l in aml:
                lot = Lot(l.lot)
                acc= Account(l.account)
                ct_type = 'Purchase'
                pr_type = 'Goods'
                acc_label = 'Account event'
                move_line = str(l.id)
                journal = l.move.journal.id
                product = lot.lot_product.name
                ct_number = ''
                amount = l.debit if l.debit != 0 else -l.credit
                # if l.account.type.payable and l.account.type.statement == 'income':
                #     amount = -amount
                ori = None #l.origin
                if not ori:
                    ori = l.move.origin
                if ori:
                    origin, id = str(ori).split(',')
                    if origin == 'stock.move':
                        acc_label = 'Reception'
                        sm = Stock(int(id))
                        if sm.to_location.type == 'customer':
                            ct_type = 'Sale'
                            if lot.sale_line:
                                ct_number = lot.sale_line.sale.number
                        else:
                            ct_type = 'Purchase'
                            if lot.line:
                                ct_number = lot.line.purchase.number
                    elif origin == 'account.invoice':
                        inv = Invoice(int(id))
                        product = None
                        if inv.lines[0].product:
                            product = inv.lines[0].product.name
                        if inv.type == 'out':
                            ct_type = 'Sale'
                            if lot.sale_line:
                                ct_number = lot.sale_line.sale.number
                        else:
                            ct_type = 'Purchase'
                            if lot.line:
                                ct_number = lot.line.purchase.number
                        if (journal == 1 and (l.account.id == 824 or l.account.id == 766)):
                            acc_label = 'COG Goods'
                        else:
                            if "Payment of invoice" in (l.move.description if l.move.description else ""):
                                acc_label = 'Payment'
                            else:
                                if 'Prepayment' in (inv.description if inv.description else ""):
                                    acc_label = 'Prepayment'
                                else:
                                    if inv.reference == 'Provisional':
                                        acc_label = 'Prov'
                                    else:
                                        acc_label = 'Final'

                pivot_data["data"].append(
                    {
                        "lot": lot.lot_name,
                        "ct type": ct_type,
                        "pr type": pr_type,
                        "ct number": ct_number, 
                        "event": acc_label,
                        "product": product,
                        "amount": str(amount),
                        "account": str(acc.code),
                        "move": str(l.move),
                        "move line": move_line,
                        "journal": journal
                    }
                )
        
            pivot_data['options'] = {
                "rows": ["lot","ct type","event"],
                "cols": ["account"],                
                "aggregatorName": "Sum",
                "vals": ["amount"]
            }
            return "pivotjson:" + json.dumps(pivot_data)

    def get_shipment_origin(self, name):
        if self.r_lot_shipment_in:
            return 'stock.shipment.in,' + str(self.r_lot_shipment_in.id)
        elif self.r_lot_shipment_out:
            return 'stock.shipment.out,' + str(self.r_lot_shipment_out.id)
        elif self.r_lot_shipment_internal:
            return 'stock.shipment.internal,' + str(self.r_lot_shipment_internal.id)
        return None

    @classmethod
    def __setup__(cls):
        cls.r_line.search_unaccented = False
        super(LotReport, cls).__setup__()
        cls._order.insert(0, ('r_line', 'DESC'))
    
    @fields.depends('r_lot_type')
    def on_change_with_qt_icon(self, name=None):
        mapping = {
            'virtual': '\U0001F4D1',   # 📑
            'physic':   '\U0001F4E6',   # 📦 
        }
        return mapping.get(self.r_lot_type if self.r_lot_type else None, '')


    @fields.depends('r_lot_shipment_in')
    def on_change_with_sh_icon(self, name=None):
        mapping = {
            '': '\U0001F69A', #truck
            'vessel': '\U0001F6A2', 
        }
        return mapping.get(self.r_lot_shipment_in.transport_type if self.r_lot_shipment_in else None, '')

    @classmethod
    def _get_origin(cls):
        'Return list of Model names for origin Reference'
        return [cls.__name__]

    @classmethod
    def get_origin(cls):
        Model = Pool().get('ir.model')
        get_name = Model.get_name
        models = cls._get_origin()
        return [(None, '')] + [(m, get_name(m)) for m in models]

    def get_unit(self,name):
        if self.r_line:
            return self.r_line.unit
        if self.r_sale_line:
            return self.r_sale_line.unit

    def get_lot_price(self,name=None):
        price = Decimal(0)
        if self.r_line:
            l = Pool().get('purchase.line')(self.r_line)
            premium = Decimal(0)
            if self.r_lot_premium:
                premium = self.r_lot_premium
            price = premium + l.unit_price  
        return round(price,2)

    def get_lot_sale_price(self,name=None):
        price = Decimal(0)
        # if self.sale_line:
        #     l = Pool().get('sale.line')(self.sale_line)
        #     premium = Decimal(0)
        #     if self.lot_premium_sale:
        #         premium = self.lot_premium_sale
        #     price = premium + l.unit_price  
        return round(price,2)

    #@classmethod
    #def delete(cls, records):
        # Unmatch = Pool().get('lot.unmatch', type='wizard')
        # session_id, _, _ = Unmatch.create()
        # Unmatch.execute(session_id, {}, 'start')

    @classmethod
    def table_query(cls, context=None):
        if context is None:
            context = Transaction().context or {}
        LotQT = Pool().get('lot.qt')
        purchase = context.get('purchase')
        sale = context.get('sale')
        shipment = context.get('shipment')
        product = context.get('product')
        location = context.get('location')
        type = context.get('type')
        state = context.get('state')
        wh = context.get('wh')
        mode = context.get('mode')
        group = context.get('group')
        origin = context.get('origin')
        supplier = context.get('supplier')
        #asof = context.get('asof')
        #todate = context.get('todate')
        query = LotQt.getQuery(purchase,sale,shipment,type,state,None,supplier,None,None,wh,group,product,location,origin)
        return query

    @classmethod
    def search_count(cls, domain, active_test=True, context=None):
        type = 'all'
        if 'r_lot_matched' in str(domain):
            type = 'matched'
        context = Transaction().context
        context = {'purchase':None,'sale':None,'shipment':None,'type':type,'state':'all','wh':'all','group':'by_physic'}
        query = cls.table_query(context)
        cursor = Transaction().connection.cursor()
        cursor.execute(*query)
        return cursor.rowcount

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree/field[@name="r_lot_shipment_in"]', 'tree_invisible',
                (Eval('mode')=='pnl')),
            ('/tree/field[@name="r_lot_state"]', 'tree_invisible',
                (Eval('mode')=='pnl')),
            ]

    # @classmethod
    # def search_rec_name(cls, name, clause):
    #     _, operator, operand, *extra = clause
    #     if operator.startswith('!') or operator.startswith('not '):
    #         bool_op = 'AND'
    #     else:
    #         bool_op = 'OR'
    #     code_value = operand
    #     if operator.endswith('like') and is_full_text(operand):
    #         code_value = lstrip_wildcard(operand)
    #     return [bool_op,
    #         ('r_line', operator, operand, *extra),
    #         ('r_lot_name', operator, operand, *extra),
    #         ('r_lot_product', operator, operand, *extra),
    #         ]

class LotContext(ModelView):
    "Lot Context"
    __name__ = 'lot.context'

    asof = fields.Date("As of")
    todate = fields.Date("To")
    purchase =  fields.Many2One('purchase.purchase', "Purchase")
    supplier = fields.Many2One('party.party', "Supplier")
    sale =  fields.Many2One('sale.sale', "Sale")
    shipment =  fields.Many2One('stock.shipment.in', "Shipment")
    product = fields.Many2One('product.product',"Product")
    location = fields.Many2One('stock.location',"Location")
    type = fields.Selection([
        ('all', 'All'),
        ('matched', 'Matched'),
        ('not matched', 'Not matched'),
        ('shipped', 'Shipped')
    ], 'Type')
    state = fields.Selection([
        ('all', 'All'),
        ('available', 'Available'),
        ('reserved', 'Reserved'),
        ('locked', 'Locked')
    ], 'State')

    wh = fields.Selection([
        ('all', 'All'),
        ('forecast', 'Forecast'),
        ('loading', 'Loading'),
        ('destination', 'Destination'),
        ('stock', 'Stock'),
        ('delivered', 'Delivered')
    ], 'Where')

    group = fields.Selection([
        ('all', 'All'),
        ('by_physic', 'By physic'),
    ], 'Group')

    origin = fields.Selection([
        ('all', 'All'),
        ('open', 'Open'),
        ('physic', 'Physic'),
    ], 'Open/Physic')

    mode = fields.Selection([
        ('normal', 'Normal'),
        ('pnl', 'Pnl'),
    ],'Mode')

    @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_type(cls):
        return 'all'

    @classmethod
    def default_group(cls):
        return 'by_physic'

    @classmethod
    def default_wh(cls):
        return 'all'

    @classmethod
    def default_state(cls):
        return 'all'

    @classmethod
    def default_purchase(cls):
        return Transaction().context.get('purchase', None)
    
    @classmethod
    def default_sale(cls):
        return Transaction().context.get('sale', None)

    @classmethod
    def get_context(cls, fields_names=None):
        fields_names = fields_names.copy() if fields_names is not None else []
        fields_names.append('purchase')
        fields_names.append('sale')
        return super().get_context(fields_names=fields_names)

    
class LotShipping(Wizard):
    "Link lot to transport"
    __name__ = "lot.shipping"

    start = StateTransition()

    ship = StateView(
        'lot.shipping.start',
        'purchase_trade.shipping_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Link", 'shipping', 'tryton-ok', default=True),
        ])
    
    shipping = StateTransition()

    def transition_start(self):
        if self.model.__name__ == 'lot.report':
            return 'ship'
        return 'end'
    
    #def default_ship(self, fields):
        #values = []
        # if self.model.__name__ == 'lot.report':
            # return {
            #     }

    def transition_shipping(self):
        Lot = Pool().get('lot.lot')
        LotQt = Pool().get('lot.qt')
        Move = Pool().get('stock.move')
        if not self.ship.shipment:
            pass
        for r in self.records:
            if r.r_lot_shipment_in:
                raise UserError("Please unlink before linking to a new shipment !")
            else:
                shipped_quantity = Decimal(r.r_lot_quantity)
                shipment_origin = None
                if self.ship.quantity:
                    shipped_quantity = self.ship.quantity
                if shipped_quantity == 0:
                    shipped_quantity = Decimal(r.r_lot_matched)
                if self.ship.shipment == 'in':
                    shipment_origin = 'stock.shipment.in,'+str(self.ship.shipment_in.id)
                elif self.ship.shipment == 'out':
                    shipment_origin = 'stock.shipment.out,'+str(self.ship.shipment_out.id)
                elif self.ship.shipment == 'int':
                    shipment_origin = 'stock.shipment.internal,'+str(self.ship.shipment_internal.id)
                if r.id < 10000000 :
                    l = Lot(r.id)
                    logger.info("IN_SHIPPING1:%s",l)
                    if self.ship.shipment == 'in':
                        l.lot_shipment_in = self.ship.shipment_in
                    elif self.ship.shipment == 'out':
                        l.lot_shipment_out = self.ship.shipment_out
                    elif self.ship.shipment == 'int':
                        l.lot_shipment_internal = self.ship.shipment_internal
                    logger.info("IN_SHIPPING2:%s",l.move)
                    if not l.move:
                        continue
                    logger.info("IN_SHIPPING3:%s",r)
                    move = Move(l.move)
                    move.shipment = shipment_origin
                    Move.save([move])
                    linked_transit_move = move.get_linked_transit_move()
                    if linked_transit_move:
                        linked_transit_move.shipment = shipment_origin
                        Move.save([linked_transit_move])
                    #Decrease forecasted virtual part shipped
                    vlot_p = l.getVlot_p()
                    l.updateVirtualPart(-l.get_current_quantity_converted(),shipment_origin,l.getVlot_s())
                    l.lot_av = 'reserved'
                    Lot.save([l])
                else:
                    lqt = LotQt(r.id - 10000000)
                    #Increase forecasted virtual part shipped
                    if not lqt.lot_p.updateVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s):
                        lqt.lot_p.createVirtualPart(shipped_quantity,shipment_origin,lqt.lot_s)
                    #Decrease forecasted virtual part non shipped
                    lqt.lot_p.updateVirtualPart(-shipped_quantity,None,lqt.lot_s)    
                    
        return 'end'

    def end(self):
        return 'reload'

class LotShippingStart(ModelView):
    "Link lot to transport"
    __name__ = "lot.shipping.start"
    shipment = fields.Selection([
        ('in', 'Shipment In'),
        ('out', 'Shipment Out'),
        ('int', 'Internal Shipment'),
    ], 'Shipment type')
    shipment_in = fields.Many2One('stock.shipment.in',"Shipment In", states={'invisible': (Eval('shipment') != 'in')})
    shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out", states={'invisible': (Eval('shipment') != 'out')})
    shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal", states={'invisible': (Eval('shipment') != 'int')})
    full = fields.Boolean("Link all the lot")
    quantity = fields.Numeric("Qt", states={'readonly': (Eval('full') == True)})

    @classmethod
    def default_full(cls):
        return True

    @classmethod
    def default_shipment(cls):
        return 'in'

class QtWarning(UserWarning):
    pass

class LotMatching(Wizard):
    "Matching"
    __name__ = "lot.matching"

    start = StateTransition()

    match = StateView(
        'lot.matching.start',
        'purchase_trade.matching_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Match", 'matching', 'tryton-ok', default=True),
        ])
    
    matching = StateTransition()

    def transition_start(self):
        return 'match'
        
    def transition_matching(self):
        Warning = Pool().get('res.user.warning')   
        LotQt = Pool().get('lot.qt')
        if self.match.tot_p != self.match.tot_s:
            warning_name = Warning.format("Quantity's issue", [])
            if Warning.check(warning_name):
                raise QtWarning(warning_name,
                            "Quantities not compatibles")   
        Lot = Pool().get('lot.lot')
        LotQt.match_lots(self.match.lot_p,self.match.lot_s)
        
        return 'end'
    
    def end(self):
        return 'reload'

class LotMatchingStart(ModelView):
    "Matching"
    __name__ = "lot.matching.start"
    lot_p = fields.One2Many('lot.matching.lot','lms',"Purchase")
    tot_p = fields.Numeric("Tot. from P")
    lot_s = fields.One2Many('lot.matching.lot','lms',"Sale")
    tot_s = fields.Numeric("Tot. from S")
    filter_p = fields.Boolean("Purchase filter")
    filter_s = fields.Boolean("Sale filter")
    product_s = fields.Many2Many('product.product',None,None,"Sale product")
    product_p = fields.Many2Many('product.product',None,None,"Purchase product")
    location_p = fields.Many2Many('stock.location',None,None,"Purchase destination")
    location_s = fields.Many2Many('stock.location',None,None,"Sale loading")
    cp_p = fields.Many2Many('party.party',None,None,"Supplier")
    cp_s = fields.Many2Many('party.party',None,None,"Client")
    inc_p = fields.Many2Many('incoterm.incoterm',None,None,"Supplier incoterm")
    inc_s = fields.Many2Many('incoterm.incoterm',None,None,"Client incoterm")
    qt_type = fields.Selection([
        ('virtual', 'Open'),
        ('physic', 'Physic'),
        ('all', 'All'),
    ], 'Qt type')
    purchase = fields.Many2One('purchase.purchase',"Purchase")
    sale = fields.Many2One('sale.sale',"Sale")

    @classmethod
    def __setup__(cls):
        super(LotMatchingStart, cls).__setup__()
        cls._buttons.update({
                'filters': {
                    },
                })
    
    @classmethod
    def default_qt_type(cls):
        return 'all'

    @dualmethod
    @ModelView.button
    def filters(cls, lotmatchingstarts):
        pool = Pool()

    # @fields.depends('filter_p')
    # def on_change_filter_p(self, name=None):
    #     if self.filter_p:
    #         ApplyMatching = Pool().get('lot.matching', type='wizard')
    #         session_id, _, _ = ApplyMatching.create()
    #         ApplyMatching.execute(session_id, {}, 'start')

    @fields.depends('lot_p')
    def on_change_with_tot_p(self, name=None):
        return sum([e.lot_matched_qt for e in self.lot_p])

    @fields.depends('lot_s')
    def on_change_with_tot_s(self, name=None):
        return sum([e.lot_matched_qt for e in self.lot_s])

    @fields.depends('product_s','qt_type','sale','cp_s')
    def on_change_with_lot_s(self, name=None):
        lp = []
        LotQt = Pool().get('lot.qt')
        cursor = Transaction().connection.cursor()
        query = LotQt.getQuery(None,self.sale.id if self.sale else None,None,'not matched','all',self.qt_type,None,self.cp_s.id if self.cp_s else None,'S',None,None,self.product_s,None,'open')
        cursor.execute(*query)
        
        for r in cursor_dict(cursor):
            val = {}
            val['lot_id'] = r['r_lot_s']
            val['lot_r_id'] = r['id'] - 10000000
            val['lot_sale'] = r['r_sale']
            val['lot_purchase'] = r['r_purchase']
            val['lot_shipment_in'] = r['r_lot_shipment_in']
            val['lot_shipment_internal'] = r['r_lot_shipment_internal']
            val['lot_shipment_out'] = r['r_lot_shipment_out']
            val['lot_type'] = r['r_lot_type']
            val['lot_product'] = r['r_lot_product']
            val['lot_quantity'] = -r['r_lot_quantity']
            if r['r_sale'] and r['r_purchase']:
                val['lot_quantity'] = r['r_lot_matched']
            val['lot_matched_qt'] = 0
            val['lot_cp'] = r['r_client']
            lp.append(val)
        return lp

    @fields.depends('cp_p','qt_type','purchase','product_p')
    def on_change_with_lot_p(self, name=None):
        lp = []
        LotQt = Pool().get('lot.qt')
        cursor = Transaction().connection.cursor()
        query = LotQt.getQuery(self.purchase.id if self.purchase else None,None,None,'not matched','all',self.qt_type,self.cp_p.id if self.cp_p else None,None,'P',None,None,self.product_p)
        cursor.execute(*query)
        
        for r in cursor_dict(cursor):
            val = {}
            val['lot_id'] = r['r_lot_p']
            val['lot_r_id'] = r['id'] - 10000000
            val['lot_purchase'] = r['r_purchase']
            val['lot_sale'] = r['r_sale']
            val['lot_shipment_in'] = r['r_lot_shipment_in']
            val['lot_shipment_internal'] = r['r_lot_shipment_internal']
            val['lot_shipment_out'] = r['r_lot_shipment_out']
            val['lot_type'] = r['r_lot_type']
            val['lot_product'] = r['r_lot_product']
            val['lot_quantity'] = r['r_lot_quantity']
            val['lot_matched_qt'] = 0
            val['lot_cp'] = r['r_supplier']
            lp.append(val)
        return lp

class LotMatchingLot(ModelView):
    "Lots"
    __name__ = "lot.matching.lot"

    lms = fields.Many2One('lot.matching.start',"Matching")
    lot_id = fields.Many2One('lot.lot',"Lot")
    lot_r_id = fields.Many2One('lot.qt',"Lot Qt")
    lot_purchase = fields.Many2One('purchase.purchase', "Purchase")
    lot_sale = fields.Many2One('sale.sale', "Sale")
    lot_name = fields.Char("Name",readonly=True)
    lot_type = fields.Selection([
        ('virtual', 'Open'),
        ('physic', 'Physic'),
        ('loss', 'Loss'),
    ], 'Qt type')
    lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In")
    lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal")
    lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
    lot_product = fields.Many2One('product.product',"Product",readonly=True)
    lot_quantity = fields.Numeric("Qt",readonly=True)
    lot_unit = fields.Many2One('product.uom',"Unit",readonly=True)
    lot_matched_qt = fields.Numeric("Qt to match")
    lot_cp = fields.Many2One('party.party',"Counterparty")
    lot_shipment_origin = fields.Function(
        fields.Reference(
            selection=[
                ("stock.shipment.in", "In"),
                ("stock.shipment.out", "Out"),
                ("stock.shipment.internal", "Internal"),
            ],
            string="Shipment",
        ),
        "get_shipment_origin",
    )
    
    def get_shipment_origin(self, name):
        if self.lot_shipment_in:
            return 'stock.shipment.in,' + str(self.lot_shipment_in.id)
        elif self.lot_shipment_out:
            return 'stock.shipment.out,' + str(self.lot_shipment_out.id)
        elif self.lot_shipment_internal:
            return 'stock.shipment.internal,' + str(self.lot_shipment_internal.id)
        return None


class LotUnmatch(Wizard):
    "Unmatch"
    __name__ = "lot.unmatch"

    start = StateTransition()

    def transition_start(self):
        Lot = Pool().get('lot.lot')
        LotQt = Pool().get('lot.qt')
        for r in self.records:
            if r.id > 10000000 :
                lqt = LotQt(r.id - 10000000)
                if lqt.lot_p and lqt.lot_s:
                    lot = lqt.lot_p
                    qt = lqt.lot_quantity
                    #Increase forecasted virtual parts non matched
                    if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,None):
                        lot.createVirtualPart(qt,lqt.lot_shipment_origin,None)
                    if not lot.updateVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale'):
                        lot.createVirtualPart(qt,lqt.lot_shipment_origin,lqt.lot_s,'only sale')
                    #Decrease forecasted virtual part matched
                    logger.info("UNMATCH_QT:%s",qt)
                    lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s)
            else:
                lot = Lot(r.id)
                qt = lot.get_current_quantity_converted()
                # #Increase forecasted virtual part matched
                # if not lot.updateVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s()):
                #     lot.createVirtualPart(qt,lot.lot_shipment_origin,lot.getVlot_s())
                # #Decrease forecasted virtual part non matched
                # if not lot.updateVirtualPart(-qt,lot.lot_shipment_origin,None):
                #     lot.createVirtualPart(-qt,lot.lot_shipment_origin,None)
                ls = lot.getVlot_s()
                ls.lot_quantity += qt
                Lot.save([ls])
                lot.sale_line = None
                Lot.save([lot])
        return 'end'

    def end(self):
        return 'reload'

class LotUnship(Wizard):
    "Unship"
    __name__ = "lot.unship"

    start = StateTransition()
    
    def transition_start(self):
        Lot = Pool().get('lot.lot')
        LotQt = Pool().get('lot.qt')
        Move = Pool().get('stock.move')
        for r in self.records:
            lqt = LotQt(r.id - 10000000)
            lot = Lot(r.id)
            if lqt and r.r_lot_type == 'virtual':
                shipment = None
                qt = lqt.lot_quantity
                mode = 'both'
                if not lqt.lot_p:
                    lot = lqt.lot_s
                    mode = 'only sale'
                else:
                    lot = lqt.lot_p
                if lqt.lot_shipment_in:
                    shipment = lqt.lot_shipment_in
                elif lqt.lot_shipment_internal:
                    shipment = lqt.lot_shipment_internal
                elif lqt.lot_shipment_out:
                    shipment = lqt.lot_shipment_out
                #Decrease forecasted virtual part shipped
                lot.updateVirtualPart(-qt,lqt.lot_shipment_origin,lqt.lot_s,mode)
                #Increase forecasted virtual part non shipped
                if not lot.updateVirtualPart(qt,None,lqt.lot_s,mode):
                    lot.createVirtualPart(qt,None,lqt.lot_s,mode)
            if lot and r.r_lot_type == 'physic':
                lot = Lot(r.r_lot_p)
                if lot.lot_type == 'physic':
                    str_sh = str(lot.lot_shipment_origin)
                    if 'stock.shipment.in' in str_sh:
                        lot.lot_shipment_in = None
                    elif 'stock.shipment.out' in str_sh:
                        lot.lot_shipment_out = None
                    elif 'stock.shipment.internal' in str_sh:
                        lot.lot_shipment_internal = None

                    if lot.move:
                        move = Move(lot.move)
                        move.shipment = None
                        Move.save([move])
                        # lm = LotMove.search([('lot','=',lot.id),('move','=',lot.move)])
                        # if lm:
                        #     LotMove.delete(lm)
                    lot.lot_status = 'forecast'
                    Lot.save([lot])
        return 'end'

    def end(self):
        return 'reload'

class LotAdding(Wizard):
    "Adding physical lots"
    __name__ = "lot.add"

    start = StateTransition()

    add = StateView(
        'lot.add.lot',
        'purchase_trade.add_lot_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Add", 'adding', 'tryton-ok', default=True),
        ])
    
    adding = StateTransition()

    def transition_start(self):
        return 'add'

    def default_add(self, fields):
        unit = None
        quantity = None
        sh_in = None
        sh_int = None
        sh_out = None
        Lot = Pool().get('lot.lot')
        LotQt = Pool().get('lot.qt')
        context = Transaction().context
        ids = context.get('active_ids')
        for i in ids:
            val = {}
            if i > 10000000 :
                lqt = LotQt(i - 10000000)
                if not lqt.lot_p:
                    raise UserError("To add physical lot to sale, use action 'Apply matching'!")
                lot = lqt.lot_p
                unit = lot.lot_unit_line.id
                quantity = lqt.lot_quantity
                sh_in = lqt.lot_shipment_in.id if lqt.lot_shipment_in else None
                sh_int = lqt.lot_shipment_internal.id if lqt.lot_shipment_internal else None
                sh_out = lqt.lot_shipment_out.id if lqt.lot_shipment_out else None
            else:
                raise UserError("Trying to add physical lots to physical lot!")
        return {
            'unit': unit,
            'quantity': quantity,
            'shipment_in':sh_in,
            'shipment_internal':sh_int,
            'shipment_out':sh_out,
            }
        
    def transition_adding(self):
        if self.records:
            LotQt = Pool().get('lot.qt')
            lqt = LotQt(self.records[0].id - 10000000)
            LotQt.add_physical_lots(lqt,self.add.lots)
        return 'end'
    
    def end(self):
        return 'reload'

class LotAddLot(ModelView):
    "Add physical lots"
    __name__ = "lot.add.lot"
    unit = fields.Many2One('product.uom',"Unit",readonly=True)
    quantity = fields.Numeric("Quantity available",digits='unit',readonly=True)
    shipment_in = fields.Many2One('stock.shipment.in',"Shipment In",states={'invisible': ~Eval('shipment_in')})
    shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal",states={'invisible': ~Eval('shipment_internal')})
    shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out",states={'invisible': ~Eval('shipment_out')})
    lots = fields.One2Many('lot.add.line','lal',"Lots")

class LotAddLine(ModelView):
    "Lots"
    __name__ = "lot.add.line"

    lal = fields.Many2One('lot.add.lot',"Lots")
    lot_qt = fields.Numeric("Quantity")
    lot_unit = fields.Many2One('product.uom', "Unit",required=True)
    lot_quantity = fields.Numeric("Net weight")
    lot_gross_quantity = fields.Numeric("Gross weight")
    lot_unit_line = fields.Many2One('product.uom', "Unit",required=True)
    lot_premium = fields.Numeric("Premium")

    # @fields.depends('lot_qt')
    # def on_change_with_lot_quantity(self):
    #     if not self.lot_quantity:
    #         return self.lot_qt

    # @fields.depends('lot_qt')
    # def on_change_with_lot_gross_quantity(self):
    #     if not self.lot_gross_quantity:
    #         return self.lot_qt

    # @fields.depends('lot_unit')
    # def on_change_with_lot_unit_line(self):
    #     if not self.lot_unit_line:
    #         return self.lot_unit
    

class LotImporting(Wizard):
    "Importing physical lots"
    __name__ = "lot.import"

    start = StateTransition()

    imp = StateView(
        'lot.import.lot',
        'purchase_trade.import_lot_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Import", 'importing', 'tryton-ok', default=True),
        ])
    
    importing = StateTransition()

    def transition_start(self):
        return 'imp'
        
    def transition_importing(self):
        if self.records:
            line = self.records[0].r_line
            Lot = Pool().get('lot.lot')
            LotQt = Pool().get('lot.qt')
            LotQtHist = Pool().get('lot.qt.hist')
            LotQtType = Pool().get('lot.qt.type')
            lqt = LotQt(self.records[0].id - 10000000)
            tot_qt = 0
            for l in self.imp.lots:
                l.line = line
                l.lot_shipment_in = self.records[0].r_lot_shipment_in
                l.lot_shipment_internal = self.records[0].r_lot_shipment_internal
                l.lot_shipment_out = self.records[0].r_lot_shipment_out
                l.lot_product = self.records[0].r_lot_product
                l.sale_line = self.records[0].r_sale_line
                l.lot_type = 'physic'
                premium = Decimal(0)
                if l.lot_price:
                    premium = l.lot_price - l.line.unit_price
                    logger.info("IMPORTLOT:%s",l.line.linked_price)
                    if l.line.linked_price:
                        premium = l.lot_price - l.line.linked_price
                l.lot_premium = premium
                l.lot_premium_sup = premium
                Uom = Pool().get('product.uom')
                net_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_quantity), line.unit)),5)
                gross_qt = round(Decimal(Uom.compute_qty(l.lot_unit_line, float(l.lot_gross_quantity), line.unit)),5)
                lqtt = LotQtType.search([('sequence','=',1)])
                if lqtt:
                    lqh = LotQtHist()
                    lqh.quantity_type = lqtt[0]
                    lqh.quantity = l.lot_quantity
                    lqh.gross_quantity = l.lot_gross_quantity
                    l.lot_hist = [lqh]
                Lot.save([l])
                tot_qt += net_qt
                logger.info("IMPORTING:%s",net_qt)
                #need to decrease sale virtual lot if matching
                if self.records[0].r_lot_s:
                    ls = Lot(self.records[0].r_lot_s)
                    ls.lot_quantity -= net_qt
                    ls.lot_gross_quantity -= net_qt
                    ls.lot_qt -= float(net_qt)
                    Lot.save([ls])
            lqt.lot_quantity -= tot_qt
            if lqt.lot_quantity < 0:
                lqt.lot_quantity = 0
            LotQt.save([lqt])
        return 'end'
    
    def end(self):
        return 'reload'

class LotImportLot(ModelView):
    "Import physical lots"
    __name__ = "lot.import.lot"

    reference = fields.Char("Reference")
    lots = fields.One2Many('lot.lot','',"Lots")

    @fields.depends('reference')
    def on_change_with_lots(self, name=None):
        lp = []
        LotFCR = Pool().get('lot.fcr')
        if self.reference:
            lfcrs = LotFCR.search([('lot_offer','=',self.reference)])
            for r in lfcrs:
                val = {}
                if r.lot_product:
                    val['lot_product'] = r.lot_product.id
                val['lot_qt'] = r.lot_qt
                val['lot_quantity'] = r.lot_quantity
                if r.lot_unit:
                    val['lot_unit'] = r.lot_unit.id
                if r.lot_unit_line:
                    val['lot_unit_line'] = r.lot_unit_line.id
                val['lot_gross_quantity'] = r.lot_gross_quantity
                val['lot_premium'] = r.lot_premium
                val['lot_price'] = r.lot_price
                lp.append(val)
            return lp

class LotRemove(Wizard):
    "Remove lots"
    __name__ = "lot.remove"

    start = StateTransition()
    
    def transition_start(self):
        Lot = Pool().get('lot.lot')
        Move = Pool().get('stock.move')
        PL = Pool().get('purchase.line')
        for r in self.records:
            if r.id > 10000000 :
                raise UserError("Trying to remove open lots!")
            if r.r_lot_s:
                raise UserError("Trying to remove matched lots!")
            if r.r_lot_shipment_in or r.r_lot_shipment_internal or r.r_lot_shipment_out:
                raise UserError("Trying to remove shipped lots!")
            lot = Lot(r.id)
            #delete move first if exist
            if lot.move:
                Move.delete([lot.move])
            if not lot.updateVirtualPart(lot.get_current_quantity_converted(),None,None):
                lot.createVirtualPart(lot.get_current_quantity_converted(),None,None)
            Lot.delete([lot])
        return 'end'

    def end(self):
        return 'reload'

class LotInvoice(Wizard):
    "Invoice lots"
    __name__ = "lot.invoice"

    start = StateTransition()

    inv = StateView(
        'lot.invoice.start',
        'purchase_trade.invoice_lot_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Invoice", 'invoicing', 'tryton-ok', default=True),
        ])
    
    invoicing = StateTransition()

    def transition_start(self):
        return 'inv'
    
    def default_inv(self, fields):
        lot_p = []
        lot_s = []
        fee_pur = []
        pp_pur = []
        fee_sale = []
        pp_sale = []
        act = 'prov'
        line = None
        sale_line = None
        val = {}
        Lot = Pool().get('lot.lot')
        LotQt = Pool().get('lot.qt')
        context = Transaction().context
        ids = context.get('active_ids')
        unit = None
        for i in ids:
            val = {}
            lqts = LotQt.search(['id','=',i - 10000000])
            if lqts:
                lot = lqts[0].lot_p
            else:
                lot = Lot(i)
            line = lot.line
            if lot.line.purchase:
                if lot.line.purchase.wb:
                    if lot.line.purchase.wb.qt_type in [e.quantity_type for e in lot.lot_hist]:
                        act = 'final'
            val['lot'] = lot.id
            val['lot_name'] = lot.lot_name
            val['lot_state'] = lot.lot_state.id
            val['lot_pur_inv_state'] = lot.lot_pur_inv_state
            val['lot_sale_inv_state'] = lot.lot_sale_inv_state
            val['lot_diff_quantity'] = Decimal(0)
            val['lot_diff_price'] = Decimal(0)
            val['lot_diff_amount'] = Decimal(0)
            val['lot_quantity'] = lot.get_current_quantity_converted()
            val['lot_price'] = lot.lot_price
            val['lot_amount'] = lot.get_current_quantity_converted() * lot.lot_price if lot.lot_price else Decimal(0)
            if lot.invoice_line_prov:
                val['lot_diff_quantity'] = val['lot_quantity'] - Decimal(lot.invoice_line_prov.quantity)
                val['lot_diff_price'] = val['lot_price'] - Decimal(lot.invoice_line_prov.unit_price)
                val['lot_diff_amount'] = val['lot_amount'] - Decimal(lot.invoice_line_prov.amount)
            val['lot_unit'] = lot.lot_unit_line.id
            unit = val['lot_unit']
            val['lot_currency'] = lot.lot_price_ct_symbol
            lot_p.append(val)
            sale_line = lot.sale_line
            val_s = val.copy()  # ou utiliser deepcopy si certains champs sont des objets imbriqués
            val_s['lot_price'] = lot.lot_price_sale
            val_s['lot_amount'] = lot.get_current_quantity_converted() * lot.lot_price_sale if lot.lot_price_sale else Decimal(0)
            val_s['lot_diff_quantity'] = 0
            val_s['lot_diff_price'] = 0
            val_s['lot_diff_amount'] = 0
            if lot.sale_invoice_line_prov:
                val_s['lot_diff_quantity'] = val_s['lot_quantity'] - Decimal(lot.sale_invoice_line_prov.quantity)
                val_s['lot_diff_price'] = val_s['lot_price'] - Decimal(lot.sale_invoice_line_prov.unit_price)
                val_s['lot_diff_amount'] = val_s['lot_amount'] - Decimal(lot.sale_invoice_line_prov.amount)
            val_s['lot_currency'] = lot.lot_price_ct_symbol_sale
            lot_s.append(val_s)
        if line:
            if line.fees:
                for f in line.fees:
                    if f.type == 'ordered':
                        val = {}
                        val['fee'] = f.id
                        val['fee_type'] = f.product.id
                        val['fee_quantity'] = f.quantity
                        val['fee_price'] = f.price
                        val['fee_unit'] = line.unit.id
                        val['fee_amount'] = f.amount
                        val['fee_landed_cost'] = f.fee_landed_cost
                        fee_pur.append(val)
            if line.purchase:
                Prep = Pool().get('account.invoice')
                prep = Prep.search([('description','=','Prepayment'),('party','=',line.purchase.party.id),('state','!=','draft')])
                if prep:
                    for p in prep:
                        for li in p.lines:
                            val = {}
                            val['inv'] = li.id
                            val['inv_amount'] = li.amount
                            pp_pur.append(val)
        if sale_line:
            if sale_line.fees:
                for f in sale_line.fees:
                    if f.type == 'ordered':
                        val = {}
                        val['fee'] = f.id
                        val['fee_type'] = f.product.id
                        val['fee_quantity'] = f.quantity
                        val['fee_price'] = f.price
                        val['fee_unit'] = sale_line.unit.id
                        val['fee_amount'] = f.amount
                        val['fee_landed_cost'] = f.fee_landed_cost
                        fee_sale.append(val)
            if sale_line.sale:
                Prep = Pool().get('account.invoice')
                prep = Prep.search([('description','=','Prepayment'),('party','=',sale_line.sale.party.id),('state','!=','draft')])
                if prep:
                    for p in prep:
                        for li in p.lines:
                            val = {}
                            val['inv'] = li.id
                            val['inv_amount'] = li.amount
                            pp_sale.append(val)
        return {
            'lot_p': lot_p,
            'lot_s': lot_s,
            'unit': unit,
            'fee_pur': fee_pur,
            'pp_pur': pp_pur,
            'fee_sale': fee_sale,
            'pp_sale': pp_sale,
            'action': act
            }

    def transition_invoicing(self):
        Lot = Pool().get('lot.lot')
        Purchase = Pool().get('purchase.purchase')
        Sale = Pool().get('sale.sale')
        lots = []
        action = self.inv.action
        for r in self.records:
            purchase = r.r_line.purchase
            sale = None
            if r.r_sale_line:
                sale = r.r_sale_line.sale
            lot = Lot(r.r_lot_p)
            # if lot.move == None:
            #     Warning = Pool().get('res.user.warning')   
            #     warning_name = Warning.format("Lot not confirmed", [])
            #     if Warning.check(warning_name):
            #         raise QtWarning(warning_name,
            #                     "Lot not confirmed, click yes to confirm and invoice")
            #         continue
            if lot.invoice_line:
                continue
            lots.append(lot)

        if self.inv.type == 'purchase':
            Purchase._process_invoice([purchase],lots,action,self.inv.pp_pur)
        else:
            if sale:
                Sale._process_invoice([sale],lots,action,self.inv.pp_sale)      
        return 'end'

    def end(self):
        return 'reload'

class LotInvoiceStart(ModelView):
    "Invoice physical lots"
    __name__ = "lot.invoice.start"

    invoice = fields.Many2One('account.invoice',"Invoice to add lines")
    lot_p = fields.One2Many('lot.invoicing.lot','lis',"Lots",states={'invisible': Eval('type')=='sale'})
    lot_s = fields.One2Many('lot.invoicing.lot','lis',"Lots",states={'invisible': Eval('type')=='purchase'})
    fee_sale = fields.One2Many('lot.invoicing.fee','lfs',"Fees",states={'invisible': Eval('type')=='purchase'})
    pp_sale = fields.One2Many('lot.invoicing.inv','lps',"Prepayments",states={'invisible': Eval('type')=='purchase'})
    fee_pur = fields.One2Many('lot.invoicing.fee','lfs',"Fees",states={'invisible': Eval('type')=='sale'})
    pp_pur = fields.One2Many('lot.invoicing.inv','lps',"Prepayments",states={'invisible': Eval('type')=='sale'})
    unit = fields.Many2One('product.uom',"Unit",readonly=True)
    action = fields.Selection([
        ('add', 'Add lines'),
        ('final', 'Final'),
        ('prov', 'Provisional'),
    ], 'Action', required=True)

    type = fields.Selection([
        ('purchase', 'Purchase'),
        ('sale', 'Sale'),
    ], 'Type', required=True)
    
    quantity = fields.Numeric("Quantity to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='sale'})
    amount = fields.Numeric("Amount to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='sale'})
    quantity_s = fields.Numeric("Quantity to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})
    amount_s = fields.Numeric("Amount to invoice",digits='unit',readonly=True,states={'invisible': Eval('type')=='purchase'})

    @fields.depends('type')
    def on_change_with_action(self, name=None):
        if self.lot_p and self.type == 'purchase':
            if self.lot_p[0].lot.line.purchase.wb.qt_type in [e.quantity_type for e in self.lot_p[0].lot.lot_hist]:
                return 'final'
            else:
                return 'prov'
        if self.lot_s and self.type == 'sale':
            if self.lot_s[0].lot.sale_line.sale.wb.qt_type in [e.quantity_type for e in self.lot_s[0].lot.lot_hist]:
                return 'final'
            else:
                return 'prov'
            
    @fields.depends('lot_p','type','quantity')
    def on_change_with_quantity(self, name=None):
        if self.lot_p and self.type == 'purchase':
            quantity = Decimal(0)
            for l in self.lot_p:
                if l.lot.invoice_line_prov:
                    quantity += l.lot_diff_quantity
                else:
                    quantity += l.lot_quantity
            return quantity

    @fields.depends('lot_p','type','amount')
    def on_change_with_amount(self, name=None):
        if self.lot_p and self.type == 'purchase':
            amount = Decimal(0)
            for l in self.lot_p:
                if l.lot.invoice_line_prov:
                    amount += l.lot_diff_amount
                else:
                    amount += l.lot_amount
            return amount

    @fields.depends('lot_s','type','quantity_s')
    def on_change_with_quantity_s(self, name=None):
        if self.lot_s and self.type == 'sale':
            quantity = Decimal(0)
            for l in self.lot_s:
                if l.lot.sale_invoice_line_prov:
                    quantity += l.lot_diff_quantity
                else:
                    quantity += l.lot_quantity
            return quantity

    @fields.depends('lot_s','type','amount_s')
    def on_change_with_amount_s(self, name=None):
        if self.lot_s and self.type == 'sale':
            amount = Decimal(0)
            for l in self.lot_s:
                if l.lot.sale_invoice_line_prov:
                    amount += l.lot_diff_amount
                else:
                    amount += l.lot_amount
            return amount

    @classmethod
    def default_action(cls):
        return 'prov'

    @classmethod
    def default_type(cls):
        return 'purchase'

class LotInvoicingFee(ModelView):
    "Fees"
    __name__ = "lot.invoicing.fee"

    lfs = fields.Many2One('lot.invoice.start',"Invoicing")
    fee = fields.Many2One('fee.fee',"Fee")
    fee_type = fields.Many2One('product.product',"Fee type")
    to_invoice = fields.Boolean("To invoice")
    fee_unit = fields.Many2One('product.uom',"Unit",readonly=True)
    fee_quantity = fields.Numeric("Qt",digits=(1,5),readonly=True)
    fee_price = fields.Numeric("Price",digits=(1,4),readonly=True)
    fee_amount = fields.Numeric("Amount",digits=(1,2),readonly=True)
    fee_landed_cost = fields.Boolean("To inventory",readonly=True)

class LotInvoicingInv(ModelView):
    "Fees"
    __name__ = "lot.invoicing.inv"

    lps = fields.Many2One('lot.invoice.start',"Invoicing")
    inv = fields.Many2One('account.invoice.line',"Invoice lines")
    inv_amount = fields.Numeric("Av.amount",digits=(1,2),readonly=True)
    inv_amount_to_use = fields.Numeric("Amount to use",digits=(1,2))

class LotInvoicingLot(ModelView):
    "Lots"
    __name__ = "lot.invoicing.lot"

    lis = fields.Many2One('lot.invoice.start',"Invoicing")
    lot = fields.Many2One('lot.lot',"Lot")
    lot_name = fields.Char("Name",readonly=True)
    lot_state = fields.Many2One('lot.qt.type',"State")
    lot_quantity = fields.Numeric("Qt",digits='unit',readonly=True)
    lot_diff_quantity = fields.Numeric("Diff. qt",digits='unit',readonly=True)
    lot_unit = fields.Many2One('product.uom',"Unit",readonly=True)
    lot_price = fields.Numeric("Price",digits='unit',readonly=True)
    lot_diff_price = fields.Numeric("Diff. price",digits='unit',readonly=True)
    lot_currency = fields.Char("Curr")
    lot_amount = fields.Numeric("Amt",digits='unit',readonly=True)
    lot_diff_amount = fields.Numeric("Diff amt",digits='unit',readonly=True)
    lot_pur_inv_state = fields.Selection([
        (None, ''),
        ('prov', 'Prov'),
        ('invoiced', 'Final')
    ], 'Pur. inv')
    lot_sale_inv_state = fields.Selection([
        (None, ''),
        ('prov', 'Prov'),
        ('invoiced', 'Final')
    ], 'Sale inv')

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree/field[@name="lot_diff_price"]', 'visual',
                If(Eval('lot_diff_price') & (Eval('lot_diff_price', 0) < 0),'danger','success')),
            ('/tree/field[@name="lot_diff_quantity"]', 'visual',
                If(Eval('lot_diff_quantity') & (Eval('lot_diff_quantity', 0) < 0),'danger','success')),
            ]

class LotFCR(ModelSQL, ModelView):
    "Lot FCR"
    __name__ = 'lot.fcr'
    _rec_name = 'lot_name'

    lot_tare = fields.Numeric("Tare")
    lot_grade = fields.Char("Grade")
    lot_staple = fields.Char("Staple")
    lot_mic = fields.Char("Mic")
    lot_gpt = fields.Char("Gpt")
    lot_uhm = fields.Char("Uhm")
    lot_color = fields.Char("Color")
    lot_counterparty = fields.Many2One('party.party','Supplier')
    lot_offer_date = fields.Date("Offer date")
    lot_offer =fields.Char("Reference")
    lot_product = fields.Many2One('product.product','Product')
    lot_origin = fields.Many2One('country.country','Origin')
    lot_name = fields.Char("Lot")
    lot_qt = fields.Integer("Qt")
    lot_price = fields.Numeric("Price",digits=(1,4))
    lot_amount = fields.Numeric("Amount",digits=(1,2))
    lot_premium = fields.Numeric("Prem/Disc", digits=(1,2))
    lot_quantity = fields.Numeric("Net Qt",digits=(1,5),required=True)
    lot_unit = fields.Many2One('product.uom', "Unit",required=True)
    lot_unit_line = fields.Many2One('product.uom', "Base unit",required=True)
    lot_gross_quantity = fields.Numeric("Gross Qt",digits=(1,5))
    lot_linked = fields.Many2One('lot.lot', "Linked lot")
    lot_create = fields.Boolean("Create")

class LotWeighing(Wizard):
    "Weighing"
    __name__ = "lot.weighing"

    start = StateTransition()

    w = StateView(
        'lot.weighing.start',
        'purchase_trade.weighing_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Do", 'weighing', 'tryton-ok', default=True),
        ])
    
    weighing = StateTransition()

    def transition_start(self):
        return 'w'
        
    def default_w(self, fields):
        lot_p = []
        val = {}
        Lot = Pool().get('lot.lot')
        context = Transaction().context
        ids = context.get('active_ids')
        for i in ids:
            if i > 10000000:
                raise UserError("Trying to do weighing on open quantity!")
            val = {}
            lot = Lot(i)
            val['lot'] = lot.id
            val['lot_name'] = lot.lot_name
            if lot.lot_shipment_in:
                val['lot_shipment_in'] = lot.lot_shipment_in.id
            if lot.lot_shipment_internal:
                val['lot_shipment_internal'] = lot.lot_shipment_internal.id
            if lot.lot_shipment_out:
                val['lot_shipment_out'] = lot.lot_shipment_out.id
            val['lot_product'] = lot.lot_product.id
            val['lot_quantity'] = lot.lot_quantity
            val['lot_gross_quantity'] = lot.lot_gross_quantity
            val['lot_unit'] = lot.lot_unit.id
            val['lot_unit_line'] = lot.lot_unit_line.id
            lot_p.append(val)
        return {
            'lot_p': lot_p,
            }

    def transition_weighing(self):
        Warning = Pool().get('res.user.warning')  
        FeeLots = Pool().get('fee.lots') 
        Lot = Pool().get('lot.lot')
        LotHist = Pool().get('lot.qt.hist') 
        for l in self.w.lot_p:
            quantity = l.lot.get_current_quantity_converted()
            lhs = LotHist.search([('lot',"=",l.lot.id),('quantity_type','=',self.w.lot_state.id)])
            if lhs:
                lh = lhs[0]
            else: 
                lh = LotHist()
            lh.lot = l.lot
            lh.quantity_type = self.w.lot_state
            lh.quantity = round(l.lot_quantity_new,5)
            lh.gross_quantity = round(l.lot_gross_quantity_new,5)
            LotHist.save([lh])
            
            if self.w.lot_update_state :
                l.lot.lot_state = self.w.lot_state
            Lot.save([l.lot])
            diff = round(Decimal(l.lot.get_current_quantity_converted() - quantity),5)
            if diff != 0 :
                #need to update virtual part
                l.lot.updateVirtualPart(-diff,l.lot.lot_shipment_origin,l.lot.getVlot_s())
            #adjuts fee ordered with new quantity
            fees = FeeLots.search(['lot','=',l.id])
            for f in fees:
                f.fee.adjust_purchase_values()
            
        return 'end'
    
    def end(self):
        return 'reload'

class LotWeighingStart(ModelView):
    "Add weighing"
    __name__ = "lot.weighing.start"
    lot_p = fields.One2Many('lot.weighing.lot','lws',"Lots")
    lot_state = fields.Many2One('lot.qt.type',"Weighing type",required=True)
    lot_update_state = fields.Boolean("Update lot state to this weighing")

    @classmethod
    def default_lot_update_state(cls):
        return True

class LotWeighingLot(ModelView):
    "Lots"
    __name__ = "lot.weighing.lot"

    lws = fields.Many2One('lot.weighing.start',"Weighing")
    lot = fields.Many2One('lot.lot',"Lot")
    lot_name = fields.Char("Name",readonly=True)
    lot_shipment_in = fields.Many2One('stock.shipment.in',"Shipment In")
    lot_shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal")
    lot_shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out")
    lot_product = fields.Many2One('product.product',"Product",readonly=True)
    lot_quantity = fields.Numeric("Net weight",digits=(1,5),readonly=True)
    lot_gross_quantity = fields.Numeric("Gross weight",digits=(1,5),readonly=True)
    lot_unit = fields.Many2One('product.uom',"Unit",readonly=True)
    lot_unit_line = fields.Many2One('product.uom',"Unit",readonly=True)
    lot_quantity_new = fields.Numeric("New net weight",digits=(1,5))
    lot_gross_quantity_new = fields.Numeric("New gross weight",digits=(1,5))
    lot_shipment_origin = fields.Function(
        fields.Reference(
            selection=[
                ("stock.shipment.in", "In"),
                ("stock.shipment.out", "Out"),
                ("stock.shipment.internal", "Internal"),
            ],
            string="Shipment",
        ),
        "get_shipment_origin",
    )
    
    def get_shipment_origin(self, name):
        if self.lot_shipment_in:
            return 'stock.shipment.in,' + str(self.lot_shipment_in.id)
        elif self.lot_shipment_out:
            return 'stock.shipment.out,' + str(self.lot_shipment_out.id)
        elif self.lot_shipment_internal:
            return 'stock.shipment.internal,' + str(self.lot_shipment_internal.id)
        return None

class CreateContracts(Wizard):
    "Create Contracts"
    __name__ = "create.contracts"

    start = StateTransition()

    ct = StateView(
        'contracts.start',
        'purchase_trade.contracts_view_form', [
        Button("Cancel", 'end', 'tryton-cancel'),
        Button("Create", 'creating', 'tryton-ok', default=True),
        ])
    
    creating = StateTransition()

    #matching = StateAction('purchase_trade.matching_unit')

    def transition_start(self):
        return 'ct'
        
    def default_ct(self, fields):
        LotQt = Pool().get('lot.qt')
        Lot = Pool().get('lot.lot')
        context = Transaction().context
        ids = context.get('active_ids')
        unit = None
        product = None
        sh_in = None
        sh_int = None
        sh_out = None
        lot = None
        qt = None
        type = None
        for i in ids:
            val = {}
            if i < 10000000:
                raise UserError("You must create contract from an open quantity !")
            l = LotQt(i - 10000000)
            ll = Lot(l.lot_p if l.lot_p else l.lot_s)
            type = "Sale" if l.lot_p else "Purchase"
            unit = l.lot_unit.id
            qt = l.lot_quantity
            product = ll.lot_product.id
            sh_in = l.lot_shipment_in.id if l.lot_shipment_in else None
            sh_int = l.lot_shipment_internal.id if l.lot_shipment_internal else None
            sh_out = l.lot_shipment_out.id if l.lot_shipment_out else None
            lot = ll.id
            
        return {
            'quantity': qt,
            'unit': unit,
            'product': product,
            'shipment_in': sh_in,
            'shipment_internal': sh_int,
            'shipment_out': sh_out,
            'lot': lot,
            'type': type,
            }

    def transition_creating(self):
        SaleLine = Pool().get('sale.line')
        Sale = Pool().get('sale.sale')
        PurchaseLine = Pool().get('purchase.line')
        Purchase = Pool().get('purchase.purchase')
        LotQt = Pool().get('lot.qt')
        LotQtHist = Pool().get('lot.qt.hist')
        LotQtType = Pool().get('lot.qt.type')
        Lot = Pool().get('lot.lot')
        Date = Pool().get('ir.date')
        self.sale_lines = []
        type = self.ct.type
        base_contract = self.ct.lot.sale_line.sale if type == 'Purchase' else self.ct.lot.line.purchase
        for c in self.ct.contracts:
            contract = Purchase() if type == 'Purchase' else Sale()
            contract_line = PurchaseLine() if type == 'Purchase' else SaleLine()
            parts = c.currency_unit.split("_")
            if int(parts[0]) != 0:
                contract.currency = int(parts[0])
            else:
                contract.currency = 1
            contract.party = c.party
            contract.crop = c.crop
            contract.tol_min = c.tol_min
            contract.tol_max = c.tol_max
            if type == 'Purchase':
                contract.purchase_date = Date.today()
            else:
                contract.sale_date = Date.today()
            contract.reference = c.reference
            if base_contract.from_location and base_contract.to_location:
                if type == 'Purchase':
                    contract.to_location = base_contract.from_location
                else:
                    contract.from_location = base_contract.to_location
                if base_contract.from_location.type == 'supplier' and base_contract.to_location.type == 'customer':
                    contract.from_location = base_contract.from_location
                    contract.to_location = base_contract.to_location
            if c.party.wb:
                contract.wb = c.party.wb
            if c.party.association:
                contract.association = c.party.association
            if type == 'Purchase':
                if c.party.supplier_payment_term:
                    contract.payment_term = c.party.supplier_payment_term
            else:
                if c.party.customer_payment_term:
                    contract.payment_term = c.party.customer_payment_term
            contract.incoterm = c.incoterm
            if c.party.addresses:
                contract.invoice_address = c.party.addresses[0]
                if type == 'Sale':
                    contract.shipment_address = c.party.addresses[0]
            contract.__class__.save([contract])
            contract_line.quantity = c.quantity
            contract_line.quantity_theorical = c.quantity
            contract_line.product = self.ct.product
            contract_line.price_type = c.price_type
            contract_line.unit = self.ct.unit
            if type == 'Purchase':
                contract_line.purchase = contract.id
            else:
                contract_line.sale = contract.id
            contract_line.created_by_code = self.ct.matched
            contract_line.premium = Decimal(0)
            if int(parts[0]) == 0:
                contract_line.enable_linked_currency = True
                contract_line.linked_currency = 1
                contract_line.linked_unit = int(parts[1])
                contract_line.linked_price = c.price
                contract_line.unit_price = contract_line.get_price_linked_currency()
            else:
                contract_line.unit_price = c.price if c.price else Decimal(0)
            contract_line.del_period = c.del_period
            contract_line.from_del = c.from_del
            contract_line.to_del = c.to_del
            contract_line.__class__.save([contract_line])
            logger.info("CREATE_ID:%s",contract.id)
            logger.info("CREATE_LINE_ID:%s",contract_line.id)
            if self.ct.matched:
                lot = Lot()
                if type == 'Purchase':
                    lot.line = contract_line.id
                else:
                    lot.sale_line = contract_line.id
                lot.lot_qt = None
                lot.lot_unit = None
                lot.lot_unit_line = contract_line.unit
                lot.lot_quantity = round(contract_line.quantity,5)
                lot.lot_gross_quantity = None
                lot.lot_status = 'forecast'
                lot.lot_type = 'virtual'
                lot.lot_product = contract_line.product
                lqtt = LotQtType.search([('sequence','=',1)])
                if lqtt:
                    lqh = LotQtHist()
                    lqh.quantity_type = lqtt[0]
                    lqh.quantity = round(lot.lot_quantity,5)
                    lqh.gross_quantity = round(lot.lot_quantity,5)
                    lot.lot_hist = [lqh]
                Lot.save([lot])
                vlot = self.ct.lot
                shipment_origin = None
                if self.ct.shipment_in: 
                    shipment_origin =  'stock.shipment.in,' + str(self.ct.shipment_in.id)
                elif self.ct.shipment_internal: 
                    shipment_origin =  'stock.shipment.internal,' + str(self.ct.shipment_internal.id)
                elif self.ct.shipment_out: 
                    shipment_origin =  'stock.shipment.out,' + str(self.ct.shipment_out.id)

                qt = c.quantity
                if type == 'Purchase':
                    if not lot.updateVirtualPart(qt,shipment_origin,vlot):
                        lot.createVirtualPart(qt,shipment_origin,vlot)
                    #Decrease forecasted virtual part non matched
                    lot.updateVirtualPart(-qt,shipment_origin,vlot,'only sale')
                else:
                    if not vlot.updateVirtualPart(qt,shipment_origin,lot):
                        vlot.createVirtualPart(qt,shipment_origin,lot)
                    #Decrease forecasted virtual part non matched
                    vlot.updateVirtualPart(-qt,shipment_origin,None)


        return 'end'
    
    # def do_matching(self, action):
    #     return action, {
    #         'ids': self.sale_lines,
    #         'model': str(self.ct.lot.id),
    #         }

    def end(self):
        return 'reload'

class ContractsStart(ModelView):
    "Create Contracts"
    __name__ = "contracts.start"
    type = fields.Char("Type of contract to create",readonly=True)
    matched = fields.Boolean("Matched")
    product = fields.Many2One('product.product',"Product",readonly=True)
    unit = fields.Many2One('product.uom',"Unit",readonly=True)
    quantity = fields.Numeric("Current Qt",digits='unit',readonly=True)
    shipment_in = fields.Many2One('stock.shipment.in',"Shipment In",readonly=True,states={'invisible': ~Eval('shipment_in')})
    shipment_internal = fields.Many2One('stock.shipment.internal',"Shipment Internal",readonly=True,states={'invisible': ~Eval('shipment_internal')})
    shipment_out = fields.Many2One('stock.shipment.out',"Shipment Out",readonly=True,states={'invisible': ~Eval('shipment_out')})
    # shipment_origin = fields.Reference(
    #         selection=[
    #             ("stock.shipment.in", "In"),
    #             ("stock.shipment.out", "Out"),
    #             ("stock.shipment.internal", "Internal"),
    #         ],
    #         string="Shipment",
    #     )
    lot = fields.Many2One('lot.lot',"Lot",readonly=True)#,states={'invisible': Eval('lqt')})
    contracts = fields.One2Many('contract.detail','cd',"Contracts")

    @classmethod
    def default_matched(cls):
        return True
    

class ContractDetail(ModelView):

    "Contract Detail"
    __name__ = "contract.detail"

    category = fields.Integer("Category")
    cd = fields.Many2One('contracts.start',"Contracts")
    party = fields.Many2One('party.party',"Party",domain=[('categories.parent', 'child_of', Eval('category'))],depends=['category'])
    currency = fields.Many2One('currency.currency',"Currency")
    incoterm = fields.Many2One('incoterm.incoterm',"Incoterm")
    quantity = fields.Numeric("Quantity",digits=(1,5))
    unit = fields.Many2One('product.uom',"Unit")
    qt_unit = fields.Many2One('product.uom',"Unit")
    tol_min = fields.Numeric("Tol - in %")
    tol_max = fields.Numeric("Tol + in %")
    crop = fields.Many2One('purchase.crop',"Crop")
    del_period = fields.Many2One('product.month',"Delivery Period")
    from_del = fields.Date("From")
    to_del = fields.Date("To")
    price = fields.Numeric("Price",digits=(1,4),states={'invisible': Eval('price_type') != 'priced'})
    price_type = price_type = fields.Selection([
        ('cash', 'Cash Price'),
        ('priced', 'Priced'),
        ('basis', 'Basis'),
    ], 'Price type')
    currency_unit = fields.Selection('get_currency_unit',string="Curr/Unit")
    reference = fields.Char("Reference")

    @classmethod
    def default_category(cls):
        lqt = cls._get_lqt_from_context()
        if lqt and lqt.lot_p:
            return 1
        else:
            return 2

    @staticmethod
    def get_currency_unit():
        Currency = Pool().get('currency.currency')
        Uom = Pool().get('product.uom')

        result = []
        currencies = Currency.search([('concatenate','=',True)])
        units = Uom.search([('concatenate','=',True)])
        result.append(("0_5","USC/lb"))
        result.append(("0_37","USC/mt"))
        for c in currencies:
            for u in units:
                key = "%s_%s" % (c.id, u.id)
                value = "%s / %s" % (c.name, u.symbol or u.name)
                result.append((key, value))
        return result
    
    @classmethod
    def default_price_type(cls):
        return 'priced'
    
    @classmethod
    def _get_lqt_from_context(cls):
        ctx = Transaction().context
        active_id = ctx.get('active_id') or next(iter(ctx.get('active_ids', [])), None)
        if not active_id:
            return None
        LotQt = Pool().get('lot.qt')
        try:
            return LotQt(active_id - 10000000)
        except Exception:
            return None
    
    @classmethod
    def default_reference(cls):
        lqt = cls._get_lqt_from_context()
        if lqt and lqt.lot_p and getattr(lqt.lot_p.line.purchase, 'reference', None):
            return lqt.lot_p.line.purchase.reference
        if lqt and lqt.lot_s and getattr(lqt.lot_s.sale_line.sale, 'reference', None):
            return lqt.lot_s.sale_line.sale.reference

    @classmethod
    def default_crop(cls):
        lqt = cls._get_lqt_from_context()
        if lqt and lqt.lot_p and getattr(lqt.lot_p.line.purchase, 'crop', None):
            return lqt.lot_p.line.purchase.crop.id
        if lqt and lqt.lot_s and getattr(lqt.lot_s.sale_line.sale, 'crop', None):
            return lqt.lot_s.line.sale.crop.id

    @classmethod
    def default_currency(cls):
        lqt = cls._get_lqt_from_context()
        if not lqt:
            return
        line = lqt.lot_p.line if lqt.lot_p else lqt.lot_s.sale_line
        if getattr(line, 'enable_linked_currency', False):
            return line.linked_currency.id
        return getattr(line.purchase.currency if lqt.lot_p else line.sale.currency, 'id', None)

    @classmethod
    def default_unit(cls):
        lqt = cls._get_lqt_from_context()
        if not lqt:
            return
        line = lqt.lot_p.line if lqt.lot_p else lqt.lot_s.sale_line
        if getattr(line, 'enable_linked_currency', False):
            return line.linked_unit.id
        return getattr(line.unit, 'id', None)

    @classmethod
    def default_tol_min(cls):
        lqt = cls._get_lqt_from_context()
        return getattr(lqt.lot_p.line.purchase if lqt.lot_p else lqt.lot_s.sale_line.sale, 'tol_min', None) if lqt else None

    @classmethod
    def default_tol_max(cls):
        lqt = cls._get_lqt_from_context()
        return getattr(lqt.lot_p.line.purchase if lqt.lot_p else lqt.lot_s.sale_line.sale, 'tol_max', None) if lqt else None

    @classmethod
    def default_del_period(cls):
        lqt = cls._get_lqt_from_context()
        if lqt and getattr(lqt.lot_p.line.purchase if lqt.lot_p else lqt.lot_s.sale_line.sale, 'del_period', None):
            return lqt.lot_p.line.purchase.del_period.id if lqt.lot_p else lqt.lot_s.sale_line.sale.del_period.id
    
    @fields.depends('del_period')
    def on_change_del_period(self):
        if self.del_period:
            self.from_del = self.del_period.beg_date
            self.to_del = self.del_period.end_date
    