From 3e5320cf9e6f6b1e3c75455db1770f27e7222188 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 26 Mar 2026 15:43:45 +0100 Subject: [PATCH] 26.03.26 --- AGENTS.md | 5 + modules/account_invoice/invoice_ict.fodt | 18 +-- modules/purchase_trade/docs/template-rules.md | 109 ++++++++++++++++++ modules/purchase_trade/invoice.py | 78 +++++++++++++ 4 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 modules/purchase_trade/docs/template-rules.md diff --git a/AGENTS.md b/AGENTS.md index 25138c5..75d3235 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,11 @@ Guide rapide pour les agents qui codent dans ce repository. - Lire `wsgi.py`, `rpc.py`, `protocols/*`, `tests/test_rpc.py`, `tests/test_wsgi.py`. - Si bug metier: - Modifier uniquement `modules//` + ses tests. +- Si bug template Relatorio (`.fodt`): + - Lire d'abord le template standard voisin du meme domaine (`invoice.fodt`, `sale.fodt`, etc.). + - Preferer des proprietes Python simples exposees par le modele plutot que des expressions Genshi complexes dans le template. + - Dans les placeholders XML, utiliser `"` et `'` plutot que des antislashs type `\'`. + - Si un document facture depend fortement d'une vente/achat, ajouter au besoin un petit pont Python pour exposer des `report_*` stables au template. ## 5) Workflow de modification (obligatoire) diff --git a/modules/account_invoice/invoice_ict.fodt b/modules/account_invoice/invoice_ict.fodt index 6b89804..c596d44 100644 --- a/modules/account_invoice/invoice_ict.fodt +++ b/modules/account_invoice/invoice_ict.fodt @@ -1,7 +1,7 @@  - Invoice no + Provisional Sale willen 2018-12-09T16:20:00 2026-03-23T20:46:35.300000000 @@ -3839,7 +3839,7 @@ - Invoice N° + Provisional Sale @@ -3889,8 +3889,8 @@ Port of loading - 14.06.2025 - SANTOS, BRAZIL + <format_date(invoice.report_bl_date, invoice.party.lang) if invoice.report_bl_date else ''> + <invoice.report_loading_port> @@ -3898,7 +3898,7 @@ Port of discharge - PORT QASIM, PAKISTAN + <invoice.report_discharge_port> @@ -3915,12 +3915,12 @@ <invoice.report_product_description> CROP <invoice.report_crop_name> <invoice.report_attributes_name> H.S CODE 5201.0090 - CFR PORT QASIM, PAKISTAN + <invoice.report_incoterm> ALL DETAILS AND SPECIFICATIONS AS PER BENEFICIARY’S - PROFORMA INVOICE NO. 1411-1 DATED 20-05-2025. + PROFORMA INVOICE NO. <invoice.report_proforma_invoice_number> DATED <format_date(invoice.report_proforma_invoice_date, invoice.party.lang) if invoice.report_proforma_invoice_date else ''>. @@ -4063,9 +4063,9 @@ Controller Name - S/BR/55 + <invoice.report_si_number> - INTERTEK + <invoice.report_controller_name> diff --git a/modules/purchase_trade/docs/template-rules.md b/modules/purchase_trade/docs/template-rules.md new file mode 100644 index 0000000..885fd88 --- /dev/null +++ b/modules/purchase_trade/docs/template-rules.md @@ -0,0 +1,109 @@ +# Template Rules - Purchase Trade + +Statut: `draft` +Version: `v0.1` +Derniere mise a jour: `2026-03-26` + +## 1) Scope + +- Domaine: `templates Relatorio .fodt` +- Modules concernes: + - `purchase_trade` + - `sale` + - `account_invoice` + +## 2) Objectif + +- Eviter les erreurs de parsing Relatorio/Genshi lors de la generation des documents. +- Standardiser la maniere d'alimenter les templates metier a partir du code Python. + +## 3) Regles pratiques + +### TR-001 - Toujours partir du template standard voisin + +- Avant de modifier un template metier (`invoice_ict.fodt`, `sale_ict.fodt`, etc.), comparer avec le template standard du module source: + - `modules/account_invoice/invoice.fodt` + - `modules/sale/sale.fodt` +- Reprendre en priorite la syntaxe Relatorio deja validee dans ces templates. + +### TR-002 - Eviter les expressions Genshi trop complexes dans le `.fodt` + +- Preferer des proprietes Python simples exposees par le modele. +- Le template doit consommer au maximum des champs ou proprietes du type: + - `record.report_address` + - `record.report_price` + - `record.report_payment_date` +- Si un template a besoin de donnees issues d'un autre modele lie, creer un petit pont Python. + +### TR-003 - Regles de syntaxe XML/Relatorio dans les placeholders + +- Dans un `text:placeholder`, utiliser: + - `"..."` pour les guillemets doubles + - `'...'` pour les apostrophes +- Eviter les formes avec antislashs: + - interdit: `\'\'` + - interdit: `\'value\'` +- Exemples corrects: + - `<replace text:p="set_lang(invoice.party.lang)">` + - `<if test="invoice.report_payment_description">` + - `<tax.description or ''>` + +### TR-004 - Pour une facture issue d'une vente, preferer un pont `account.invoice -> sale` + +- Si le template facture doit reutiliser la logique de la pro forma vente, ne pas dupliquer les calculs directement dans le `.fodt`. +- Ajouter plutot dans `purchase_trade` une extension `account.invoice` avec des proprietes `report_*` qui relaient vers `invoice.sales[0]`. +- Exemple de proprietes utiles: + - `report_address` + - `report_contract_number` + - `report_shipment` + - `report_product_description` + - `report_crop_name` + - `report_attributes_name` + - `report_price` + - `report_payment_date` + - `report_nb_bale` + - `report_gross` + - `report_net` + - `report_lbs` + +### TR-005 - Reutiliser les proprietes existantes du module `purchase_trade.sale` + +- Avant d'ajouter une nouvelle logique pour un template vente ou facture issue d'une vente, verifier si une propriete existe deja sur `sale.sale`. +- Proprietes deja utiles: + - `report_terms` + - `report_gross` + - `report_net` + - `report_qt` + - `report_nb_bale` + - `report_deal` + - `report_packing` + - `report_price` + - `report_delivery` + - `report_payment_date` + - `report_shipment` + +## 4) Workflow recommande pour corriger un template en erreur + +1. Identifier le placeholder exact qui provoque l'erreur Relatorio. +2. Comparer sa syntaxe avec le template standard equivalent. +3. Remplacer les guillemets/quotes non valides par `"` / `'`. +4. Si l'expression devient trop longue, la deplacer dans une propriete Python `report_*`. +5. Ne modifier que les placeholders necessaires. +6. Regenerer le document pour verifier la prochaine erreur eventuelle. + +## 5) Cas documentes dans ce repo + +### Invoice ICT + +- Fichier: `modules/account_invoice/invoice_ict.fodt` +- Strategie retenue: + - aligner la syntaxe sur `modules/account_invoice/invoice.fodt` + - reutiliser au maximum les proprietes de `sale.sale` + - exposer dans `modules/purchase_trade/invoice.py` des proprietes de pont `account.invoice -> sale` + +### Sale ICT + +- Fichier: `modules/sale/sale_ict.fodt` +- Usage: + - reference principale pour les champs metier proches d'une pro forma / facture commerciale + - source de verite pratique pour les placeholders `report_*` issus de `purchase_trade.sale` diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py index bf4b5ff..48de10d 100644 --- a/modules/purchase_trade/invoice.py +++ b/modules/purchase_trade/invoice.py @@ -7,6 +7,8 @@ class Invoice(metaclass=PoolMeta): __name__ = 'account.invoice' def _get_report_sale(self): + # Bridge invoice templates to the originating sale so FODT files can + # reuse stable sale.report_* properties instead of complex expressions. sales = list(self.sales or []) return sales[0] if sales else None @@ -15,6 +17,24 @@ class Invoice(metaclass=PoolMeta): if sale and sale.lines: return sale.lines[0] + def _get_report_lot(self): + line = self._get_report_sale_line() + if line and line.lots: + for lot in line.lots: + if lot.lot_type == 'physic': + return lot + return line.lots[0] + + def _get_report_shipment(self): + lot = self._get_report_lot() + if not lot: + return None + return ( + getattr(lot, 'lot_shipment_in', None) + or getattr(lot, 'lot_shipment_out', None) + or getattr(lot, 'lot_shipment_internal', None) + ) + @property def report_address(self): sale = self._get_report_sale() @@ -111,3 +131,61 @@ class Invoice(metaclass=PoolMeta): if net == '': return '' return Decimal(net) * Decimal('2.20462') + + @property + def report_bl_date(self): + shipment = self._get_report_shipment() + if shipment: + return shipment.bl_date + + @property + def report_loading_port(self): + shipment = self._get_report_shipment() + if shipment and shipment.from_location: + return shipment.from_location.rec_name + return '' + + @property + def report_discharge_port(self): + shipment = self._get_report_shipment() + if shipment and shipment.to_location: + return shipment.to_location.rec_name + return '' + + @property + def report_incoterm(self): + sale = self._get_report_sale() + if not sale: + return '' + incoterm = sale.incoterm.code if sale.incoterm else '' + location = sale.incoterm_location.party_name if sale.incoterm_location else '' + if incoterm and location: + return f"{incoterm} {location}" + return incoterm or location + + @property + def report_proforma_invoice_number(self): + lot = self._get_report_lot() + if lot and lot.sale_invoice_line_prov and lot.sale_invoice_line_prov.invoice: + return lot.sale_invoice_line_prov.invoice.number or '' + return '' + + @property + def report_proforma_invoice_date(self): + lot = self._get_report_lot() + if lot and lot.sale_invoice_line_prov and lot.sale_invoice_line_prov.invoice: + return lot.sale_invoice_line_prov.invoice.invoice_date + + @property + def report_controller_name(self): + shipment = self._get_report_shipment() + if shipment and shipment.controller: + return shipment.controller.rec_name + return '' + + @property + def report_si_number(self): + shipment = self._get_report_shipment() + if shipment: + return shipment.number or '' + return ''