This commit is contained in:
2026-01-26 12:42:49 +01:00
parent ef27c020e0
commit 4c70f0bc5f
4 changed files with 372 additions and 120 deletions

View File

@@ -29,6 +29,7 @@ from . import (
credit_risk,
valuation,
weight_report,
service,
)
def register():

View File

@@ -20,6 +20,7 @@ import datetime
import json
import logging
from trytond.exceptions import UserWarning, UserError
from purchase_trade.service import ContractFactory
logger = logging.getLogger(__name__)
@@ -3142,127 +3143,131 @@ class CreateContracts(Wizard):
}
def transition_creating(self):
SaleLine = Pool().get('sale.line')
Sale = Pool().get('sale.sale')
PurchaseLine = Pool().get('purchase.line')
Purchase = Pool().get('purchase.purchase')
LotQt = Pool().get('lot.qt')
LotQtHist = Pool().get('lot.qt.hist')
LotQtType = Pool().get('lot.qt.type')
Lot = Pool().get('lot.lot')
Date = Pool().get('ir.date')
self.sale_lines = []
type = self.ct.type
base_contract = self.ct.lot.sale_line.sale if type == 'Purchase' else self.ct.lot.line.purchase
for c in self.ct.contracts:
contract = Purchase() if type == 'Purchase' else Sale()
contract_line = PurchaseLine() if type == 'Purchase' else SaleLine()
parts = c.currency_unit.split("_")
if int(parts[0]) != 0:
contract.currency = int(parts[0])
else:
contract.currency = 1
contract.party = c.party
contract.crop = c.crop
contract.tol_min = c.tol_min
contract.tol_max = c.tol_max
if type == 'Purchase':
contract.purchase_date = Date.today()
else:
contract.sale_date = Date.today()
contract.reference = c.reference
if base_contract.from_location and base_contract.to_location:
if type == 'Purchase':
contract.to_location = base_contract.from_location
else:
contract.from_location = base_contract.to_location
if base_contract.from_location.type == 'supplier' and base_contract.to_location.type == 'customer':
contract.from_location = base_contract.from_location
contract.to_location = base_contract.to_location
if c.party.wb:
contract.wb = c.party.wb
if c.party.association:
contract.association = c.party.association
if type == 'Purchase':
if c.party.supplier_payment_term:
contract.payment_term = c.party.supplier_payment_term
else:
if c.party.customer_payment_term:
contract.payment_term = c.party.customer_payment_term
contract.incoterm = c.incoterm
if c.party.addresses:
contract.invoice_address = c.party.addresses[0]
if type == 'Sale':
contract.shipment_address = c.party.addresses[0]
contract.__class__.save([contract])
contract_line.quantity = c.quantity
contract_line.quantity_theorical = c.quantity
contract_line.product = self.ct.product
contract_line.price_type = c.price_type
contract_line.unit = self.ct.unit
if type == 'Purchase':
contract_line.purchase = contract.id
else:
contract_line.sale = contract.id
contract_line.created_by_code = self.ct.matched
contract_line.premium = Decimal(0)
if int(parts[0]) == 0:
contract_line.enable_linked_currency = True
contract_line.linked_currency = 1
contract_line.linked_unit = int(parts[1])
contract_line.linked_price = c.price
contract_line.unit_price = contract_line.get_price_linked_currency()
else:
contract_line.unit_price = c.price if c.price else Decimal(0)
contract_line.del_period = c.del_period
contract_line.from_del = c.from_del
contract_line.to_del = c.to_del
contract_line.__class__.save([contract_line])
logger.info("CREATE_ID:%s",contract.id)
logger.info("CREATE_LINE_ID:%s",contract_line.id)
if self.ct.matched:
lot = Lot()
if type == 'Purchase':
lot.line = contract_line.id
else:
lot.sale_line = contract_line.id
lot.lot_qt = None
lot.lot_unit = None
lot.lot_unit_line = contract_line.unit
lot.lot_quantity = round(contract_line.quantity,5)
lot.lot_gross_quantity = None
lot.lot_status = 'forecast'
lot.lot_type = 'virtual'
lot.lot_product = contract_line.product
lqtt = LotQtType.search([('sequence','=',1)])
if lqtt:
lqh = LotQtHist()
lqh.quantity_type = lqtt[0]
lqh.quantity = round(lot.lot_quantity,5)
lqh.gross_quantity = round(lot.lot_quantity,5)
lot.lot_hist = [lqh]
Lot.save([lot])
vlot = self.ct.lot
shipment_origin = None
if self.ct.shipment_in:
shipment_origin = 'stock.shipment.in,' + str(self.ct.shipment_in.id)
elif self.ct.shipment_internal:
shipment_origin = 'stock.shipment.internal,' + str(self.ct.shipment_internal.id)
elif self.ct.shipment_out:
shipment_origin = 'stock.shipment.out,' + str(self.ct.shipment_out.id)
qt = c.quantity
if type == 'Purchase':
if not lot.updateVirtualPart(qt,shipment_origin,vlot):
lot.createVirtualPart(qt,shipment_origin,vlot)
#Decrease forecasted virtual part non matched
lot.updateVirtualPart(-qt,shipment_origin,vlot,'only sale')
else:
if not vlot.updateVirtualPart(qt,shipment_origin,lot):
vlot.createVirtualPart(qt,shipment_origin,lot)
#Decrease forecasted virtual part non matched
vlot.updateVirtualPart(-qt,shipment_origin,None)
ContractFactory.create_contracts(
self.ct.contracts,
type_=self.ct.type,
ct=self.ct,
)
# SaleLine = Pool().get('sale.line')
# Sale = Pool().get('sale.sale')
# PurchaseLine = Pool().get('purchase.line')
# Purchase = Pool().get('purchase.purchase')
# LotQt = Pool().get('lot.qt')
# LotQtHist = Pool().get('lot.qt.hist')
# LotQtType = Pool().get('lot.qt.type')
# Lot = Pool().get('lot.lot')
# Date = Pool().get('ir.date')
# self.sale_lines = []
# type = self.ct.type
# base_contract = self.ct.lot.sale_line.sale if type == 'Purchase' else self.ct.lot.line.purchase
# for c in self.ct.contracts:
# contract = Purchase() if type == 'Purchase' else Sale()
# contract_line = PurchaseLine() if type == 'Purchase' else SaleLine()
# parts = c.currency_unit.split("_")
# if int(parts[0]) != 0:
# contract.currency = int(parts[0])
# else:
# contract.currency = 1
# contract.party = c.party
# contract.crop = c.crop
# contract.tol_min = c.tol_min
# contract.tol_max = c.tol_max
# if type == 'Purchase':
# contract.purchase_date = Date.today()
# else:
# contract.sale_date = Date.today()
# contract.reference = c.reference
# if base_contract.from_location and base_contract.to_location:
# if type == 'Purchase':
# contract.to_location = base_contract.from_location
# else:
# contract.from_location = base_contract.to_location
# if base_contract.from_location.type == 'supplier' and base_contract.to_location.type == 'customer':
# contract.from_location = base_contract.from_location
# contract.to_location = base_contract.to_location
# if c.party.wb:
# contract.wb = c.party.wb
# if c.party.association:
# contract.association = c.party.association
# if type == 'Purchase':
# if c.party.supplier_payment_term:
# contract.payment_term = c.party.supplier_payment_term
# else:
# if c.party.customer_payment_term:
# contract.payment_term = c.party.customer_payment_term
# contract.incoterm = c.incoterm
# if c.party.addresses:
# contract.invoice_address = c.party.addresses[0]
# if type == 'Sale':
# contract.shipment_address = c.party.addresses[0]
# contract.__class__.save([contract])
# contract_line.quantity = c.quantity
# contract_line.quantity_theorical = c.quantity
# contract_line.product = self.ct.product
# contract_line.price_type = c.price_type
# contract_line.unit = self.ct.unit
# if type == 'Purchase':
# contract_line.purchase = contract.id
# else:
# contract_line.sale = contract.id
# contract_line.created_by_code = self.ct.matched
# contract_line.premium = Decimal(0)
# if int(parts[0]) == 0:
# contract_line.enable_linked_currency = True
# contract_line.linked_currency = 1
# contract_line.linked_unit = int(parts[1])
# contract_line.linked_price = c.price
# contract_line.unit_price = contract_line.get_price_linked_currency()
# else:
# contract_line.unit_price = c.price if c.price else Decimal(0)
# contract_line.del_period = c.del_period
# contract_line.from_del = c.from_del
# contract_line.to_del = c.to_del
# contract_line.__class__.save([contract_line])
# logger.info("CREATE_ID:%s",contract.id)
# logger.info("CREATE_LINE_ID:%s",contract_line.id)
# if self.ct.matched:
# lot = Lot()
# if type == 'Purchase':
# lot.line = contract_line.id
# else:
# lot.sale_line = contract_line.id
# lot.lot_qt = None
# lot.lot_unit = None
# lot.lot_unit_line = contract_line.unit
# lot.lot_quantity = round(contract_line.quantity,5)
# lot.lot_gross_quantity = None
# lot.lot_status = 'forecast'
# lot.lot_type = 'virtual'
# lot.lot_product = contract_line.product
# lqtt = LotQtType.search([('sequence','=',1)])
# if lqtt:
# lqh = LotQtHist()
# lqh.quantity_type = lqtt[0]
# lqh.quantity = round(lot.lot_quantity,5)
# lqh.gross_quantity = round(lot.lot_quantity,5)
# lot.lot_hist = [lqh]
# Lot.save([lot])
# vlot = self.ct.lot
# shipment_origin = None
# if self.ct.shipment_in:
# shipment_origin = 'stock.shipment.in,' + str(self.ct.shipment_in.id)
# elif self.ct.shipment_internal:
# shipment_origin = 'stock.shipment.internal,' + str(self.ct.shipment_internal.id)
# elif self.ct.shipment_out:
# shipment_origin = 'stock.shipment.out,' + str(self.ct.shipment_out.id)
# qt = c.quantity
# if type == 'Purchase':
# if not lot.updateVirtualPart(qt,shipment_origin,vlot):
# lot.createVirtualPart(qt,shipment_origin,vlot)
# #Decrease forecasted virtual part non matched
# lot.updateVirtualPart(-qt,shipment_origin,vlot,'only sale')
# else:
# if not vlot.updateVirtualPart(qt,shipment_origin,lot):
# vlot.createVirtualPart(qt,shipment_origin,lot)
# #Decrease forecasted virtual part non matched
# vlot.updateVirtualPart(-qt,shipment_origin,None)
return 'end'

