Files
tradon/modules/purchase_trade/lot.py
2026-01-26 17:32:33 +01:00

3436 lines
142 KiB
Python
Executable File

# 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