3407 lines
140 KiB
Python
Executable File
3407 lines
140 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",ondelete='CASCADE')
|
|
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")
|
|
lot_chunk_key = fields.Integer("Chunk key")
|
|
#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
|
|
newlot.lot_chunk_key = l.lot_chunk_key
|
|
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,finished=False):
|
|
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))
|
|
if not finished:
|
|
wh &= (pl.finished == False)
|
|
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 not finished:
|
|
wh &= (sl.finished == False)
|
|
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(
|
|
(((lqt.lot_s != None) & (lqt.lot_p == None) & (sl.id > 0)),
|
|
sl.del_period),
|
|
else_=Case((pl.id>0, pl.del_period),else_=None)
|
|
).as_('r_del_period'),
|
|
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"),
|
|
pl.del_period.as_("r_del_period"),
|
|
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(pl.del_period).as_("r_del_period"),
|
|
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_del_period.as_("r_del_period"),
|
|
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_del_period = fields.Many2One('product.month', "Delivery Period")
|
|
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')
|
|
finished = context.get('finished')
|
|
query = LotQt.getQuery(purchase,sale,shipment,type,state,None,supplier,None,None,wh,group,product,location,origin,finished)
|
|
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')
|
|
|
|
finished = fields.Boolean("Display finished")
|
|
|
|
@classmethod
|
|
def default_finished(cls):
|
|
return False
|
|
|
|
@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(str(r.r_lot_quantity)).quantize(Decimal("0.00001"))
|
|
logger.info("LotShipping:%s",shipped_quantity)
|
|
shipment_origin = None
|
|
if self.ship.quantity:
|
|
shipped_quantity = self.ship.quantity
|
|
if shipped_quantity == 0:
|
|
shipped_quantity = Decimal(str(r.r_lot_matched)).quantize(Decimal("0.00001"))
|
|
if self.ship.shipment == 'in':
|
|
if not self.ship.shipment_in:
|
|
UserError("Shipment not known!")
|
|
shipment_origin = 'stock.shipment.in,'+str(self.ship.shipment_in.id)
|
|
elif self.ship.shipment == 'out':
|
|
if not self.ship.shipment_out:
|
|
UserError("Shipment not known!")
|
|
shipment_origin = 'stock.shipment.out,'+str(self.ship.shipment_out.id)
|
|
elif self.ship.shipment == 'int':
|
|
if not self.ship.shipment_internal:
|
|
UserError("Shipment not known!")
|
|
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])
|
|
l.set_current_quantity(l.lot_quantity,l.lot_gross_quantity,2)
|
|
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):
|
|
logger.info("LotShipping2:%s",shipped_quantity)
|
|
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")
|
|
lot_chunk_key = fields.Integer("Chunk key")
|
|
|
|
# @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()
|
|
|
|
message = StateView(
|
|
'purchase.create_prepayment.message',
|
|
'purchase_trade.create_prepayment_message_form',
|
|
[
|
|
Button('OK', 'end', 'tryton-ok'),
|
|
Button('See Invoice', 'see_invoice', 'tryton-go-next'),
|
|
]
|
|
)
|
|
|
|
see_invoice = StateAction('account_invoice.act_invoice_form')
|
|
|
|
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'] = line.unit.id #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
|
|
val_s['lot_unit'] = sale_line.unit.id if sale_line else None
|
|
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 = []
|
|
purchases = []
|
|
sales = []
|
|
action = self.inv.action
|
|
for r in self.records:
|
|
purchase = r.r_line.purchase if r.r_line else None
|
|
sale = r.r_sale_line.sale if r.r_sale_line else None
|
|
if purchase and purchase not in purchases:
|
|
purchases.append(purchase)
|
|
if sale and sale not in sales:
|
|
sales.append(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)
|
|
|
|
invoice_line = None
|
|
if self.inv.type == 'purchase':
|
|
Purchase._process_invoice(purchases, lots, action, self.inv.pp_pur)
|
|
for lot in lots:
|
|
lot = Lot(lot.id)
|
|
invoice_line = lot.invoice_line or lot.invoice_line_prov
|
|
if invoice_line:
|
|
break
|
|
else:
|
|
if sales:
|
|
Sale._process_invoice(sales, lots, action, self.inv.pp_sale)
|
|
for lot in lots:
|
|
lot = Lot(lot.id)
|
|
invoice_line = lot.sale_invoice_line or lot.sale_invoice_line_prov
|
|
if invoice_line:
|
|
break
|
|
if not invoice_line:
|
|
raise UserError("No invoice line was generated from the selected lots.")
|
|
self.message.invoice = invoice_line.invoice
|
|
|
|
return 'message'
|
|
|
|
def default_message(self, fields):
|
|
return {
|
|
'message': 'The invoice has been successfully created.',
|
|
}
|
|
|
|
def do_see_invoice(self, action):
|
|
action['views'].reverse() # pour ouvrir en form directement
|
|
logger.info("*************SEE_INVOICE******************:%s",self.message.invoice)
|
|
return action, {'res_id':self.message.invoice.id}
|
|
|
|
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_qt'] = lot.lot_qt
|
|
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])
|
|
l.lot.lot_qt = l.lot_qt
|
|
|
|
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_qt = fields.Integer("Qt")
|
|
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 = Decimal(0)
|
|
type = None
|
|
shipment_in_values = set()
|
|
shipment_internal_values = set()
|
|
shipment_out_values = set()
|
|
for i in ids:
|
|
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)
|
|
current_type = "Sale" if l.lot_p else "Purchase"
|
|
if type and current_type != type:
|
|
raise UserError("You must select open quantities from the same side.")
|
|
type = current_type
|
|
if product and ll.lot_product.id != product:
|
|
raise UserError("You must select open quantities with the same product.")
|
|
if unit and l.lot_unit.id != unit:
|
|
raise UserError("You must select open quantities with the same unit.")
|
|
unit = l.lot_unit.id
|
|
qt += abs(Decimal(str(l.lot_quantity or 0)))
|
|
product = ll.lot_product.id
|
|
shipment_in_values.add(l.lot_shipment_in.id if l.lot_shipment_in else None)
|
|
shipment_internal_values.add(
|
|
l.lot_shipment_internal.id if l.lot_shipment_internal else None)
|
|
shipment_out_values.add(l.lot_shipment_out.id if l.lot_shipment_out else None)
|
|
if lot is None:
|
|
lot = ll.id
|
|
|
|
if len(shipment_in_values) == 1:
|
|
sh_in = next(iter(shipment_in_values))
|
|
if len(shipment_internal_values) == 1:
|
|
sh_int = next(iter(shipment_internal_values))
|
|
if len(shipment_out_values) == 1:
|
|
sh_out = next(iter(shipment_out_values))
|
|
|
|
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,
|
|
)
|
|
return 'end'
|
|
|
|
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", required=True,domain=[('categories.parent', 'child_of', Eval('category'))],depends=['category'])
|
|
currency = fields.Many2One('currency.currency',"Currency", required=True)
|
|
incoterm = fields.Many2One('incoterm.incoterm',"Incoterm", required=True)
|
|
quantity = fields.Numeric("Quantity",digits=(1,5), required=True)
|
|
unit = fields.Many2One('product.uom',"Unit", required=True)
|
|
qt_unit = fields.Many2One('product.uom',"Unit")
|
|
tol_min = fields.Numeric("Tol - in %", required=True)
|
|
tol_max = fields.Numeric("Tol + in %", required=True)
|
|
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", required=True,digits=(1,4),states={'invisible': Eval('price_type') != 'priced'})
|
|
price_type = price_type = fields.Selection([
|
|
('cash', 'Cash Price'),
|
|
('priced', 'Priced'),
|
|
('basis', 'Basis'),
|
|
], 'Price type', required=True)
|
|
currency_unit = fields.Selection('get_currency_unit',string="Curr/Unit")
|
|
reference = fields.Char("Reference")
|
|
from_location = fields.Many2One('stock.location',"From location")
|
|
to_location = fields.Many2One('stock.location',"To location")
|
|
payment_term = fields.Many2One('account.invoice.payment_term',"Payment Term", required=True)
|
|
|
|
@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
|
|
|