View File

@@ -0,0 +1,215 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
import logging
from trytond.pool import Pool
from trytond.transaction import Transaction
logger = logging.getLogger(__name__)
class ContractFactory:
"""
Factory métier pour créer des Purchase depuis Sale
ou des Sale depuis Purchase.
Compatible :
- Wizard (n contrats)
- Appel direct depuis un modèle (1 contrat)
"""
@classmethod
def create_contracts(cls, contracts, *, type_, ct):
"""
:param contracts: iterable de contracts (wizard lines)
:param type_: 'Purchase' ou 'Sale'
:param ct: objet contenant le contexte (lot, product, unit, matched...)
:return: liste des contracts créés
"""
pool = Pool()
Sale = pool.get('sale.sale')
Purchase = pool.get('purchase.purchase')
SaleLine = pool.get('sale.line')
PurchaseLine = pool.get('purchase.line')
Date = pool.get('ir.date')
created = []
base_contract = (
ct.lot.sale_line.sale
if type_ == 'Purchase'
else ct.lot.line.purchase
)
for c in contracts:
contract = Purchase() if type_ == 'Purchase' else Sale()
line = PurchaseLine() if type_ == 'Purchase' else SaleLine()
# ---------- CONTRACT ----------
parts = c.currency_unit.split("_")
contract.currency = int(parts[0]) or 1
contract.party = c.party
contract.crop = c.crop
contract.tol_min = c.tol_min
contract.tol_max = c.tol_max
contract.reference = c.reference
if type_ == 'Purchase':
contract.purchase_date = Date.today()
else:
contract.sale_date = Date.today()
cls._apply_locations(contract, base_contract, type_)
cls._apply_party_data(contract, c.party, type_)
cls._apply_payment_term(contract, c.party, type_)
contract.incoterm = c.incoterm
if c.party.addresses:
contract.invoice_address = c.party.addresses[0]
if type_ == 'Sale':
contract.shipment_address = c.party.addresses[0]
contract.save()
# ---------- LINE ----------
line.quantity = c.quantity
line.quantity_theorical = c.quantity
line.product = ct.product
line.unit = ct.unit
line.price_type = c.price_type
line.created_by_code = ct.matched
line.premium = Decimal(0)
if type_ == 'Purchase':
line.purchase = contract.id
else:
line.sale = contract.id
cls._apply_price(line, c, parts)
line.del_period = c.del_period
line.from_del = c.from_del
line.to_del = c.to_del
line.save()
logger.info("CREATE_ID:%s", contract.id)
logger.info("CREATE_LINE_ID:%s", line.id)
if ct.matched:
cls._create_lot(line, c, ct, type_)
created.append(contract)
return created
# -------------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------------
@staticmethod
def _apply_locations(contract, base, type_):
if not (base.from_location and base.to_location):
return
if type_ == 'Purchase':
contract.to_location = base.from_location
else:
contract.from_location = base.to_location
if (base.from_location.type == 'supplier'
and base.to_location.type == 'customer'):
contract.from_location = base.from_location
contract.to_location = base.to_location
@staticmethod
def _apply_party_data(contract, party, type_):
if party.wb:
contract.wb = party.wb
if party.association:
contract.association = party.association
@staticmethod
def _apply_payment_term(contract, party, type_):
if type_ == 'Purchase' and party.supplier_payment_term:
contract.payment_term = party.supplier_payment_term
elif type_ == 'Sale' and party.customer_payment_term:
contract.payment_term = party.customer_payment_term
@staticmethod
def _apply_price(line, c, parts):
if int(parts[0]) == 0:
line.enable_linked_currency = True
line.linked_currency = 1
line.linked_unit = int(parts[1])
line.linked_price = c.price
line.unit_price = line.get_price_linked_currency()
else:
line.unit_price = c.price if c.price else Decimal(0)
# -------------------------------------------------------------------------
# LOT / MATCHING (repris tel quel du wizard)
# -------------------------------------------------------------------------
@classmethod
def _create_lot(cls, line, c, ct, type_):
pool = Pool()
Lot = pool.get('lot.lot')
LotQtHist = pool.get('lot.qt.hist')
LotQtType = pool.get('lot.qt.type')
lot = Lot()
if type_ == 'Purchase':
lot.line = line.id
else:
lot.sale_line = line.id
lot.lot_qt = None
lot.lot_unit = None
lot.lot_unit_line = line.unit
lot.lot_quantity = round(line.quantity, 5)
lot.lot_gross_quantity = None
lot.lot_status = 'forecast'
lot.lot_type = 'virtual'
lot.lot_product = line.product
lqtt = LotQtType.search([('sequence', '=', 1)])
if lqtt:
lqh = LotQtHist()
lqh.quantity_type = lqtt[0]
lqh.quantity = round(lot.lot_quantity, 5)
lqh.gross_quantity = round(lot.lot_quantity, 5)
lot.lot_hist = [lqh]
lot.save()
vlot = ct.lot
shipment_origin = cls._get_shipment_origin(ct)
qt = c.quantity
if type_ == 'Purchase':
if not lot.updateVirtualPart(qt, shipment_origin, vlot):
lot.createVirtualPart(qt, shipment_origin, vlot)
# Decrease forecasted virtual part non matched
lot.updateVirtualPart(-qt, shipment_origin, vlot, 'only sale')
else:
if not vlot.updateVirtualPart(qt, shipment_origin, lot):
vlot.createVirtualPart(qt, shipment_origin, lot)
# Decrease forecasted virtual part non matched
vlot.updateVirtualPart(-qt, shipment_origin, None)
@staticmethod
def _get_shipment_origin(ct):
if ct.shipment_in:
return 'stock.shipment.in,%s' % ct.shipment_in.id
if ct.shipment_internal:
return 'stock.shipment.internal,%s' % ct.shipment_internal.id
if ct.shipment_out:
return 'stock.shipment.out,%s' % ct.shipment_out.id
return None