# 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 from trytond.modules.purchase_trade.service import ContractFactory 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): ContractFactory.create_contracts( self.ct.contracts, type_=self.ct.type, ct=self.ct, ) # 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.sale_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