This commit is contained in:
2026-03-26 15:43:45 +01:00
parent f9010ddefd
commit 3e5320cf9e
4 changed files with 201 additions and 9 deletions

View File

@@ -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/<module>/` + 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 `&quot;` et `&apos;` 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)

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:officeooo="http://openoffice.org/2009/office" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta>
<dc:title>Invoice no</dc:title>
<dc:title>Provisional Sale</dc:title>
<meta:initial-creator>willen</meta:initial-creator>
<meta:creation-date>2018-12-09T16:20:00</meta:creation-date>
<dc:date>2026-03-23T20:46:35.300000000</dc:date>
@@ -3839,7 +3839,7 @@
<table:table-row table:style-name="Tableau3.1">
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
<text:p text:style-name="P20" />
<text:p text:style-name="P20">Invoice N°</text:p>
<text:p text:style-name="P20">Provisional Sale</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
<text:p text:style-name="P20" />
@@ -3889,8 +3889,8 @@
<text:p text:style-name="P14">Port of loading</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau4.A1" office:value-type="string">
<text:p text:style-name="P14">14.06.2025</text:p>
<text:p text:style-name="P14">SANTOS, BRAZIL</text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;format_date(invoice.report_bl_date, invoice.party.lang) if invoice.report_bl_date else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;invoice.report_loading_port&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
<table:table-row table:style-name="Tableau4.1">
@@ -3898,7 +3898,7 @@
<text:p text:style-name="P14">Port of discharge</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau4.A1" office:value-type="string">
<text:p text:style-name="P14">PORT QASIM, PAKISTAN</text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;invoice.report_discharge_port&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
</table:table>
@@ -3915,12 +3915,12 @@
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;invoice.report_product_description&gt;</text:placeholder> CROP <text:placeholder text:placeholder-type="text">&lt;invoice.report_crop_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P19"><text:placeholder text:placeholder-type="text">&lt;invoice.report_attributes_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P30">H.S CODE 5201.0090</text:p>
<text:p text:style-name="P30">CFR PORT QASIM, PAKISTAN </text:p>
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text">&lt;invoice.report_incoterm&gt;</text:placeholder></text:p>
<text:p text:style-name="P27">
<text:span text:style-name="T1">ALL DETAILS AND SPECIFICATIONS AS PER</text:span>
<text:span text:style-name="T3"> BENEFICIARY’S </text:span>
</text:p>
<text:p text:style-name="P24">PROFORMA INVOICE NO. 1411-1 DATED 20-05-2025.</text:p>
<text:p text:style-name="P24">PROFORMA INVOICE NO. <text:placeholder text:placeholder-type="text">&lt;invoice.report_proforma_invoice_number&gt;</text:placeholder> DATED <text:placeholder text:placeholder-type="text">&lt;format_date(invoice.report_proforma_invoice_date, invoice.party.lang) if invoice.report_proforma_invoice_date else &apos;&apos;&gt;</text:placeholder>.</text:p>
<text:p text:style-name="P24" />
<text:p text:style-name="P22" />
<text:p text:style-name="P34" />
@@ -4063,9 +4063,9 @@
<text:p text:style-name="P13">Controller Name</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau10.A1" office:value-type="string">
<text:p text:style-name="P23">S/BR/55</text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P23" />
<text:p text:style-name="P23">INTERTEK </text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
<table:table-row table:style-name="Tableau10.1">

View File

@@ -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:
- `&quot;...&quot;` pour les guillemets doubles
- `&apos;...&apos;` pour les apostrophes
- Eviter les formes avec antislashs:
- interdit: `\'\'`
- interdit: `\'value\'`
- Exemples corrects:
- `&lt;replace text:p=&quot;set_lang(invoice.party.lang)&quot;&gt;`
- `&lt;if test=&quot;invoice.report_payment_description&quot;&gt;`
- `&lt;tax.description or &apos;&apos;&gt;`
### 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 `&quot;` / `&apos;`.
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`

View File

@@ -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 ''