From 4c70f0bc5f875232f4892bf1d81e9871bef30e39 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Mon, 26 Jan 2026 12:42:49 +0100 Subject: [PATCH] 26.01.26 --- modules/automation/automation.py | 31 ++++ modules/purchase_trade/__init__.py | 1 + modules/purchase_trade/lot.py | 245 +++++++++++++++-------------- modules/purchase_trade/service.py | 215 +++++++++++++++++++++++++ 4 files changed, 372 insertions(+), 120 deletions(-) create mode 100644 modules/purchase_trade/service.py diff --git a/modules/automation/automation.py b/modules/automation/automation.py index 936f57b..dbb7db1 100644 --- a/modules/automation/automation.py +++ b/modules/automation/automation.py @@ -2,6 +2,8 @@ from trytond.model import ModelSQL, ModelView, fields, Workflow from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval from trytond.wizard import Button +from trytond.transaction import Transaction +from sql import Table import requests import io import logging @@ -203,6 +205,35 @@ class AutomationDocument(ModelSQL, ModelView, Workflow): ShipmentWR.save([swr]) doc.notes = (doc.notes or "") + f"Shipment found: {sh[0].number}\n" + t = Table('freight_booking_lots') + cursor = Transaction().connection.cursor() + cursor.execute(*t.select( + t.BOOKING_NUMBER, + t.LOT_NUMBER, + t.LOT_NBR_BALES, + t.LOT_GROSS_WEIGHT, + t.LOT_NET_WEIGHT, + t.LOT_UOM, + t.LOT_QUALITY, + t.CUSTOMER, + t.SELL_PRICE_CURRENCY, + t.SELL_PRICE_UNIT, + t.SALE_INVOICE, + t.SELL_INV_AMOUNT, + t.SALE_INVOICE_DATE, + t.SELL_PREMIUM, + t.SALE_CONTRACT, + where=(t.BOOKING_NUMBER == sh[0].bl_number) + )) + + rows = cursor.fetchall() + if rows: + #Purchase & Sale creation + Purchase = Pool().get('purchase.purchase') + PurchaseLine = Pool().get('purchase.line') + for row in rows: + #Lots creation + pass # if cls.rule_set.ocr_required:[] # cls.run_ocr([doc]) # if cls.rule_set.structure_required and doc.state != "error": diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index 99e5b2a..22f4c31 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -29,6 +29,7 @@ from . import ( credit_risk, valuation, weight_report, + service, ) def register(): diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index d0a2fa6..526417e 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -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' diff --git a/modules/purchase_trade/service.py b/modules/purchase_trade/service.py new file mode 100644 index 0000000..0159e12 --- /dev/null +++ b/modules/purchase_trade/service.py @@ -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