Compare commits

..

26 Commits

Author SHA1 Message Date
AzureAD\SylvainDUVERNAY
7f2e61c059 Record market price import backlog commit 2026-05-07 15:02:44 +02:00
AzureAD\SylvainDUVERNAY
eaae2e5b40 Document rules and enforce price value uniqueness 2026-05-07 15:02:15 +02:00
AzureAD\SylvainDUVERNAY
8fb6d681a0 Import Prices 2026-05-06 14:47:57 +02:00
AzureAD\SylvainDUVERNAY
17934fe210 Increase import prices result height 2026-05-06 14:31:48 +02:00
AzureAD\SylvainDUVERNAY
ef54b5d94b Import Prices 2026-05-06 14:21:51 +02:00
AzureAD\SylvainDUVERNAY
3a0ea871c2 ITSA - Import Prices 2026-05-05 16:43:29 +02:00
AzureAD\SylvainDUVERNAY
1c8abc7c1e Trade Finance 2026-04-12 17:22:25 +02:00
AzureAD\SylvainDUVERNAY
37bf6ba23b Trade Finance 2026-04-12 17:15:08 +02:00
AzureAD\SylvainDUVERNAY
2e649aa61b Commit 2026-04-12 17:06:26 +02:00
AzureAD\SylvainDUVERNAY
735a72d23e Trade Finance 2026-04-12 16:58:45 +02:00
AzureAD\SylvainDUVERNAY
ebf9b6c495 Trade Finance 2026-04-12 16:47:59 +02:00
AzureAD\SylvainDUVERNAY
13d26ac41b Trade Finance 2026-04-12 16:36:08 +02:00
AzureAD\SylvainDUVERNAY
b829b11791 Trade Finance - Commit 2026-04-12 14:41:12 +02:00
AzureAD\SylvainDUVERNAY
4a056ef402 Change on Facility definition 2026-04-07 21:53:10 +02:00
AzureAD\SylvainDUVERNAY
da65da79c0 Trade Finance - Facility - Limit ordering 2026-03-31 22:06:58 +02:00
AzureAD\SylvainDUVERNAY
aa6b3fb9ad Trade Finance - TF Facility - Order limits 2026-03-31 21:55:34 +02:00
AzureAD\SylvainDUVERNAY
b1dd118628 Trade Finance - Facility - Improve Limits table layout 2026-03-31 21:39:56 +02:00
AzureAD\SylvainDUVERNAY
f1f9d157cc Trade Finance - Facility - Fix error on Save 2026-03-31 16:50:45 +02:00
AzureAD\SylvainDUVERNAY
283b71fda9 Trade Finance - Sub Limit 2026-03-31 14:14:05 +02:00
AzureAD\SylvainDUVERNAY
f27dd5620e Trade Finance - Adjustment on sub-limit 2026-03-31 14:10:43 +02:00
AzureAD\SylvainDUVERNAY
143f59c62e Trade Finance - Facility Fix 3 2026-03-31 13:37:42 +02:00
AzureAD\SylvainDUVERNAY
be6b6517a5 Trade Finance - Facility Fix 2 2026-03-31 13:34:31 +02:00
AzureAD\SylvainDUVERNAY
d96973310b Trade Finance Facility - Fix 1 2026-03-31 13:31:33 +02:00
AzureAD\SylvainDUVERNAY
39278c4483 Trade Finance - Facility 2026-03-31 13:22:18 +02:00
AzureAD\SylvainDUVERNAY
4534ad86f1 Trade Finance - Fix 2026-03-31 12:24:17 +02:00
AzureAD\SylvainDUVERNAY
10e8e5be9b Trade Finance - Initial Commit 2026-03-31 12:14:51 +02:00
434 changed files with 7920 additions and 112268 deletions

View File

@@ -0,0 +1,17 @@
{
"permissions": {
"allow": [
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules)",
"Bash(ls -d */)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade)",
"Bash(ls -1d */)",
"Bash(for f:*)",
"Bash(do echo:*)",
"Read(//c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/**)",
"Bash(done)",
"Bash(cd /c/Users/SylvainDUVERNAY/Documents/Visual Studio Code/Tradon DEV/tradon/modules/purchase_trade/view)",
"Bash(ls -1 *.xml)",
"Bash(py --version)"
]
}
}

5
.gitignore vendored
View File

@@ -1,3 +1,2 @@
*.pyc
~$*
notes/accounting/excel_web_api/.env
deployment/vps-TradonDev_Instructions.md
deployment/vps/46.202.173.47-credentials.md

View File

@@ -38,55 +38,15 @@ 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.
- Conventions de champs dates:
- Dans ce projet, ne pas introduire de `fields.DateTime`.
- Utiliser `fields.Date` pour les dates metier et les champs de suivi UI, sauf demande explicite deja existante dans le module cible.
- 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.
- Pour les templates `stock.shipment.in`, preferer aussi des proprietes `report_*` sur le shipment plutot que des contextes ad hoc (`si_*`) quand le document devient metier ou client-specifique.
- Si plusieurs actions de report pointent vers `report_name = 'account.invoice'`, verifier aussi le cache `invoice_report_cache` dans `modules/account_invoice/invoice.py`: un mauvais cache peut faire croire que plusieurs actions utilisent le meme `.fodt`.
- Avant de conclure qu'un template ou une action est faux, verifier si le report alternatif doit bypasser le cache standard.
- Pour les templates shipment, ne pas supposer qu'une variable locale comme `shipment` sera definie partout dans Genshi, surtout dans les headers/footers; preferer `records[0]....` ou des placeholders alignes sur le scope reel du report.
- Dans `purchase_trade`, pour remonter d'une facture vers shipment, pro forma, freight ou autres donnees logistiques, privilegier le lot physique comme pont entre `purchase.line`, `sale.line` et shipment.
- Pour `FREIGHT VALUE`, ne pas lire un champ direct sur la facture: retrouver le fee de shipment (`shipment_in`) dont le produit est `Maritime freight`, puis utiliser `fee.get_amount()`.
- Rappels session templates (2026-04-08):
- `insurance.fodt`: le texte "insured for account of" doit afficher la compagnie courante (shipment.company.party), pas le client.
- `insurance.fodt`: exposer des proprietes Python `report_*` sur `stock.shipment.in` pour les montants (incoming moves) et les zones client-specifiques.
- `insurance.fodt`: "Amount insured" suit la regle metier 110% du montant incoming (base calculee via lot -> purchase.line.unit_price * quantite courante convertie).
- `insurance.fodt`: zone "Contact the following surveyor" alimentee par une propriete dediee, avec champ `surveyor` (party.party) cote shipment.
- `packing_list.fodt`: date en haut a droite = date du jour; unites Net/Gross = unite de `purchase.line`.
- `bill.fodt` (sale): la 2eme date doit etre une vraie maturity date (depuis `invoice.lines_to_pay.maturity_date`), pas `payment_term.rec_name`.
- `bill.fodt` (sale): le montant en lettres doit provenir du montant du bill (facture/total), pas du `unit_price` de ligne.
- Quand un template affiche les placeholders en brut, verifier que les champs sont bien des placeholders Relatorio dans le XML (pas du texte litteral).
- Eviter les apostrophes echappees style `\'` dans placeholders; preferer `&quot;` et `&apos;`.
## 4.bis) Memos metier et templates
- Regles metier transverses:
- `notes/business_rules.md`
- Regles metier locales `purchase_trade`:
- `modules/purchase_trade/docs/business-rules.md`
- `modules/purchase_trade/docs_source/business/` pour les sources de verite
des pages business publiees dans le wiki
- `modules/purchase_trade/docs/business/` pour les pages generees lues par le
wiki
- Decisions templates / reports:
- `notes/template_business_rules.md`
- Documentation comptable et reporting:
- `notes/accounting/README.md`
- `notes/accounting/gap_analysis_workflow.md`
- `notes/accounting/reporting.md`
- `notes/accounting/business_rules.md`
- `notes/accounting/purchase_trade_extensions.md`
- Regles sensibles `purchase_trade` a relire avant de toucher lots, quantites
ou fees:
- `modules/purchase_trade/AGENTS.md`
- `modules/purchase_trade/docs_source/business/lots-and-quantities.md`
- `modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021
(`quantity_theorical`, `lot.qt`, lots physiques, fees et PnL fee).
## 5) Workflow de modification (obligatoire)
@@ -145,18 +105,3 @@ Toujours fournir:
3. Proposer le patch minimal.
4. Implementer + tester cible.
5. Rendre avec le contrat de sortie (section 8).
- Rappels session 2026-04-09:
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids et unites depuis `lot.qt.hist` / `lot_unit_line`, priorite lots `physic`, sinon lot `virtual` unique.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: infos shipment depuis les lots reels des lignes facture; ne rien afficher si plusieurs shipments differents.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: `S/I` = `shipment.reference`; `NB BALES: 0` => `Unchanged` sur le final.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: quantites uniformisees a `2` decimales; conversion `LBS` via UoM, jamais via un facteur fixe aveugle.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: si plusieurs lignes reutilisent le meme lot, les lignes detaillees suivent la quantite facturee convertie, mais le `GROSS` global doit rester le vrai delta historique du lot.
- `sale_ict.fodt`: meme priorite lots; les mots suivent l'unite reelle; le total convertit vers une unite commune, qui est celle du lot virtuel seulement s'il y a un seul lot virtuel sur tout le report.
- `lot.report.r_del_period`: utiliser `sale.line.del_period` pour `lot_s` sans `lot_p`, sinon `purchase.line.del_period`.
- `lot.do_weighing`: `lot_qt` editable et ecrasement direct de `lot.lot_qt`.
- `account.invoice`: `Validate` cree aussi le `account.move` pour les factures client, attribue aussi le `number` a ce stade pour les factures client comme fournisseur; `Post` ne doit plus forcer une fresh session sur ce flux.
- `pricing.pricing`: saisie manuelle autorisee meme sans composant; en manuel, l'utilisateur saisit seulement `Qt` et `Settl. price`; `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et `eod_price` sont derives automatiquement.
- `pricing.pricing`: en manuel, `fixed_qt` = cumul des `quantity`, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`, `unfixed_qt` = reste a fixer, `unfixed_qt_price` = `settl_price` de la ligne.
- `pricing.pricing`: `eod_price` reste non editable et calcule en prix moyen pondere; `last=True` gere par groupe `line + component`, choisi sur la `pricing_date` la plus grande.
- `purchase_trade`: `trader` filtre sur `TRADER`, `operator` sur `OPERATOR`; fallback sur `quantity` si `quantity_theorical` est vide dans les quotas/pricings.
- `sale.line` / `purchase.line`: en mode `basis`, sans `price_component`, le `Price` et le `Fix. progress` de la ligne doivent remonter depuis la ligne `Summary` sans component.

View File

@@ -1 +0,0 @@
[0407/143111.471:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: Accès refusé. (0x5)

View File

@@ -1,36 +0,0 @@
# Fiche VPS - 46.202.173.47
Date de reference: 2026-04-07
## Identite serveur
- IP: `46.202.173.47`
- Hostname alias conseille: `vps3`
## Acces
- Cle publique Laurent Barontini (vps-deploy):
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEm8JMCYsk6I1IoYhIHXNrdyERHdh+eeDCJagOHaRAEK vps-deploy`
- Cle publique Sylvain Duvernay (s.duvernay@singa-associates.com):
`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6Xsp/v6q6JO04ETv1880qoSPptUMxlWQvgcBz67o63 s.duvernay@singa-associates.com`
- Fichier local: `$env:USERPROFILE\.ssh\id_ed25519`
- Mot de passe fourni:
`!!OpenSquared!!`
- Utilisateur SSH:
'root'
- Port SSH:
'22'
## Commande de connexion type
- Laurent Barontini (cle vps-deploy):
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@46.202.173.47`
- Sylvain Duvernay (cle id_ed25519):
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47`
- Avec port custom:
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 -p <port> <user>@46.202.173.47`

View File

@@ -0,0 +1,3 @@
Serveur: 'VPS-62.72.36.116'
Alias Name: 'VPS DEV'
IP Address:'62.72.36.116'

View File

@@ -1538,10 +1538,6 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
})
action = fields.Function(
fields.Char("Action"), 'get_action', searcher='search_action')
old_values = fields.Text("Old Values", readonly=True)
new_values = fields.Text("New Values", readonly=True)
change_summary = fields.Function(
fields.Text("Changes"), 'get_change_summary')
@classmethod
def __setup__(cls):
@@ -1587,172 +1583,6 @@ class Log(ResourceAccessMixin, ModelSQL, ModelView):
return f'{field_name} : {state}'
return self.target
@staticmethod
def _load_change_values(value):
if not value:
return {}
try:
return json.loads(value)
except (TypeError, ValueError):
return {}
@staticmethod
def _format_change_value(value):
if isinstance(value, list):
if len(value) > 6:
return '[%s items]' % len(value)
return ', '.join(Log._format_change_value(v) for v in value)
if isinstance(value, dict):
return json.dumps(value, sort_keys=True, ensure_ascii=False)
if value in (None, ''):
return ''
return str(value)
@staticmethod
def _singular_label(label):
if label.endswith('ies'):
return label[:-3] + 'y'
if label.endswith('s'):
return label[:-1]
return label
def _nested_record_label(self, field, field_label, record_id):
record_label = self._singular_label(field_label)
if not self.resource:
return '%s %s' % (record_label, record_id)
records = list(getattr(self.resource, field.name, None) or [])
line_records = [
record for record in records
if getattr(record, 'type', 'line') == 'line']
ordered_records = line_records or records
for index, record in enumerate(ordered_records, 1):
if str(getattr(record, 'id', '')) == str(record_id):
return '%s %s' % (record_label, index)
return '%s %s' % (record_label, record_id)
def _format_nested_change_summary(
self, field, field_label, target_model, old_value, new_value):
pool = Pool()
Field = pool.get('ir.model.field')
lines = []
if not isinstance(old_value, dict) or not isinstance(new_value, dict):
return lines
def record_sort_key(value):
try:
return int(value)
except (TypeError, ValueError):
return value
for record_id in sorted(
set(old_value) | set(new_value), key=record_sort_key):
record_old = old_value.get(record_id, {})
record_new = new_value.get(record_id, {})
if not isinstance(record_old, dict) or not isinstance(
record_new, dict):
continue
for child_field in sorted(set(record_old) | set(record_new)):
child_old = record_old.get(child_field)
child_new = record_new.get(child_field)
if child_old == child_new:
continue
child_label = Field.get_name(target_model, child_field)
lines.append('%s %s %s => %s' % (
self._nested_record_label(
field, field_label, record_id),
child_label,
self._format_change_value(child_old),
self._format_change_value(child_new)))
return lines
@staticmethod
def _iter_change_commands(value):
if not isinstance(value, list):
return
for item in value:
if isinstance(item, list) and item and isinstance(item[0], str):
command = item[0]
if len(item) >= 3:
yield command, item[1], item[2]
elif len(item) >= 2:
yield command, item[1], None
if value and all(isinstance(item, list) for item in value):
return
index = 0
while index < len(value):
command = value[index]
if not isinstance(command, str):
index += 1
continue
if command == 'write':
if index + 2 >= len(value):
break
yield command, value[index + 1], value[index + 2]
index += 3
continue
if index + 1 >= len(value):
break
yield command, value[index + 1], None
index += 2
def _format_command_change_summary(
self, field, field_label, target_model, new_value):
pool = Pool()
Field = pool.get('ir.model.field')
lines = []
for command, record_ids, values in self._iter_change_commands(
new_value) or []:
if command != 'write' or not isinstance(values, dict):
continue
for record_id in record_ids:
for child_field, child_value in sorted(values.items()):
child_label = Field.get_name(target_model, child_field)
lines.append('%s %s => %s' % (
self._nested_record_label(
field, field_label, record_id),
child_label,
self._format_change_value(child_value)))
return lines
def get_change_summary(self, name):
if self.event != 'write':
return ''
old_values = self._load_change_values(self.old_values)
new_values = self._load_change_values(self.new_values)
if not old_values and not new_values:
return ''
pool = Pool()
Field = pool.get('ir.model.field')
model = self.resource.__name__ if self.resource else None
lines = []
for field_name in sorted(set(old_values) | set(new_values)):
old_value = old_values.get(field_name)
new_value = new_values.get(field_name)
if old_value == new_value:
continue
if model:
field_label = Field.get_name(model, field_name)
else:
field_label = field_name
field = (
getattr(self.resource.__class__, field_name, None)
if self.resource else None)
if field and hasattr(field, 'get_target'):
target_model = field.get_target().__name__
nested_lines = self._format_nested_change_summary(
field, field_label, target_model, old_value, new_value)
if nested_lines:
lines.extend(nested_lines)
continue
command_lines = self._format_command_change_summary(
field, field_label, target_model, new_value)
if command_lines:
lines.extend(command_lines)
continue
lines.append('%s: %s -> %s' % (
field_label,
self._format_change_value(old_value),
self._format_change_value(new_value)))
return '\n'.join(lines)
@classmethod
def search_action(cls, name, clause):
return [('target', *clause[1:])]

View File

@@ -73,7 +73,6 @@ attlist.field &=
| "text"
| "time"
| "timedelta"
| "tolerance_gauge"
| "url"
}?
attlist.field &= attribute fill { "0" | "1" }?
@@ -123,12 +122,6 @@ attlist.field &= attribute symbol { text }?
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
attlist.field &= [a:defaultValue = "square"] attribute border { "square" | "circle" | "rounded" }?
attlist.field &= attribute loading { "lazy" | "eager" }?
attlist.field &= attribute min { text }?
attlist.field &= attribute max { text }?
attlist.field &= attribute min_field { text }?
attlist.field &= attribute max_field { text }?
attlist.field &= attribute center { text }?
attlist.field &= attribute digits { text }?
image = element image { attlist.image, empty }
attlist.image &= attribute name { text }
attlist.image &= [a:defaultValue = "icon"] attribute type { "icon" | "url" }?

View File

@@ -251,7 +251,6 @@
<value>text</value>
<value>time</value>
<value>timedelta</value>
<value>tolerance_gauge</value>
<value>url</value>
</choice>
</attribute>
@@ -578,54 +577,6 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">center</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">digits</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>

View File

@@ -42,7 +42,6 @@ attlist.field &=
| "text"
| "time"
| "timedelta"
| "tolerance_gauge"
| "url"
}?
attlist.field &=
@@ -76,12 +75,6 @@ attlist.field &= attribute help_field { text }?
attlist.field &= attribute view_ids { text }?
attlist.field &= attribute symbol { text }?
attlist.field &= [a:defaultValue = "1"] attribute grouping { "0" | "1" }?
attlist.field &= attribute min { text }?
attlist.field &= attribute max { text }?
attlist.field &= attribute min_field { text }?
attlist.field &= attribute max_field { text }?
attlist.field &= attribute center { text }?
attlist.field &= attribute digits { text }?
prefix = element prefix { attlist.affix, empty }
suffix = element suffix { attlist.affix, empty }
attlist.affix &= attribute string { text }?

View File

@@ -117,7 +117,6 @@
<name ns="">widget</name>
<choice>
<value>binary</value>
<value>badge</value>
<value>boolean</value>
<value>callto</value>
<value>char</value>
@@ -140,21 +139,11 @@
<value>text</value>
<value>time</value>
<value>timedelta</value>
<value>tolerance_gauge</value>
<value>variation</value>
<value>url</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">badge_colors</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
@@ -209,52 +198,6 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">sum_previous</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
<name ns="">sum_variation</name>
<choice>
<value>0</value>
<value>1</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">variation_previous</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">variation_group_by</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute a:defaultValue="0">
<name ns="">variation_summary</name>
<choice>
<value>0</value>
<value>1</value>
</choice>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
@@ -387,54 +330,6 @@
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">min_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">max_field</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">center</name>
<text/>
</attribute>
</optional>
</define>
<define name="attlist.field" combine="interleave">
<optional>
<attribute>
<name ns="">digits</name>
<text/>
</attribute>
</optional>
</define>
<define name="prefix">
<element>
<name ns="">prefix</name>

View File

@@ -13,10 +13,4 @@ this repository contains the full copyright notices and license terms. -->
<field name="event"/>
<field name="action"/>
</group>
<label name="change_summary"/>
<field name="change_summary"/>
<label name="old_values"/>
<field name="old_values"/>
<label name="new_values"/>
<field name="new_values"/>
</form>

View File

@@ -8,5 +8,4 @@ this repository contains the full copyright notices and license terms. -->
<field name="user" expand="1"/>
<field name="event"/>
<field name="action" expand="1"/>
<field name="change_summary" optional="1"/>
</tree>

View File

@@ -5,7 +5,6 @@ import base64
import csv
import datetime
import decimal
import json
import random
import time
import warnings
@@ -194,99 +193,6 @@ class ModelStorage(Model):
resource=record, event=event, target=target, user=user,
**extra))
@staticmethod
def _json_log_value(value):
if isinstance(value, (datetime.date, datetime.datetime, datetime.time)):
return value.isoformat()
if isinstance(value, decimal.Decimal):
return str(value)
if isinstance(value, bytes):
return '<binary>'
if isinstance(value, Model):
return str(value)
if isinstance(value, (list, tuple)):
return [ModelStorage._json_log_value(v) for v in value]
if isinstance(value, dict):
return {
str(k): ModelStorage._json_log_value(v)
for k, v in value.items()
}
return value
@classmethod
def _log_value_text(cls, values):
values = {
name: cls._json_log_value(value)
for name, value in values.items()
}
return json.dumps(values, sort_keys=True, ensure_ascii=False)
@staticmethod
def _iter_log_commands(value):
if not isinstance(value, (list, tuple)):
return
for item in value:
if (isinstance(item, (list, tuple))
and item and isinstance(item[0], str)):
command = item[0]
if len(item) >= 3:
yield command, item[1], item[2]
elif len(item) >= 2:
yield command, item[1], None
if value and all(isinstance(item, (list, tuple)) for item in value):
return
index = 0
while index < len(value):
command = value[index]
if not isinstance(command, str):
index += 1
continue
if command == 'write':
if index + 2 >= len(value):
break
yield command, value[index + 1], value[index + 2]
index += 3
continue
if index + 1 >= len(value):
break
yield command, value[index + 1], None
index += 2
@classmethod
def _nested_log_values(cls, values):
nested_old = {}
nested_new = {}
for field_name, value in values.items():
field = cls._fields.get(field_name)
if not field or not hasattr(field, 'get_target'):
continue
commands = list(cls._iter_log_commands(value) or [])
write_commands = [
(ids, command_values)
for command, ids, command_values in commands
if command == 'write' and command_values]
if not write_commands:
continue
Target = field.get_target()
field_old = {}
field_new = {}
for ids, command_values in write_commands:
target_ids = [int(i) for i in ids]
field_names = list(sorted(command_values.keys()))
with without_check_access():
rows = Target.read(target_ids, field_names)
for row in rows:
record_id = str(row['id'])
field_old.setdefault(record_id, {}).update({
name: row.get(name)
for name in field_names
})
field_new.setdefault(record_id, {}).update(command_values)
if field_old or field_new:
nested_old[field_name] = field_old
nested_new[field_name] = field_new
return nested_old, nested_new
@classmethod
def create(cls, vlist):
'''
@@ -361,25 +267,7 @@ class ModelStorage(Model):
all_records += records
all_fields.update(values.keys())
if transaction.check_access and values:
fields_names = list(sorted(values.keys()))
with without_check_access():
old_values = {
row['id']: {
name: row.get(name)
for name in fields_names
}
for row in cls.read(
[r.id for r in records], fields_names)}
nested_old, nested_new = cls._nested_log_values(values)
for record in records:
record_old_values = old_values.get(record.id, {})
record_new_values = values.copy()
record_old_values.update(nested_old)
record_new_values.update(nested_new)
cls.log([record], 'write', ','.join(fields_names),
old_values=cls._log_value_text(
record_old_values),
new_values=cls._log_value_text(record_new_values))
cls.log(records, 'write', ','.join(sorted(values.keys())))
ModelAccess.check(cls.__name__, 'write')
ModelFieldAccess.check(cls.__name__, all_fields, 'write')

View File

@@ -1,10 +0,0 @@
# AGENTS.md - Regles globales des modules
Ce guide complete le `AGENTS.md` racine et s'applique a tous les modules sous `modules/`.
## Regles transversales
- Ne pas introduire de `fields.DateTime` dans les modules metier sauf demande explicite et code cible deja base sur ce type.
- Pour afficher une date issue d'un champ technique comme `create_date`, preferer un champ fonctionnel en `fields.Date` plutot qu'un acces direct en vue.
- Si une vue doit afficher une information technique non declaree explicitement sur le modele, ajouter un champ fonctionnel dedie cote Python avant de modifier le XML.

View File

@@ -30,8 +30,6 @@ def register():
account.GeneralLedgerAccountParty,
account.GeneralLedgerLine,
account.GeneralLedgerLineContext,
account.GeneralLedgerDetail,
account.GeneralLedgerDetailContext,
account.BalanceSheetContext,
account.BalanceSheetComparisionContext,
account.IncomeStatementContext,
@@ -120,7 +118,6 @@ def register():
Pool.register(
account.AccountTypeStatement,
account.GeneralLedger,
account.GeneralLedgerDetailReport,
account.TrialBalance,
account.AgedBalanceReport,
move.GeneralJournal,

View File

@@ -7,10 +7,9 @@ from decimal import Decimal
from itertools import zip_longest
from dateutil.relativedelta import relativedelta
from sql import Column, Literal, Null, Union, Window
from sql import Column, Literal, Null, Window
from sql.aggregate import Count, Max, Min, Sum
from sql.conditionals import Case, Coalesce
from sql.operators import Concat
from trytond import backend
from trytond.i18n import gettext
@@ -2584,562 +2583,6 @@ class GeneralLedgerLineContext(GeneralLedgerAccountContext):
return super().get_context(fields_names=fields_names)
class GeneralLedgerDetailContext(GeneralLedgerAccountContext):
'General Ledger Detail Context'
__name__ = 'account.general_ledger.detail.context'
account = fields.Many2One(
'account.account', "Account",
domain=[
('company', '=', Eval('company')),
('type', '!=', None),
('closed', '!=', True),
],
depends=['company'])
account_from = fields.Char("Account From")
account_to = fields.Char("Account To")
currency = fields.Many2One('currency.currency', "Transaction Currency")
party = fields.Many2One(
'party.party', "Party",
context={
'company': Eval('company', -1),
},
depends=['company'])
document_number = fields.Char("Document Number")
@classmethod
def default_account(cls):
return Transaction().context.get('account')
@classmethod
def default_account_from(cls):
return Transaction().context.get('account_from')
@classmethod
def default_account_to(cls):
return Transaction().context.get('account_to')
@classmethod
def default_currency(cls):
return Transaction().context.get('currency')
@classmethod
def default_party(cls):
return Transaction().context.get('party')
@classmethod
def default_document_number(cls):
return Transaction().context.get('document_number')
@classmethod
def get_context(cls, fields_names=None):
fields_names = fields_names.copy() if fields_names is not None else []
fields_names += [
'account', 'account_from', 'account_to', 'currency', 'party',
'document_number']
return super().get_context(fields_names=fields_names)
class GeneralLedgerDetail(DescriptionOriginMixin, ModelSQL, ModelView):
'General Ledger Detail'
__name__ = 'account.general_ledger.detail'
row_type = fields.Selection([
('opening', "Opening"),
('movement', "Movement"),
('closing', "Closing"),
], "Row Type", sort=False)
row_sequence = fields.Integer("Row Sequence")
company = fields.Many2One('company.company', "Company")
account = fields.Many2One('account.account', "Account")
account_code = fields.Char("Account Code")
account_name = fields.Char("Account Name")
transaction_currency = fields.Many2One(
'currency.currency', "Transaction Currency")
base_currency = fields.Many2One('currency.currency', "Base Currency")
posting_date = fields.Date("Posting Date")
journal = fields.Many2One('account.journal', "Journal")
journal_entry_number = fields.Char("Journal Entry Number")
document_number = fields.Function(
fields.Char("Document Number"), 'get_document_field')
voucher_number = fields.Char("Voucher Number")
document_type = fields.Function(
fields.Char("Document Type"), 'get_document_field')
document_date = fields.Date("Document Date")
posting_status = fields.Selection([
('draft', "Draft"),
('posted', "Posted"),
], "Posting Status", sort=False)
party = fields.Many2One('party.party', "Party")
move = fields.Many2One('account.move', "Move")
move_line = fields.Many2One('account.move.line', "Move Line")
origin = fields.Reference('Origin', selection='get_origin')
description = fields.Char("Line Desc.")
move_description = fields.Char("Description")
reference = fields.Char("Reference")
debit_base_currency = fields.Numeric(
"Debit Base Currency", digits=(16, 2))
credit_base_currency = fields.Numeric(
"Credit Base Currency", digits=(16, 2))
balance_base_currency = fields.Numeric(
"Balance Base Currency", digits=(16, 2))
running_balance_base_currency = fields.Numeric(
"Running Balance Base Currency", digits=(16, 2))
debit_transaction_currency = fields.Numeric(
"Debit Transaction Currency", digits=(16, 2))
credit_transaction_currency = fields.Numeric(
"Credit Transaction Currency", digits=(16, 2))
balance_transaction_currency = fields.Numeric(
"Balance Transaction Currency", digits=(16, 2))
running_balance_transaction_currency = fields.Numeric(
"Running Balance Transaction Currency", digits=(16, 2))
payable_qty = fields.Numeric("Quantity", digits=(16, 5))
uom = fields.Char("UoM")
entered_by = fields.Many2One('res.user', "Entered By")
entered_date = fields.Date("Entered Date")
modified_by = fields.Many2One('res.user', "Modified By")
modified_date = fields.Date("Modified Date")
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('account_code', 'ASC'),
('transaction_currency', 'ASC'),
('row_sequence', 'ASC'),
('posting_date', 'ASC'),
('journal_entry_number', 'ASC'),
('id', 'ASC'),
]
@classmethod
def _get_period_ids(cls, name, context):
pool = Pool()
Period = pool.get('account.period')
period = None
if name == 'opening':
period_ids = []
if context.get('start_period'):
period = Period(context['start_period'])
elif name == 'closing':
period_ids = []
if context.get('end_period'):
period = Period(context['end_period'])
else:
periods = Period.search([
('fiscalyear', '=', context.get('fiscalyear')),
('type', '=', 'standard'),
],
order=[('start_date', 'DESC')], limit=1)
if periods:
period, = periods
else:
start_period_ids = set(cls._get_period_ids('opening', context))
end_period_ids = set(cls._get_period_ids('closing', context))
return list(end_period_ids.difference(start_period_ids))
if period:
if name == 'opening':
date_clause = ('end_date', '<=', period.start_date)
else:
date_clause = [
('end_date', '<=', period.end_date),
('start_date', '<', period.end_date),
]
periods = Period.search([
('fiscalyear', '=', context.get('fiscalyear')),
date_clause,
])
if period.start_date == period.end_date:
periods.append(period)
if periods:
period_ids = [p.id for p in periods]
if name == 'closing':
period_ids.append(period.id)
return period_ids
@classmethod
def _get_dates(cls, name, context):
if name == 'opening':
to_date = context.get('from_date') or datetime.date.min
if to_date:
try:
to_date -= datetime.timedelta(days=1)
except OverflowError:
pass
return None, to_date
elif name == 'closing':
return None, context.get('to_date')
return context.get('from_date'), context.get('to_date')
@classmethod
def _query_context(cls, name, context):
if context.get('start_period') or context.get('end_period'):
return {
'periods': cls._get_period_ids(name, context),
'from_date': None,
'to_date': None,
}
elif context.get('from_date') or context.get('to_date'):
from_date, to_date = cls._get_dates(name, context)
return {
'periods': None,
'from_date': from_date,
'to_date': to_date,
}
else:
if name == 'opening':
return {
'periods': [],
'from_date': None,
'to_date': None,
}
return {
'periods': None,
'from_date': None,
'to_date': None,
}
@classmethod
def _extra_where(cls, line, move, account, tx_currency, context):
where = (
(move.company == context.get('company'))
& (account.type != Null)
& (account.closed != Literal(True)))
if context.get('account'):
where &= line.account == context['account']
if context.get('account_from'):
where &= account.code >= context['account_from']
if context.get('account_to'):
where &= account.code <= context['account_to']
if context.get('currency'):
where &= tx_currency == context['currency']
if context.get('party'):
where &= line.party == context['party']
if context.get('document_number'):
value = '%%%s%%' % context['document_number']
where &= (
move.number.ilike(value)
| move.post_number.ilike(value)
| move.ext_ref.ilike(value))
return where
@classmethod
def _tables(cls):
pool = Pool()
Line = pool.get('account.move.line')
Move = pool.get('account.move')
Account = pool.get('account.account')
Company = pool.get('company.company')
line = Line.__table__()
move = Move.__table__()
account = Account.__table__()
company = Company.__table__()
return Line, line, move, account, company
@classmethod
def _lot_tables(cls, Line):
if 'lot' not in Line._fields:
return None, None
pool = Pool()
try:
Lot = pool.get('lot.lot')
Uom = pool.get('product.uom')
except KeyError:
return None, None
return Lot.__table__(), Uom.__table__()
@classmethod
def _account_label(cls, account):
return Concat(
Concat(Coalesce(account.code, ''), ' - '),
Coalesce(account.name, ''))
@classmethod
def _opening_balance_query(cls, context):
Line, line, move, account, company = cls._tables()
tx_currency = Coalesce(line.second_currency, company.currency)
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
tx_amount = Case(
(line.second_currency != Null,
Coalesce(line.amount_second_currency, 0)),
else_=base_amount)
with Transaction().set_context(
context, **cls._query_context('opening', context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
return (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id)
.select(
line.account.as_('account'),
tx_currency.as_('transaction_currency'),
Sum(base_amount).as_('base_amount'),
Sum(tx_amount).as_('transaction_amount'),
where=where,
group_by=[line.account, tx_currency],
having=Sum(base_amount) != 0))
@classmethod
def _line_query(cls, name, context):
Line, line, move, account, company = cls._tables()
lot, uom = cls._lot_tables(Line)
opening_balance = cls._opening_balance_query(context)
account_label = cls._account_label(account)
quantity = Literal(None)
unit = Literal(None)
tx_currency = Coalesce(line.second_currency, company.currency)
from_ = (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id))
from_ = from_.join(opening_balance, 'LEFT',
condition=(
(opening_balance.account == line.account)
& (opening_balance.transaction_currency == tx_currency)))
if lot is not None:
from_ = (from_
.join(lot, 'LEFT', condition=Column(line, 'lot') == lot.id)
.join(uom, 'LEFT', condition=lot.lot_unit_line == uom.id))
quantity = lot.lot_qt
unit = uom.symbol
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
second_amount = Coalesce(line.amount_second_currency, 0)
tx_amount = Case(
(line.second_currency != Null, second_amount),
else_=base_amount)
debit_tx = Case(
(line.second_currency == Null, Coalesce(line.debit, 0)),
(second_amount > 0, second_amount),
else_=Literal(0))
credit_tx = Case(
(line.second_currency == Null, Coalesce(line.credit, 0)),
(second_amount < 0, -second_amount),
else_=Literal(0))
with Transaction().set_context(
context, **cls._query_context(name, context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
window = Window(
[line.account, tx_currency],
order_by=[move.date.asc, move.number.asc, line.id.asc])
return from_.select(
(line.id * 3).as_('id'),
line.create_uid.as_('create_uid'),
line.create_date.as_('create_date'),
line.write_uid.as_('write_uid'),
line.write_date.as_('write_date'),
Literal('movement').as_('row_type'),
Literal(1).as_('row_sequence'),
move.company.as_('company'),
line.account.as_('account'),
account_label.as_('account_code'),
account.name.as_('account_name'),
tx_currency.as_('transaction_currency'),
company.currency.as_('base_currency'),
move.date.as_('posting_date'),
move.journal.as_('journal'),
move.number.as_('journal_entry_number'),
move.post_number.as_('voucher_number'),
move.date.as_('document_date'),
move.state.as_('posting_status'),
line.party.as_('party'),
line.move.as_('move'),
line.id.as_('move_line'),
move.origin.as_('origin'),
line.description.as_('description'),
move.description.as_('move_description'),
Coalesce(move.ext_ref, move.description).as_('reference'),
line.debit.as_('debit_base_currency'),
line.credit.as_('credit_base_currency'),
base_amount.as_('balance_base_currency'),
(Coalesce(opening_balance.base_amount, 0)
+ Sum(base_amount, window=window)).as_(
'running_balance_base_currency'),
debit_tx.as_('debit_transaction_currency'),
credit_tx.as_('credit_transaction_currency'),
tx_amount.as_('balance_transaction_currency'),
(Coalesce(opening_balance.transaction_amount, 0)
+ Sum(tx_amount, window=window)).as_(
'running_balance_transaction_currency'),
quantity.as_('payable_qty'),
unit.as_('uom'),
line.create_uid.as_('entered_by'),
line.create_date.as_('entered_date'),
line.write_uid.as_('modified_by'),
line.write_date.as_('modified_date'),
where=where)
@classmethod
def _summary_query(cls, name, context):
Line, line, move, account, company = cls._tables()
account_label = cls._account_label(account)
tx_currency = Coalesce(line.second_currency, company.currency)
base_amount = Coalesce(line.debit, 0) - Coalesce(line.credit, 0)
tx_amount = Case(
(line.second_currency != Null,
Coalesce(line.amount_second_currency, 0)),
else_=base_amount)
row_sequence = 0 if name == 'opening' else 2
row_id = (
(Min(line.id) * 3 - 1) if name == 'opening'
else (Min(line.id) * 3 - 2))
with Transaction().set_context(
context, **cls._query_context(name, context)):
line_query, _ = Line.query_get(line)
where = line_query & cls._extra_where(
line, move, account, tx_currency, context)
return (line.join(move, condition=line.move == move.id)
.join(account, condition=line.account == account.id)
.join(company, condition=move.company == company.id)
.select(
row_id.as_('id'),
Literal(0).as_('create_uid'),
Max(line.create_date).as_('create_date'),
Literal(0).as_('write_uid'),
Max(line.write_date).as_('write_date'),
Literal(name).as_('row_type'),
Literal(row_sequence).as_('row_sequence'),
move.company.as_('company'),
line.account.as_('account'),
account_label.as_('account_code'),
account.name.as_('account_name'),
tx_currency.as_('transaction_currency'),
company.currency.as_('base_currency'),
Literal(None).as_('posting_date'),
Literal(None).as_('journal'),
Literal(None).as_('journal_entry_number'),
Literal(None).as_('voucher_number'),
Literal(None).as_('document_date'),
Literal(None).as_('posting_status'),
Literal(None).as_('party'),
Literal(None).as_('move'),
Literal(None).as_('move_line'),
Literal(None).as_('origin'),
Literal(name.title()).as_('description'),
Literal(None).as_('move_description'),
Literal(None).as_('reference'),
Literal(0).as_('debit_base_currency'),
Literal(0).as_('credit_base_currency'),
Sum(base_amount).as_('balance_base_currency'),
Sum(base_amount).as_('running_balance_base_currency'),
Literal(0).as_('debit_transaction_currency'),
Literal(0).as_('credit_transaction_currency'),
Sum(tx_amount).as_('balance_transaction_currency'),
Sum(tx_amount).as_('running_balance_transaction_currency'),
Literal(None).as_('payable_qty'),
Literal(None).as_('uom'),
Literal(None).as_('entered_by'),
Literal(None).as_('entered_date'),
Literal(None).as_('modified_by'),
Literal(None).as_('modified_date'),
where=where,
group_by=[
move.company, line.account, account.code, account.name,
tx_currency, company.currency],
having=Sum(base_amount) != 0))
@classmethod
def table_query(cls):
pool = Pool()
DetailContext = pool.get('account.general_ledger.detail.context')
context = DetailContext.get_context()
return Union(
cls._summary_query('opening', context),
cls._line_query('movement', context),
cls._summary_query('closing', context))
@classmethod
def get_origin(cls):
Move = Pool().get('account.move')
return Move.get_origin()
def _get_related_payment(self):
if not self.move_line:
return None
try:
Payment = Pool().get('account.payment')
except KeyError:
return None
payments = Payment.search([('line', '=', self.move_line.id)], limit=1)
return payments[0] if payments else None
@staticmethod
def _resolve_reference(value):
if not value or hasattr(value, '__name__'):
return value
if not isinstance(value, str):
return None
try:
model_name, record_id = value.split(',', 1)
record_id = int(record_id)
except ValueError:
return None
try:
Model = Pool().get(model_name)
except KeyError:
return None
return Model(record_id)
def get_document_field(self, name):
origin = self._resolve_reference(
self.origin or (self.move.origin if self.move else None))
if origin:
model_name = origin.__name__
if model_name == 'account.invoice':
if name == 'document_number':
return origin.number or origin.reference
side = {
'out': 'Customer',
'in': 'Supplier',
}.get(origin.type, '')
stage = ''
reference = (origin.reference or '').lower()
if 'provisional' in reference:
stage = 'Provisional'
elif 'final' in reference:
stage = 'Final'
return ' '.join(filter(None, ['Invoice', side, stage]))
if model_name == 'account.payment':
if name == 'document_number':
return origin.number
side = {
'receivable': 'Customer',
'payable': 'Supplier',
}.get(origin.kind, '')
return ' '.join(filter(None, ['Payment', side]))
if name == 'document_number':
return getattr(origin, 'number', None) or getattr(
origin, 'reference', None)
return getattr(origin, 'rec_name', None) or model_name
payment = self._get_related_payment()
if payment:
if name == 'document_number':
return payment.number
side = {
'receivable': 'Customer',
'payable': 'Supplier',
}.get(payment.kind, '')
return ' '.join(filter(None, ['Payment', side]))
return None
class GeneralLedger(Report):
__name__ = 'account.general_ledger'
@@ -3168,52 +2611,6 @@ class GeneralLedger(Report):
return report_context
class GeneralLedgerDetailReport(Report):
__name__ = 'account.general_ledger.detail.report'
@classmethod
def get_context(cls, records, header, data):
pool = Pool()
Company = pool.get('company.company')
Fiscalyear = pool.get('account.fiscalyear')
Period = pool.get('account.period')
context = Transaction().context
report_context = super().get_context(records, header, data)
report_context['company'] = Company(context['company'])
report_context['fiscalyear'] = Fiscalyear(context['fiscalyear'])
for period in ['start_period', 'end_period']:
if context.get(period):
report_context[period] = Period(context[period])
else:
report_context[period] = None
report_context['from_date'] = context.get('from_date')
report_context['to_date'] = context.get('to_date')
report_context['groups'] = cls._groups(records)
return report_context
@classmethod
def _groups(cls, records):
groups = []
current_key = None
current = None
for line in records:
currency = getattr(line, 'transaction_currency', None)
key = (
getattr(line, 'account_code', None),
getattr(currency, 'id', None))
if key != current_key:
current = {
'account': line.account_code or line.account_name or '',
'currency': currency,
'lines': [],
}
groups.append(current)
current_key = key
current['lines'].append(line)
return groups
class TrialBalance(Report):
__name__ = 'account.trial_balance'

View File

@@ -806,70 +806,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="rule_group" ref="rule_group_general_ledger_line_companies"/>
</record>
<record model="ir.ui.view" id="general_ledger_detail_view_list">
<field name="model">account.general_ledger.detail</field>
<field name="type">tree</field>
<field name="name">general_ledger_detail_list</field>
</record>
<record model="ir.action.act_window" id="act_general_ledger_detail_form">
<field name="name">General Ledger Detail</field>
<field name="res_model">account.general_ledger.detail</field>
<field name="context_model">account.general_ledger.detail.context</field>
</record>
<record model="ir.action.act_window.view"
id="act_general_ledger_detail_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="general_ledger_detail_view_list"/>
<field name="act_window" ref="act_general_ledger_detail_form"/>
</record>
<record model="ir.action.report" id="report_general_ledger_detail">
<field name="name">General Ledger Detail</field>
<field name="records">listed</field>
<field name="model">account.general_ledger.detail</field>
<field name="report_name">account.general_ledger.detail.report</field>
<field name="report">account/general_ledger_detail.fodt</field>
</record>
<record model="ir.action.keyword" id="report_general_ledger_detail_keyword">
<field name="keyword">form_print</field>
<field name="model">account.general_ledger.detail,-1</field>
<field name="action" ref="report_general_ledger_detail"/>
</record>
<menuitem
name="General Ledger Detail"
parent="menu_reporting"
action="act_general_ledger_detail_form"
sequence="11"
id="menu_general_ledger_detail"/>
<record model="ir.model.access" id="access_general_ledger_detail">
<field name="model">account.general_ledger.detail</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_general_ledger_detail_account">
<field name="model">account.general_ledger.detail</field>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.rule.group" id="rule_group_general_ledger_detail_companies">
<field name="name">User in companies</field>
<field name="model">account.general_ledger.detail</field>
<field name="global_p" eval="True"/>
</record>
<record model="ir.rule" id="rule_general_ledger_detail_companies">
<field name="domain"
eval="[('company', 'in', Eval('companies', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_general_ledger_detail_companies"/>
</record>
<record model="ir.ui.view" id="general_ledger_account_context_view_form">
<field name="model">account.general_ledger.account.context</field>
<field name="type">form</field>
@@ -881,11 +817,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="general_ledger_account_context_view_form"/>
<field name="name">general_ledger_line_context_form</field>
</record>
<record model="ir.ui.view" id="general_ledger_detail_context_view_form">
<field name="model">account.general_ledger.detail.context</field>
<field name="type">form</field>
<field name="name">general_ledger_detail_context_form</field>
</record>
<record model="ir.ui.view" id="account_balance_sheet_view_tree">
<field name="model">account.account.type</field>

View File

@@ -1,825 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:officeooo="http://openoffice.org/2009/office" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d: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:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" 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:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:generator>LibreOffice/7.3.5.2$Linux_X86_64 LibreOffice_project/30$Build-2</meta:generator><meta:creation-date>2008-06-07T15:26:41</meta:creation-date><dc:date>2008-08-27T17:03:36</dc:date><meta:editing-cycles>1</meta:editing-cycles><meta:editing-duration>PT0S</meta:editing-duration><meta:document-statistic meta:table-count="4" meta:image-count="0" meta:object-count="0" meta:page-count="3" meta:paragraph-count="51" meta:word-count="145" meta:character-count="1808" meta:non-whitespace-character-count="1714"/><meta:user-defined meta:name="Info 1"/><meta:user-defined meta:name="Info 2"/><meta:user-defined meta:name="Info 3"/><meta:user-defined meta:name="Info 4"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">24130</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">48129</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">22994</config:config-item>
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">19627</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">29242</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">24130</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">48128</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">47122</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
<config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
<config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
<config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
</config:config-item-map-entry>
</config:config-item-map-indexed>
</config:config-item-set>
<config:config-item-set config:name="ooo:configuration-settings">
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
<config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
<config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="Rsid" config:type="int">4069242</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">2070049</config:config-item>
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
<config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
</config:config-item-set>
</office:settings>
<office:scripts>
<office:script script:language="ooo:Basic">
<ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</office:script>
</office:scripts>
<office:font-face-decls>
<style:font-face style:name="Andale Sans UI" svg:font-family="&apos;Andale Sans UI&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="DejaVu Sans" svg:font-family="&apos;DejaVu Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif1" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Bold" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif2" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Regular" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>
<style:font-face style:name="Thorndale AMT" svg:font-family="&apos;Thorndale AMT&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Liberation Serif2" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Regular" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="&apos;DejaVu Sans&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="DejaVu Sans" style:font-family-complex="&apos;DejaVu Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
</style:style>
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-size-complex="12pt" style:font-style-complex="italic"/>
</style:style>
<style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Bold" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="html">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" style:border-line-width-bottom="0.002cm 0.035cm 0.002cm" fo:padding="0cm" fo:border-left="none" fo:border-right="none" fo:border-top="none" fo:border-bottom="1.11pt double #808080" text:number-lines="false" text:line-number="0" style:join-border="false"/>
<style:text-properties fo:font-size="6pt" style:font-size-asian="6pt" style:font-size-complex="6pt"/>
</style:style>
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.5cm" style:type="center"/>
<style:tab-stop style:position="17cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
</style:style>
<style:style style:name="Header" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="8.795cm" style:type="center"/>
<style:tab-stop style:position="17.59cm" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Text_20_body_20_indent" style:display-name="Text body indent" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-left="0.499cm" fo:margin-right="0cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
<style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm" fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Title" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="28pt" fo:font-weight="bold" style:font-size-asian="28pt" style:font-weight-asian="bold" style:font-size-complex="28pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="chapter">
<style:paragraph-properties fo:margin-top="0.106cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties fo:font-size="18pt" style:font-size-asian="18pt" style:font-size-complex="18pt"/>
</style:style>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
<style:text-properties style:font-name="StarSymbol" fo:font-family="StarSymbol" fo:font-size="9pt" style:font-name-asian="StarSymbol" style:font-family-asian="StarSymbol" style:font-size-asian="9pt" style:font-name-complex="StarSymbol" style:font-family-complex="StarSymbol" style:font-size-complex="9pt"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" loext:num-list-format="%1%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="2" loext:num-list-format="%2%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="3" loext:num-list-format="%3%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="4" loext:num-list-format="%4%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="5" loext:num-list-format="%5%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="6" loext:num-list-format="%6%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="7" loext:num-list-format="%7%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="8" loext:num-list-format="%8%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="9" loext:num-list-format="%9%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
<text:outline-level-style text:level="10" loext:num-list-format="%10%" style:num-format="">
<style:list-level-properties text:min-label-distance="0.381cm"/>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
<style:style style:name="Default_20_Style.1" style:display-name="Default Style.1" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.2" style:display-name="Default Style.2" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.3" style:display-name="Default Style.3" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.4" style:display-name="Default Style.4" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.5" style:display-name="Default Style.5" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.6" style:display-name="Default Style.6" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.7" style:display-name="Default Style.7" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.8" style:display-name="Default Style.8" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.9" style:display-name="Default Style.9" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.10" style:display-name="Default Style.10" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.11" style:display-name="Default Style.11" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.12" style:display-name="Default Style.12" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.13" style:display-name="Default Style.13" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.14" style:display-name="Default Style.14" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-right="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.15" style:display-name="Default Style.15" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-top="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<style:style style:name="Default_20_Style.16" style:display-name="Default Style.16" style:family="table-cell">
<style:table-cell-properties fo:border-left="0.51pt solid #000000" fo:border-bottom="0.51pt solid #000000"/>
</style:style>
<table:table-template table:name="Default Style" table:first-row-end-column="row" table:first-row-start-column="row" table:last-row-end-column="row" table:last-row-start-column="row">
<table:first-row table:style-name="Default_20_Style.1"/>
<table:last-row table:style-name="Default_20_Style.2"/>
<table:first-column table:style-name="Default_20_Style.3"/>
<table:last-column table:style-name="Default_20_Style.4"/>
<table:body table:style-name="Default_20_Style.9"/>
<table:even-rows table:style-name="Default_20_Style.5"/>
<table:odd-rows table:style-name="Default_20_Style.6"/>
<table:even-columns table:style-name="Default_20_Style.7"/>
<table:odd-columns table:style-name="Default_20_Style.8"/>
<table:background table:style-name="Default_20_Style.10"/>
<loext:first-row-even-column table:style-name="Default_20_Style.15"/>
<loext:last-row-even-column table:style-name="Default_20_Style.16"/>
<loext:first-row-end-column table:style-name="Default_20_Style.12"/>
<loext:first-row-start-column table:style-name="Default_20_Style.11"/>
<loext:last-row-end-column table:style-name="Default_20_Style.14"/>
<loext:last-row-start-column table:style-name="Default_20_Style.13"/>
</table:table-template>
</office:styles>
<office:automatic-styles>
<style:style style:name="Table2" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table2.A" style:family="table-column">
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
</style:style>
<style:style style:name="Table2.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table1.A" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
</style:style>
<style:style style:name="Table1.B" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
</style:style>
<style:style style:name="Table1.A1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
</style:style>
<style:style style:name="Table2" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table2.A" style:family="table-column">
<style:table-column-properties style:column-width="7.98cm" style:rel-column-width="21845*"/>
</style:style>
<style:style style:name="Table2.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="transparent" fo:padding="0.097cm" fo:border="none">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table1" style:family="table">
<style:table-properties style:width="23.94cm" table:align="margins"/>
</style:style>
<style:style style:name="Table1.A" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32768*"/>
</style:style>
<style:style style:name="Table1.B" style:family="table-column">
<style:table-column-properties style:column-width="11.97cm" style:rel-column-width="32767*"/>
</style:style>
<style:style style:name="Table1.A1" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border="none"/>
</style:style>
<style:style style:name="Lines" style:family="table">
<style:table-properties style:width="23.94cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-properties>
</style:style>
<style:style style:name="Lines.A" style:family="table-column">
<style:table-column-properties style:column-width="2.499cm" style:rel-column-width="1417*"/>
</style:style>
<style:style style:name="Lines.B" style:family="table-column">
<style:table-column-properties style:column-width="3.358cm" style:rel-column-width="1904*"/>
</style:style>
<style:style style:name="Lines.C" style:family="table-column">
<style:table-column-properties style:column-width="3.36cm" style:rel-column-width="1905*"/>
</style:style>
<style:style style:name="Lines.F" style:family="table-column">
<style:table-column-properties style:column-width="4.002cm" style:rel-column-width="2269*"/>
</style:style>
<style:style style:name="Lines.1" style:family="table-row">
<style:table-row-properties fo:keep-together="always"/>
</style:style>
<style:style style:name="Lines.A1" style:family="table-cell">
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A2" style:family="table-cell">
<style:table-cell-properties fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A3" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.G3" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="0.05pt solid #000000" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.C4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.G4" style:family="table-cell">
<style:table-cell-properties fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Lines.A5" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.B6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.C6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.D6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.E6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.F6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.G6" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A7" style:family="table-cell">
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
</style:style>
<style:style style:name="Lines.A8" style:family="table-cell">
<style:table-cell-properties fo:background-color="#e6e6e6" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3" style:family="table" style:master-page-name="">
<style:table-properties style:width="23.94cm" style:page-number="auto" fo:break-before="auto" fo:break-after="auto" table:align="margins" fo:background-color="transparent" style:shadow="none" fo:keep-with-next="always" style:may-break-between-rows="true" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-properties>
</style:style>
<style:style style:name="Table3.A" style:family="table-column">
<style:table-column-properties style:column-width="5.851cm" style:rel-column-width="3317*"/>
</style:style>
<style:style style:name="Table3.B" style:family="table-column">
<style:table-column-properties style:column-width="3.362cm" style:rel-column-width="1906*"/>
</style:style>
<style:style style:name="Table3.E" style:family="table-column">
<style:table-column-properties style:column-width="4.001cm" style:rel-column-width="2268*"/>
</style:style>
<style:style style:name="Table3.1" style:family="table-row">
<style:table-row-properties fo:background-color="transparent" fo:keep-together="always">
<style:background-image/>
</style:table-row-properties>
</style:style>
<style:style style:name="Table3.A1" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.A2" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#b3b3b3" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.A3" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="Table3.F3" style:family="table-cell">
<style:table-cell-properties style:vertical-align="" fo:background-color="#cccccc" fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000" style:writing-mode="lr-tb">
<style:background-image/>
</style:table-cell-properties>
</style:style>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P3" style:family="paragraph" style:parent-style-name="Footer">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P4" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P5" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P6" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P7" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" style:shadow="none" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="12pt" fo:font-weight="bold" style:font-size-asian="12pt" style:font-weight-asian="bold" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P8" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
</style:style>
<style:style style:name="P9" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P10" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P11" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P12" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P13" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P14" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P15" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="11pt" style:text-underline-style="none" style:font-size-asian="11pt" style:font-size-complex="11pt"/>
</style:style>
<style:style style:name="P16" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P17" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8"/>
</style:style>
<style:style style:name="P18" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="00251ac8" fo:background-color="transparent"/>
</style:style>
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:break-before="page"/>
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:paragraph-rsid="0037c304"/>
</style:style>
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:paragraph-rsid="003a045d"/>
</style:style>
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:text-properties officeooo:rsid="003955ec" officeooo:paragraph-rsid="003a045d"/>
</style:style>
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Heading_20_1">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Header">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
</style:style>
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:text-outline="false" style:text-line-through-style="none" style:text-line-through-type="none" style:font-name="Liberation Serif" fo:font-size="12pt" fo:font-style="normal" fo:text-shadow="none" style:text-underline-style="none" fo:font-weight="normal" style:font-size-asian="12pt" style:font-style-asian="normal" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-style-complex="normal" style:font-weight-complex="normal" style:text-overline-style="none" style:text-overline-color="font-color"/>
</style:style>
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties officeooo:paragraph-rsid="002aca3c" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Table_20_Contents">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
</style:style>
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Table_20_Heading">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false"/>
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:break-before="page" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties style:text-underline-style="none" officeooo:paragraph-rsid="003e177a"/>
</style:style>
<style:style style:name="P38" style:family="paragraph" style:parent-style-name="Text_20_body">
<style:paragraph-properties fo:text-align="start" style:justify-single-word="false" fo:keep-together="always" fo:keep-with-next="always"/>
<style:text-properties fo:font-size="12pt" style:text-underline-style="none" fo:font-weight="normal" officeooo:paragraph-rsid="003e177a" fo:background-color="transparent" style:font-size-asian="10.5pt" style:font-weight-asian="normal" style:font-size-complex="12pt" style:font-weight-complex="normal"/>
</style:style>
<style:style style:name="T1" style:family="text">
<style:text-properties officeooo:rsid="0037c304"/>
</style:style>
<style:style style:name="T2" style:family="text">
<style:text-properties officeooo:rsid="0039b016"/>
</style:style>
<style:style style:name="T3" style:family="text">
<style:text-properties officeooo:rsid="003e177a"/>
</style:style>
<style:style style:name="T4" style:family="text">
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="T5" style:family="text">
<style:text-properties style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" officeooo:rsid="003e177a"/>
</style:style>
<style:style style:name="T6" style:family="text">
<style:text-properties style:text-underline-style="none"/>
</style:style>
<style:style style:name="T7" style:family="text">
<style:text-properties style:text-underline-style="none" officeooo:rsid="003e177a"/>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="27.94cm" fo:page-height="21.59cm" style:num-format="1" style:print-orientation="landscape" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.55cm" style:layout-grid-ruby-height="0cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
<style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="none" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-bottom="0.499cm"/>
</style:header-style>
<style:footer-style>
<style:header-footer-properties fo:min-height="0cm" fo:margin-left="0cm" fo:margin-right="0cm" fo:margin-top="0.499cm"/>
</style:footer-style>
</style:page-layout>
<style:style style:name="dp1" style:family="drawing-page">
<style:drawing-page-properties draw:background-size="full"/>
</style:style>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1">
<style:header>
<table:table table:name="Table2" table:style-name="Table2">
<table:table-column table:style-name="Table2.A" table:number-columns-repeated="3"/>
<table:table-row table:style-name="TableLine94368948362784">
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="Header">Company: <text:placeholder text:placeholder-type="text">&lt;company.rec_name&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="P27">General Ledger Detail<text:placeholder text:placeholder-type="text">&lt;if test=&quot;not context.get(&apos;posted&apos;)&quot;&gt;</text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:span></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table2.A1" office:value-type="string">
<text:p text:style-name="P2">Print Date: <text:placeholder text:placeholder-type="text">&lt;format_date(datetime.date.today(), user.language)&gt;</text:placeholder><text:s/>at <text:placeholder text:placeholder-type="text">&lt;datetime.datetime.now().strftime(&apos;%H:%M:%S&apos;)&gt;</text:placeholder></text:p>
</table:table-cell>
</table:table-row>
</table:table>
</style:header>
<style:footer>
<table:table table:name="Table1" table:style-name="Table1">
<table:table-column table:style-name="Table1.A"/>
<table:table-column table:style-name="Table1.B"/>
<table:table-row table:style-name="TableLine94368949141280">
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="Footer">User: <text:placeholder text:placeholder-type="text">&lt;user.rec_name&gt;</text:placeholder></text:p>
</table:table-cell>
<table:table-cell table:style-name="Table1.A1" office:value-type="string">
<text:p text:style-name="P3"><text:page-number text:select-page="current">1</text:page-number>/<text:page-count>3</text:page-count></text:p>
</table:table-cell>
</table:table-row>
</table:table>
</style:footer>
</style:master-page>
</office:master-styles>
<office:body>
<office:text text:use-soft-page-breaks="true">
<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
<text:p text:style-name="P25">General Ledger Detail<text:placeholder text:placeholder-type="text">&lt;if test=&quot;not context.get(&apos;posted&apos;)&quot;&gt;</text:placeholder> <text:span text:style-name="T3">Draft</text:span><text:span text:style-name="T3"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:span></text:p>
<text:p text:style-name="P23"><text:span text:style-name="T1">Fiscal Year: </text:span><text:placeholder text:placeholder-type="text">&lt;fiscalyear.rec_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;start_period or end_period&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:span text:style-name="T1">From Period</text:span>: <text:placeholder text:placeholder-type="text">&lt;start_period.name if start_period else &apos;&apos;&gt;</text:placeholder><text:s/><text:span text:style-name="T2">To</text:span> <text:placeholder text:placeholder-type="text">&lt;end_period.name if end_period else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;from_date or to_date&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P24">From Date: <text:placeholder text:placeholder-type="text">&lt;format_date(from_date, user.language) if from_date else &apos;&apos;&gt;</text:placeholder><text:s/>To <text:placeholder text:placeholder-type="text">&lt;format_date(to_date, user.language) if to_date else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P23"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P20"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;group in groups&quot;&gt;</text:placeholder></text:p>
<table:table table:name="Lines" table:style-name="Lines">
<table:table-column table:style-name="Lines.A"/>
<table:table-column table:style-name="Lines.B"/>
<table:table-column table:style-name="Lines.C"/>
<table:table-column table:style-name="Lines.B" table:number-columns-repeated="2"/>
<table:table-column table:style-name="Lines.F" table:number-columns-repeated="2"/>
<table:table-header-rows>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A1" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P5">Account / Currency</text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A2" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P7"><text:placeholder text:placeholder-type="text">&lt;group[&apos;account&apos;]&gt;</text:placeholder><text:s/>/<text:s/><text:placeholder text:placeholder-type="text">&lt;group[&apos;currency&apos;].rec_name if group[&apos;currency&apos;] else &apos;&apos;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
<table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Date</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Journal</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Entry / Doc.</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="Table_20_Heading">Debit</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Credit</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.A3" office:value-type="string"><text:p text:style-name="P6">Running</text:p></table:table-cell>
<table:table-cell table:style-name="Lines.G3" office:value-type="string"><text:p text:style-name="P6">Description / Ref.</text:p></table:table-cell>
</table:table-row>
</table:table-header-rows>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A5" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in group[&apos;lines&apos;]&quot;&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
</table:table-row>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A6" office:value-type="string"><text:p text:style-name="P33"><text:placeholder text:placeholder-type="text">&lt;format_date(line.posting_date, user.language) if line.posting_date else &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.B6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.journal.rec_name if line.journal else &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.C6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.journal_entry_number or &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.document_number or line.voucher_number or &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.D6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.debit_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.E6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.credit_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.F6" office:value-type="string"><text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;format_currency(line.running_balance_base_currency or 0, user.language, line.base_currency or company.currency)&gt;</text:placeholder></text:p></table:table-cell>
<table:table-cell table:style-name="Lines.G6" office:value-type="string"><text:p text:style-name="Table_20_Contents"><text:placeholder text:placeholder-type="text">&lt;line.description or line.move_description or &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.party.rec_name if line.party else &apos;&apos;&gt;</text:placeholder><text:line-break/><text:placeholder text:placeholder-type="text">&lt;line.reference or &apos;&apos;&gt;</text:placeholder></text:p></table:table-cell>
</table:table-row>
<text:soft-page-break/>
<table:table-row table:style-name="Lines.1">
<table:table-cell table:style-name="Lines.A7" table:number-columns-spanned="7" office:value-type="string">
<text:p text:style-name="P18"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</table:table-cell>
<table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/><table:covered-table-cell/>
</table:table-row>
</table:table>
<text:p text:style-name="P8"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</office:text>
</office:body>
</office:document>

View File

@@ -1013,9 +1013,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
journal = fields.Function(fields.Many2One('account.journal',"Journal"),'get_journal')
rate = fields.Numeric("Rate", digits=(10, 6), states={
'readonly': _states['readonly'],
})
rate = fields.Function(fields.Numeric("Rate",digits=(1,6)),'get_rate')
del _states
@@ -1119,84 +1117,46 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
return line.move.id
def get_rate(self,name=None):
amount = self.credit if self.credit else self.debit
amount_second_currency = getattr(self, 'amount_second_currency', None)
if amount_second_currency and amount:
return round(abs(amount_second_currency) / amount, 6)
def _set_rate_from_amounts(self):
if (getattr(self, 'amount_second_currency', None)
and not getattr(self, 'rate', None)):
self.rate = self.get_rate()
if self.amount_second_currency:
return round((self.credit if self.credit else self.debit) / abs(self.amount_second_currency),6)
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency', 'rate',
'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
def on_change_second_currency(self):
if self._manual_rate_mode():
self.rate = self._get_second_currency_rate()
self._compute_amount_from_second_currency()
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency', 'rate',
'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
def on_change_rate(self):
if self._manual_rate_mode() and not self.debit and not self.credit:
self._compute_amount_from_second_currency()
def _manual_rate_mode(self):
if getattr(self, 'origin', None) or getattr(self, 'move_origin', None):
return False
move = getattr(self, 'move', None)
if move and getattr(move, 'origin', None):
return False
return True
def _get_second_currency_rate(self):
'date','second_currency','amount_second_currency')
def on_change_amount_second_currency(self):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
if self.second_currency is None:
return None
tdate = Date.today()
if self.date:
tdate = self.date
rates = Currency._get_rate([self.second_currency], tdate)
return rates.get(self.second_currency.id) if rates else None
def _get_company_currency(self):
Company = Pool().get('company.company')
for record in (self, getattr(self, 'move', None),
getattr(self, 'account', None)):
company = getattr(record, 'company', None)
if company:
return company.currency
company_id = Transaction().context.get('company')
if company_id:
return Company(company_id).currency
def _compute_amount_from_second_currency(self):
if self.second_currency is not None and self.amount_second_currency is not None:
if not self.rate:
self.rate = self._get_second_currency_rate()
if self.rate:
company_currency = self._get_company_currency()
if not company_currency:
return
amount = company_currency.round(
abs(self.amount_second_currency) / self.rate)
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
tdate = Date.today()
if self.date:
tdate = self.date
rate = Currency._get_rate([self.second_currency],tdate)
if rate:
rate = rate[self.second_currency.id]
if self.amount_second_currency > 0:
self.debit = amount
self.credit = Decimal(0)
elif self.amount_second_currency < 0:
self.debit = Decimal(0)
self.credit = amount
else:
self.debit = Decimal(0)
self.credit = Decimal(0)
self.debit = round(rate * self.amount_second_currency,2)
elif self.amount_second_currency > 0:
self.credit = round(rate * abs(self.amount_second_currency),2)
self.rate = self.get_rate()
@fields.depends(
'debit', 'credit',
'date','second_currency','amount_second_currency')
def on_change_second_currency(self):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
if self.second_currency != None and self.amount_second_currency != None and (not self.credit and not self.debit):
tdate = Date.today()
if self.date:
tdate = self.date
rate = Currency._get_rate([self.second_currency],tdate)
if rate:
rate = rate[self.second_currency.id]
if self.amount_second_currency > 0:
self.debit = round(rate * self.amount_second_currency,2)
elif self.amount_second_currency > 0:
self.credit = round(rate * abs(self.amount_second_currency),2)
self.rate = self.get_rate()
@fields.depends(
'move', 'debit', 'credit',
@@ -1263,26 +1223,15 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
if self.debit:
self.credit = Decimal(0)
self._amount_second_currency_sign()
self.rate = self.get_rate()
@fields.depends('debit', 'credit', 'amount_second_currency')
def on_change_credit(self):
if self.credit:
self.debit = Decimal(0)
self._amount_second_currency_sign()
self.rate = self.get_rate()
@fields.depends(
'amount_second_currency', 'debit', 'credit', 'date', 'second_currency',
'rate', 'origin', 'move_origin', 'move', '_parent_move.origin',
'company', 'account')
@fields.depends('amount_second_currency', 'debit', 'credit')
def on_change_amount_second_currency(self):
if self._manual_rate_mode():
if not self.rate:
self.rate = self._get_second_currency_rate()
self._compute_amount_from_second_currency()
elif not self.rate:
self.rate = self.get_rate()
self._amount_second_currency_sign()
def _amount_second_currency_sign(self):
@@ -1292,7 +1241,6 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
self.amount_second_currency = \
self.amount_second_currency.copy_sign(
(self.debit or 0) - (self.credit or 0))
self._set_rate_from_amounts()
@fields.depends('account')
def on_change_account(self):
@@ -1597,14 +1545,6 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
for fname in move_fields(move_name=False):
vals.setdefault(fname, None)
lines = super(Line, cls).create(vlist)
to_save = []
for line in lines:
if (getattr(line, 'amount_second_currency', None)
and not getattr(line, 'rate', None)):
line._set_rate_from_amounts()
to_save.append(line)
if to_save:
cls.save(to_save)
period_and_journals = set((line.period, line.journal)
for line in lines)
for period, journal in period_and_journals:

View File

@@ -12,7 +12,7 @@ from trytond.modules.account.exceptions import (
from trytond.modules.account.tax import TaxableMixin
from trytond.modules.company.tests import (
CompanyTestMixin, PartyCompanyCheckEraseMixin, create_company, set_company)
from trytond.modules.currency.tests import add_currency_rate, create_currency
from trytond.modules.currency.tests import create_currency
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.transaction import Transaction, inactive_records
@@ -576,54 +576,6 @@ class AccountTestCase(
self.assertEqual(
cash_cur.amount_second_currency, Decimal(50))
@with_transaction()
def test_move_line_second_currency_amount_on_change(self):
'Test account move line computes amount from second currency'
pool = Pool()
Account = pool.get('account.account')
Line = pool.get('account.move.line')
company = create_company()
with set_company(company):
create_chart(company)
second_currency = create_currency('sec')
add_currency_rate(second_currency, Decimal('0.939506'))
expense, = Account.search([
('type.expense', '=', True),
])
line = Line(
account=expense,
second_currency=second_currency,
amount_second_currency=Decimal('100.00'))
line.on_change_amount_second_currency()
self.assertEqual(line.debit, Decimal('106.44'))
self.assertEqual(line.credit, Decimal(0))
self.assertEqual(line.rate, Decimal('0.939506'))
self.assertEqual(line.get_rate(), Decimal('0.939496'))
line = Line(
account=expense,
credit=Decimal('7.61'),
second_currency=second_currency,
amount_second_currency=Decimal('-108.10'))
line.on_change_amount_second_currency()
self.assertEqual(line.debit, Decimal(0))
self.assertEqual(line.credit, Decimal('115.06'))
self.assertEqual(line.rate, Decimal('0.939506'))
self.assertEqual(line.get_rate(), Decimal('0.939510'))
line.debit = line.credit = Decimal(0)
line.rate = Decimal('1.100000')
line.on_change_rate()
self.assertEqual(line.debit, Decimal(0))
self.assertEqual(line.credit, Decimal('98.27'))
self.assertEqual(line.rate, Decimal('1.100000'))
@with_transaction()
def test_account_type_amount(self):
"Test account type amount"
@@ -2097,121 +2049,5 @@ class AccountTestCase(
self.assertEqual(account.parent, new_account)
@with_transaction()
def test_general_ledger_detail_models_are_registered(self):
"Test General Ledger Detail models are registered with expected fields"
pool = Pool()
Detail = pool.get('account.general_ledger.detail')
DetailContext = pool.get('account.general_ledger.detail.context')
DetailReport = pool.get(
'account.general_ledger.detail.report', type='report')
for name in [
'row_type', 'account_code', 'transaction_currency',
'journal_entry_number', 'document_number',
'debit_base_currency', 'credit_base_currency',
'running_balance_base_currency',
'debit_transaction_currency',
'credit_transaction_currency',
'running_balance_transaction_currency', 'payable_qty']:
self.assertIn(name, Detail._fields)
for name in [
'debit_base_currency', 'credit_base_currency',
'balance_base_currency', 'running_balance_base_currency',
'debit_transaction_currency',
'credit_transaction_currency',
'balance_transaction_currency',
'running_balance_transaction_currency']:
self.assertEqual(Detail._fields[name].digits, (16, 2))
self.assertEqual(Detail._fields['payable_qty'].digits, (16, 5))
for name in [
'account', 'account_from', 'account_to', 'currency', 'party',
'document_number']:
self.assertIn(name, DetailContext._fields)
account = pool.get('account.account').__table__()
account_label = Detail._account_label(account)
self.assertIn('||', str(account_label))
self.assertNotIn(' + ', str(account_label))
self.assertEqual(account_label.params, ('', ' - ', ''))
self.assertEqual(
DetailReport.__name__, 'account.general_ledger.detail.report')
@with_transaction()
def test_general_ledger_detail_report_groups_by_account_currency(self):
"Test General Ledger Detail report groups lines by account and currency"
DetailReport = Pool().get(
'account.general_ledger.detail.report', type='report')
currency = type('Currency', (), {'id': 1})()
other_currency = type('Currency', (), {'id': 2})()
first = type('Line', (), {
'account_code': '1000 - Cash',
'account_name': 'Cash',
'transaction_currency': currency,
})()
second = type('Line', (), {
'account_code': '1000 - Cash',
'account_name': 'Cash',
'transaction_currency': currency,
})()
third = type('Line', (), {
'account_code': '2000 - Revenue',
'account_name': 'Revenue',
'transaction_currency': other_currency,
})()
groups = DetailReport._groups([first, second, third])
self.assertEqual(len(groups), 2)
self.assertEqual(groups[0]['account'], '1000 - Cash')
self.assertEqual(groups[0]['currency'], currency)
self.assertEqual(groups[0]['lines'], [first, second])
self.assertEqual(groups[1]['account'], '2000 - Revenue')
self.assertEqual(groups[1]['currency'], other_currency)
self.assertEqual(groups[1]['lines'], [third])
@with_transaction()
def test_general_ledger_detail_date_contexts(self):
"Test General Ledger Detail opening and movement date contexts"
Detail = Pool().get('account.general_ledger.detail')
context = {
'from_date': datetime.date(2026, 1, 10),
'to_date': datetime.date(2026, 1, 31),
}
self.assertEqual(Detail._query_context('opening', context), {
'periods': None,
'from_date': None,
'to_date': datetime.date(2026, 1, 9),
})
self.assertEqual(Detail._query_context('movement', context), {
'periods': None,
'from_date': datetime.date(2026, 1, 10),
'to_date': datetime.date(2026, 1, 31),
})
self.assertEqual(Detail._query_context('closing', context), {
'periods': None,
'from_date': None,
'to_date': datetime.date(2026, 1, 31),
})
@with_transaction()
def test_general_ledger_detail_document_field_reference_string(self):
"Test General Ledger Detail document fields resolve reference strings"
pool = Pool()
Detail = pool.get('account.general_ledger.detail')
Party = pool.get('party.party')
party = Party(name='Customer')
party.save()
detail = Detail()
detail.origin = '%s,%s' % (Party.__name__, party.id)
self.assertIsNone(detail.get_document_field('document_number'))
self.assertEqual(detail.get_document_field('document_type'),
party.rec_name)
del ModuleTestCase

View File

@@ -1,41 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="fiscalyear"/>
<field name="fiscalyear"/>
<label name="company"/>
<field name="company"/>
<label name="journal"/>
<field name="journal" widget="selection"/>
<group col="-1" colspan="2" id="checkboxes">
<label name="posted"/>
<field name="posted"/>
</group>
<label name="start_period"/>
<field name="start_period"/>
<label name="end_period"/>
<field name="end_period"/>
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
<label name="account"/>
<field name="account"/>
<label name="currency"/>
<field name="currency"/>
<label name="account_from"/>
<field name="account_from"/>
<label name="account_to"/>
<field name="account_to"/>
<label name="party"/>
<field name="party"/>
<label name="document_number"/>
<field name="document_number"/>
</form>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="row_type"/>
<field name="account_code" expand="1"/>
<field name="transaction_currency"/>
<field name="base_currency" optional="1"/>
<field name="posting_date"/>
<field name="journal"/>
<field name="journal_entry_number"/>
<field name="document_number"/>
<field name="voucher_number" optional="1"/>
<field name="document_type" optional="1"/>
<field name="document_date" optional="1"/>
<field name="posting_status" optional="1"/>
<field name="party" optional="1"/>
<field name="description" expand="1" optional="1"/>
<field name="move_description" expand="1" optional="1"/>
<field name="reference" optional="1"/>
<field name="origin" optional="1"/>
<field name="debit_base_currency" sum="1"/>
<field name="credit_base_currency" sum="1"/>
<field name="balance_base_currency" sum="1"/>
<field name="running_balance_base_currency" optional="1"/>
<field name="debit_transaction_currency" sum="1" optional="1"/>
<field name="credit_transaction_currency" sum="1" optional="1"/>
<field name="balance_transaction_currency" sum="1" optional="1"/>
<field name="running_balance_transaction_currency" optional="1"/>
<field name="payable_qty" sum="1" optional="1"/>
<field name="uom" optional="1"/>
<field name="entered_by" optional="1"/>
<field name="entered_date" optional="1"/>
<field name="modified_by" optional="1"/>
<field name="modified_date" optional="1"/>
<field name="move" optional="1"/>
<field name="move_line" optional="1"/>
<field name="company" optional="1"/>
</tree>

View File

@@ -26,8 +26,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="amount_second_currency" symbol=""/>
<label name="second_currency"/>
<field name="second_currency"/>
<label name="rate"/>
<field name="rate"/>
</page>
<page name="tax_lines">
<field name="tax_lines" colspan="4"/>

View File

@@ -1,15 +0,0 @@
# 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.pool import Pool
from . import account
def register():
Pool.register(
account.AccountTemplate,
module='account_ch_os', type_='model')
Pool.register(
account.CreateChart,
module='account_ch_os', type_='wizard')

View File

@@ -1,27 +0,0 @@
# 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.pool import Pool, PoolMeta
class AccountTemplate(metaclass=PoolMeta):
__name__ = 'account.account.template'
class CreateChart(metaclass=PoolMeta):
__name__ = 'account.create_chart'
def default_properties(self, fields):
pool = Pool()
ModelData = pool.get('ir.model.data')
defaults = super().default_properties(fields)
try:
template_id = ModelData.get_id('account_ch_os', 'root')
except KeyError:
return defaults
if self.account.account_template.id == template_id:
defaults['account_receivable'] = self.get_account(
'account_ch_os.acct_110000')
defaults['account_payable'] = self.get_account(
'account_ch_os.acct_200000')
return defaults

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -1,12 +0,0 @@
# 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.tests.test_tryton import ModuleTestCase
class AccountCHOSTestCase(ModuleTestCase):
'Test Account CH OS module'
module = 'account_ch_os'
del ModuleTestCase

View File

@@ -1,6 +0,0 @@
[tryton]
version=7.2.3
depends:
account
xml:
account_ch_os.xml

File diff suppressed because it is too large Load Diff

View File

@@ -485,7 +485,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
})
cls.__rpc__.update({
'post': RPC(
readonly=False, instantiate=0, fresh_session=False),
readonly=False, instantiate=0, fresh_session=True),
})
@classmethod
@@ -515,9 +515,7 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
~((table.state == 'cancelled') & (table.number == Null)),
CharLength(table.number), table.number]
@fields.depends(
'selection_rate', 'rate_date', 'currency', 'company', 'invoice_date',
'lines')
@fields.depends('selection_rate','rate_date')
def on_change_with_rate(self, name=None):
return self.get_selected_rate()
@@ -530,8 +528,8 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
def get_selected_rate(self,name=None):
Currency = Pool().get('currency.currency')
Date = Pool().get('ir.date')
company = getattr(self, 'company', None)
currency = getattr(self, 'currency', None)
company = self.company
currency = self.currency
if not currency or not company:
return None
@@ -565,16 +563,6 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
if rate:
return round(1/rate,6) or 1
def _compute_company_currency_amount(self, amount, date=None):
Currency = Pool().get('currency.currency')
if self.currency == self.company.currency:
return amount
if self.rate:
return self.company.currency.round(amount / self.rate)
with Transaction().set_context(date=date or self.currency_date):
return Currency.compute(
self.currency, amount, self.company.currency)
@staticmethod
def default_type():
return Transaction().context.get('type', 'out')
@@ -1230,7 +1218,6 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
logger.info("_GET_MOVE_LINE_ACCOUNT:%s",line.account)
if self.account.party_required:
@@ -1299,16 +1286,14 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
remainder = sum(l.debit - l.credit for l in move_lines)
if self.payment_term:
payment_date = self.payment_term_date or self.invoice_date or today
origin = self.lines[0].origin if self.lines and self.lines[0].origin else None
model = str(origin).split(",")[0] if origin else None
model = str(self.lines[0].origin).split(",")[0] if self.lines[0].origin else None
logger.info("MODEL:%s",model)
line = None
if model:
Line = Pool().get(model)
line = Line(int(str(origin).split(",")[1]))
line = Line(int(str(self.lines[0].origin).split(",")[1]))
logger.info("LINE:%s",line)
term_lines = self.payment_term.compute(
self.total_amount, self.currency, payment_date, line)
term_lines = self.payment_term.compute(
self.total_amount, self.currency, payment_date, line)
else:
term_lines = [(self.payment_term_date or today, self.total_amount)]
past_payment_term_dates = []
@@ -1905,17 +1890,17 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
cls._check_taxes(invoices)
# cls._check_similar(invoices)
cls.set_number(invoices)
invoices_in = cls.browse([i for i in invoices if i.type == 'in'])
cls.set_number(invoices_in)
cls._store_cache(invoices)
moves = []
for invoice in invoices:
if invoice.currency != invoice.company.currency and not invoice.rate:
invoice.rate = invoice.get_selected_rate()
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
if invoice.type == 'in':
move = invoice.get_move()
if move != invoice.move:
invoice.move = move
moves.append(move)
invoice.do_lot_invoicing()
if moves:
Move.save(moves)
@@ -1978,35 +1963,23 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
move_line_.amount_second_currency = -original_amount
if amount < 0:
lot_has_sale_invoice = (
move_line.lot
and (move_line.lot.sale_invoice_line
or move_line.lot.sale_invoice_line_prov))
move_line.debit = Decimal(0)
move_line.credit = -amount
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
move_line_.credit = Decimal(0)
move_line_.debit = -amount
move_line_.account = gl.product.account_stock_in_used
else:
lot_has_sale_invoice = (
move_line.lot
and (move_line.lot.sale_invoice_line
or move_line.lot.sale_invoice_line_prov))
move_line.debit = amount
move_line.credit = Decimal(0)
move_line.account = gl.product.account_stock_used if not lot_has_sale_invoice else gl.product.account_stock_out_used
move_line.account = gl.product.account_stock_used if not (move_line.lot.sale_invoice_line or move_line.lot.sale_invoice_line_prov) else gl.product.account_stock_out_used
move_line.account = gl.product.account_cogs_used if gl.fee else move_line.account
move_line_.debit = Decimal(0)
move_line_.credit = amount
move_line_.account = gl.product.account_stock_in_used
move_lines = [move_line,move_line_]
if getattr(move_line, 'amount_second_currency', None):
move_line.rate = move_line.get_rate()
if getattr(move_line_, 'amount_second_currency', None):
move_line_.rate = move_line_.get_rate()
if drop:
drop_line = AccountMoveLine()
@@ -2044,10 +2017,6 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
drop_line_.second_currency = self.currency
drop_line_.amount_second_currency = -original_amount
move_lines = [drop_line,drop_line_]
if getattr(drop_line, 'amount_second_currency', None):
drop_line.rate = drop_line.get_rate()
if getattr(drop_line_, 'amount_second_currency', None):
drop_line_.rate = drop_line_.get_rate()
return move_lines
@@ -2069,16 +2038,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
var_qt = sum([i.quantity for i in gl])
logger.info("LOT_TO_PROCESS:%s",lot)
logger.info("FEE_TO_PROCESS:%s",gl[0].fee)
fee_correction = (
gl[0].fee
and any(Decimal(str(i.quantity or 0)) < 0 for i in gl)
and any(Decimal(str(i.quantity or 0)) > 0 for i in gl))
if (gl[0].fee and not gl[0].product.landed_cost
and not fee_correction):
if (gl[0].fee and not gl[0].product.landed_cost):
diff = gl[0].fee.amount - gl[0].fee.get_non_cog(lot)
account_move = gl[0].fee._get_account_move_fee(lot,'in',diff)
if account_move:
Move.save([account_move])
Move.save([account_move])
if (lot and not gl[0].fee) or (gl[0].fee and gl[0].product.landed_cost):
adjust_move_lines = []
mov = None
@@ -3211,6 +3174,7 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
def _compute_taxes(self):
pool = Pool()
Currency = pool.get('currency.currency')
TaxLine = pool.get('account.tax.line')
tax_lines = []
@@ -3219,7 +3183,11 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
taxes = self._get_taxes().values()
for tax in taxes:
amount = tax['base']
amount = self.invoice._compute_company_currency_amount(amount)
with Transaction().set_context(
date=self.invoice.currency_date):
amount = Currency.compute(
self.invoice.currency, amount,
self.invoice.company.currency)
tax_line = TaxLine()
tax_line.amount = amount
tax_line.type = 'base'
@@ -3284,7 +3252,6 @@ class InvoiceLine(sequence_ordered(), ModelSQL, ModelView, TaxableMixin):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
logger.info("GET_MOVE_LINES_ACCOUNT:%s",line.account)
if self.account.party_required:
@@ -3544,7 +3511,9 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
'''
Return a list of move lines instances for invoice tax
'''
Currency = Pool().get('currency.currency')
pool = Pool()
Currency = pool.get('currency.currency')
MoveLine = pool.get('account.move.line')
TaxLine = pool.get('account.tax.line')
line = MoveLine()
@@ -3552,8 +3521,11 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
return []
line.description = self.description
if self.invoice.currency != self.invoice.company.currency:
amount = self.invoice._compute_company_currency_amount(self.amount)
base = self.invoice._compute_company_currency_amount(self.base)
with Transaction().set_context(date=self.invoice.currency_date):
amount = Currency.compute(self.invoice.currency, self.amount,
self.invoice.company.currency)
base = Currency.compute(self.invoice.currency, self.base,
self.invoice.company.currency)
line.amount_second_currency = self.amount
line.second_currency = self.invoice.currency
else:
@@ -3575,7 +3547,6 @@ class InvoiceTax(sequence_ordered(), ModelSQL, ModelView):
line.amount_second_currency = (
line.amount_second_currency.copy_sign(
line.debit - line.credit))
line.rate = line.get_rate()
line.account = self.account
if self.account.party_required:
line.party = self.invoice.party

View File

@@ -288,7 +288,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.action.report" id="report_invoice">
<field name="name">Invoice</field>
<field name="name">Provisional Invoice</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/invoice.fodt</field>
@@ -314,7 +314,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.action.report" id="report_invoice_ict_final">
<field name="name">CN/DN</field>
<field name="name">Final Invoice</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/invoice_ict_final.fodt</field>
@@ -326,45 +326,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="action" ref="report_invoice_ict_final"/>
</record>
<record model="ir.action.report" id="report_invoice_ict_commission_final">
<field name="name">CN/DN Commission</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/commission__ict_final.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_invoice_ict_commission_final_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_invoice_ict_commission_final"/>
</record>
<record model="ir.action.report" id="report_sale_commission_ict">
<field name="name">Commission invoice Sale</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/sale_commission_ict.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_sale_commission_ict_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_sale_commission_ict"/>
</record>
<record model="ir.action.report" id="report_purchase_commission_ict">
<field name="name">Commission invoice Purchase</field>
<field name="model">account.invoice</field>
<field name="report_name">account.invoice</field>
<field name="report">account_invoice/purchase_commission_ict.fodt</field>
<field name="single" eval="True"/>
</record>
<record model="ir.action.keyword" id="report_purchase_commission_ict_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="report_purchase_commission_ict"/>
</record>
<record model="ir.sequence.type" id="sequence_type_account_invoice">
<field name="name">Invoice</field>
</record>

View File

@@ -2,19 +2,19 @@
<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</dc:title>
<dc:title>Provisional Invoice</dc:title>
<meta:initial-creator>willen</meta:initial-creator>
<meta:generator>LibreOffice/7.6.0.3$Windows_X86_64 LibreOffice_project/69edd8b8ebc41d00b4de3915dc82f8f0fc3b6265</meta:generator>
<meta:editing-duration>PT3H20M59S</meta:editing-duration>
<meta:editing-cycles>12</meta:editing-cycles>
<meta:editing-duration>PT3H16M29S</meta:editing-duration>
<meta:editing-cycles>9</meta:editing-cycles>
<dc:date>2026-04-10T10:30:16.567000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="66" meta:word-count="168" meta:character-count="2416" meta:non-whitespace-character-count="1896"/></office:meta>
<dc:date>2026-03-27T07:35:45.157000000</dc:date><meta:document-statistic meta:table-count="10" meta:image-count="2" meta:object-count="0" meta:page-count="2" meta:paragraph-count="61" meta:word-count="210" meta:character-count="2997" meta:non-whitespace-character-count="2429"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">6879</config:config-item>
<config:config-item config:name="ViewAreaTop" config:type="long">28575</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">27264</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">13187</config:config-item>
@@ -23,12 +23,12 @@
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">13275</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">15563</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">7583</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">33203</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">6879</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">28575</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">27263</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">20064</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">41760</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
@@ -101,7 +101,7 @@
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="Rsid" config:type="int">523469</config:config-item>
<config:config-item config:name="Rsid" config:type="int">297701</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">84510</config:config-item>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
@@ -168,20 +168,19 @@
<style:font-face style:name="Lucida Sans1" svg:font-family="&apos;Lucida Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Microsoft YaHei" svg:font-family="&apos;Microsoft YaHei&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="Tahoma" svg:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman1" svg:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Times New Roman" svg:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="true"/>
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:paragraph-properties style:text-autospace="none" style:line-break="strict" loext:tab-stop-distance="0cm" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="none" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.249cm" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman1" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman1" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman1" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Times New Roman" fo:font-size="12pt" fo:language="fr" fo:country="CH" style:letter-kerning="true" style:font-name-asian="Times New Roman" style:font-size-asian="12pt" style:language-asian="fr" style:country-asian="CH" style:font-name-complex="Times New Roman" style:font-size-complex="12pt" style:language-complex="fr" style:country-complex="CH" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
@@ -246,7 +245,7 @@
</style:style>
<style:style style:name="No_20_Spacing" style:display-name="No Spacing" style:family="paragraph">
<style:paragraph-properties fo:margin-left="0cm" fo:margin-right="0cm" fo:text-align="start" style:justify-single-word="false" fo:text-indent="0cm" style:auto-text-indent="false" style:text-autospace="ideograph-alpha" style:vertical-align="auto"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="11pt" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="11pt" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="11pt" style:language-complex="ar" style:country-complex="SA"/>
</style:style>
<style:style style:name="Header_20_and_20_Footer" style:display-name="Header and Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
@@ -296,35 +295,30 @@
</style:paragraph-properties>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="Normal" style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit"/>
<style:text-properties fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="no-limit" loext:hyphenation-zone="no-limit"/>
</style:style>
<style:style style:name="Default_20_Paragraph_20_Font" style:display-name="Default Paragraph Font" style:family="text"/>
<style:style style:name="Titolo_20_1_20_Carattere" style:display-name="Titolo 1 Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Arial1" fo:font-family="Arial, Arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="12pt" fo:language="en" fo:country="US" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color" fo:font-weight="bold" style:font-name-asian="Arial1" style:font-family-asian="Arial, Arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="12pt" style:language-asian="en" style:country-asian="US" style:font-weight-asian="bold" style:font-name-complex="Arial1" style:font-family-complex="Arial, Arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="12pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Intestazione_20_Carattere" style:display-name="Intestazione Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
<style:text-properties style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="10pt" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="Placeholder_20_Text" style:display-name="Placeholder Text" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
<style:text-properties fo:color="#808080" loext:opacity="100%" style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Testo_20_fumetto_20_Carattere" style:display-name="Testo fumetto Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Tahoma" fo:font-family="Tahoma, arial" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="8pt" style:font-name-asian="Tahoma" style:font-family-asian="Tahoma, arial" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="8pt" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma, arial" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="8pt"/>
</style:style>
<style:style style:name="PiÃ_a8__20_di_20_pagina_20_Carattere" style:display-name="Piè di pagina Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Times New Roman1" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman1" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
<style:text-properties style:font-name="Times New Roman" fo:font-family="&apos;Times New Roman&apos;, Times" style:font-family-generic="roman" style:font-pitch="variable" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:font-name-complex="Times New Roman" style:font-family-complex="&apos;Times New Roman&apos;, Times" style:font-family-generic-complex="roman" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Nessuna_20_spaziatura_20_Carattere" style:display-name="Nessuna spaziatura Carattere" style:family="text" style:parent-style-name="Default_20_Paragraph_20_Font">
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman1" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
<style:text-properties style:font-name="Calibri1" fo:font-family="Calibri, &apos;Century Gothic&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:language="it" fo:country="IT" style:font-name-asian="Times New Roman" style:font-family-asian="&apos;Times New Roman&apos;, Times" style:font-family-generic-asian="roman" style:font-pitch-asian="variable" style:language-asian="en" style:country-asian="US" style:font-name-complex="Calibri1" style:font-family-complex="Calibri, &apos;Century Gothic&apos;" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" loext:opacity="100%" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Police_20_par_20_dÃ_a9_faut" style:display-name="Police par défaut" style:family="text"/>
<style:style style:name="Default_20_Paragraph_20_Font_20__28_WW_29_" style:display-name="Default Paragraph Font (WW)" style:family="text"/>
<style:style style:name="Police_20_par_20_défaut" style:display-name="Police par défaut" style:family="text"/>
<style:style style:name="Frame" style:family="graphic">
<style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" draw:fill-color="#cfe7f5"/>
</style:style>
@@ -670,7 +664,7 @@
</style:style>
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -690,7 +684,7 @@
</style:style>
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="US" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -702,7 +696,7 @@
</style:style>
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="center" style:justify-single-word="false" style:writing-mode="lr-tb"/>
@@ -714,7 +708,7 @@
</style:style>
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Times New Roman" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" fo:text-align="end" style:justify-single-word="false" style:writing-mode="lr-tb"/>
@@ -730,7 +724,7 @@
</style:style>
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman1" style:font-size-complex="1pt" text:display="none"/>
<style:text-properties fo:font-size="1pt" fo:language="en" fo:country="US" style:font-size-asian="1pt" style:font-name-complex="Times New Roman" style:font-size-complex="1pt" text:display="none"/>
</style:style>
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
@@ -765,34 +759,14 @@
</style:paragraph-properties>
<style:text-properties officeooo:paragraph-rsid="00022035"/>
</style:style>
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="10pt" style:font-name-complex="Arial" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties officeooo:paragraph-rsid="00056da2"/>
</style:style>
<style:style style:name="P47" style:family="paragraph" style:parent-style-name="Normal">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%"/>
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" officeooo:paragraph-rsid="00056da2" style:font-size-asian="10pt" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P48" style:family="paragraph" style:parent-style-name="Standard">
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties style:font-name="Calibri" fo:font-size="10pt" fo:language="fr" fo:country="CH" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
<style:text-properties fo:font-size="10pt" fo:language="en" fo:country="GB" officeooo:paragraph-rsid="000391f3" style:font-size-asian="10pt" style:font-name-complex="Arial1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P49" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false" fo:line-height="100%" style:writing-mode="lr-tb"/>
<style:text-properties fo:font-size="10pt" style:font-size-asian="10pt" style:font-name-complex="Times New Roman1" style:font-size-complex="10pt"/>
</style:style>
<style:style style:name="P50" style:family="paragraph" style:parent-style-name="footer">
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="footer">
<style:paragraph-properties style:writing-mode="lr-tb"/>
</style:style>
<style:style style:name="P51" style:family="paragraph" style:parent-style-name="header">
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="header">
<style:paragraph-properties style:writing-mode="lr-tb"/>
</style:style>
<style:style style:name="T1" style:family="text">
@@ -816,24 +790,6 @@
<style:style style:name="T7" style:family="text">
<style:text-properties fo:language="fr" fo:country="CH"/>
</style:style>
<style:style style:name="T8" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T9" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Arial" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T10" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T11" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" fo:font-size="9pt" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-size-asian="9pt" style:font-name-complex="Arial" style:font-size-complex="9pt"/>
</style:style>
<style:style style:name="T12" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
</style:style>
<style:style style:name="T13" style:family="text">
<style:text-properties fo:color="#000000" loext:opacity="100%" officeooo:rsid="00056da2" style:letter-kerning="false" style:font-name-asian="Times New Roman" style:font-name-complex="Arial"/>
</style:style>
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
<style:graphic-properties style:run-through="background" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="top" style:vertical-rel="baseline" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
</style:style>
@@ -3910,7 +3866,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="P22"/>
<text:p text:style-name="P22">Invoice</text:p>
<text:p text:style-name="P22">Provisional Invoice</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau3.A1" office:value-type="string">
<text:p text:style-name="P22"/>
@@ -3974,19 +3930,15 @@
<text:p text:style-name="P25">Goods description</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau5.A1" office:value-type="string">
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text">&lt;invoice.report_lbs_display&gt;</text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text">&lt;invoice.report_net_display&gt;</text:placeholder> <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder>)</text:p>
<text:p text:style-name="P26"/>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_description_upper or invoice.report_product_description&gt;</text:placeholder><text:placeholder text:placeholder-type="text">&lt;&apos; CROP &apos; + invoice.report_crop_name if invoice.report_crop_name else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P26">QUANTITY: <text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/>LBS (<text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != &apos;&apos; else &apos;&apos;&gt;</text:placeholder> MTS)</text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_description_upper or invoice.report_product_description&gt;</text:placeholder><text:s/>CROP <text:placeholder text:placeholder-type="text">&lt;invoice.report_crop_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P21"><text:placeholder text:placeholder-type="text">&lt;invoice.report_attributes_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;block in invoice.report_trade_blocks&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;block[0]&gt;</text:placeholder></text:p>
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text">&lt;block[1]&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"/>
<text:p text:style-name="P26"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P18">At <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_currency_upper&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_value&gt;</text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_unit_upper&gt;</text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_price_words&gt;</text:placeholder>) <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_pricing_text&gt;</text:placeholder></text:p>
<text:p text:style-name="P18"/>
<text:p text:style-name="P18"/>
<text:p text:style-name="P29"><text:span text:style-name="T7"><text:placeholder text:placeholder-type="text">&lt;invoice.report_incoterm&gt;</text:placeholder></text:span><text:span text:style-name="T3"><text:s/></text:span></text:p>
<text:p text:style-name="P26"/>
<text:p text:style-name="P32"><text:placeholder text:placeholder-type="text">&lt;invoice.report_incoterm&gt;</text:placeholder></text:p>
<text:p text:style-name="P29"><text:span text:style-name="T1">ALL DETAILS AND SPECIFICATIONS AS PER</text:span> <text:span text:style-name="T3">BENEFICIARY </text:span></text:p>
<text:p text:style-name="P26">PROFORMA INVOICE NO. <text:placeholder text:placeholder-type="text">&lt;invoice.report_proforma_invoice_number&gt;</text:placeholder><text:s/>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="P26"/>
<text:p text:style-name="P12"/>
</table:table-cell>
@@ -4000,10 +3952,10 @@
<text:p text:style-name="P15"><text:s text:c="19"/>BALES</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P41"><text:s text:c="13"/>Gross <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder></text:p>
<text:p text:style-name="P41"><text:s text:c="13"/>Gross KGS</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P15"><text:s text:c="13"/>NET <text:placeholder text:placeholder-type="text">&lt;invoice.report_weight_unit_upper&gt;</text:placeholder></text:p>
<text:p text:style-name="P15"><text:s text:c="13"/>NET KGS</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A1" office:value-type="string">
<text:p text:style-name="P41"><text:s text:c="10"/></text:p>
@@ -4017,10 +3969,10 @@
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_nb_bale&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;invoice.report_gross_display&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P15"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_gross, invoice.party.lang) if invoice.report_gross != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text">&lt;invoice.report_net_display&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P30"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_net, invoice.party.lang) if invoice.report_net != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau6.A2" office:value-type="string">
<text:p text:style-name="P16"/>
@@ -4044,7 +3996,7 @@
<text:p text:style-name="P16">Equivalent to LBS</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;invoice.report_lbs_display&gt;</text:placeholder><text:s/></text:p>
<text:p text:style-name="P16"><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_lbs, invoice.party.lang) if invoice.report_lbs != &apos;&apos; else &apos;&apos;&gt;</text:placeholder><text:s/></text:p>
</table:table-cell>
<table:table-cell table:style-name="Tableau7.A1" office:value-type="string">
<text:p text:style-name="P20"/>
@@ -4057,18 +4009,13 @@
<table:table-column table:style-name="Tableau8.B"/>
<table:table-row table:style-name="Tableau8.1">
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in invoice.report_rate_lines.splitlines()&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P14">At <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_currency_upper&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_value&gt;</text:placeholder><text:s/>PER <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_unit_upper&gt;</text:placeholder><text:s/>(<text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_price_words&gt;</text:placeholder>) <text:placeholder text:placeholder-type="text">&lt;invoice.report_rate_pricing_text&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"/>
<text:p text:style-name="P14">FREIGHT VALUE: <text:placeholder text:placeholder-type="text">&lt;invoice.report_freight_currency_symbol&gt;</text:placeholder><text:s/><text:placeholder text:placeholder-type="text">&lt;format_number(invoice.report_freight_amount, invoice.party.lang) if invoice.report_freight_amount != &apos;&apos; else &apos;&apos;&gt;</text:placeholder></text:p>
<text:p text:style-name="P14"/>
<text:p text:style-name="P17">WE CERTIFY THAT THE MERCHANDISE IS OF <text:span text:style-name="T7"><text:placeholder text:placeholder-type="text">&lt;invoice.report_origin or &apos;&apos;&gt;</text:placeholder></text:span><text:s/>ORIGIN</text:p>
<text:p text:style-name="P17"/>
<text:p text:style-name="P47"><text:span text:style-name="T12">B</text:span><text:span text:style-name="T13">ANK</text:span><text:span text:style-name="T12">: EFG BANK SA</text:span></text:p>
<text:p text:style-name="P45">IBAN : CH8808667007168111027</text:p>
<text:p text:style-name="P45">SwifT Code: EFGBCHZZ</text:p>
<text:p text:style-name="P48"/>
<text:p text:style-name="P31"><text:soft-page-break/>L/C NUMBER </text:p>
<text:p text:style-name="P31"/>
</table:table-cell>
<table:table-cell table:style-name="Tableau8.A1" office:value-type="string">
<text:p text:style-name="P34"><text:s text:c="3"/><text:placeholder text:placeholder-type="text">&lt;format_currency(invoice.total_amount, invoice.party.lang, invoice.currency)&gt;</text:placeholder><text:s/></text:p>
@@ -4080,7 +4027,6 @@
<text:p text:style-name="P11"/>
<table:table table:name="Tableau9" table:style-name="Tableau9">
<table:table-column table:style-name="Tableau9.A"/>
<text:soft-page-break/>
<table:table-row table:style-name="Tableau9.1">
<table:table-cell table:style-name="Tableau9.A1" office:value-type="string">
<text:p text:style-name="P21">NET LANDED WEIGHTS, ACTUAL TARE, NO FRANCHISE</text:p>
@@ -4106,7 +4052,7 @@
<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="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_reference&gt;</text:placeholder></text:p>
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_si_number&gt;</text:placeholder></text:p>
<text:p text:style-name="P25"/>
<text:p text:style-name="P25"><text:placeholder text:placeholder-type="text">&lt;invoice.report_controller_name&gt;</text:placeholder></text:p>
</table:table-cell>
@@ -4154,4 +4100,4 @@
<text:p text:style-name="P38"/>
</office:text>
</office:body>
</office:document>
</office:document>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
import datetime
from decimal import Decimal
from unittest.mock import Mock, patch
from trytond.modules.account_invoice.exceptions import (
PaymentTermValidationError)
@@ -252,70 +251,5 @@ class AccountInvoiceTestCase(
(datetime.date(2012, 1, 14), Decimal('-1.0')),
])
def test_post_rpc_does_not_require_fresh_session(self):
'posting invoices does not force a fresh session'
Invoice = Pool().get('account.invoice')
self.assertFalse(Invoice.__rpc__['post'].fresh_session)
@with_transaction()
def test_validate_invoice_creates_move_for_customer_invoice(self):
'validating customer invoices now creates the account move'
Invoice = Pool().get('account.invoice')
move = Mock()
invoice = Invoice()
invoice.type = 'out'
invoice.move = None
invoice.get_move = Mock(return_value=move)
invoice.do_lot_invoicing = Mock()
move_model = Mock()
with patch.object(Invoice, '_check_taxes'), patch.object(
Invoice, '_store_cache'), patch.object(
Invoice, 'browse', return_value=[]), patch.object(
Invoice, 'cleanMoves') as clean_moves, patch.object(
Invoice, 'save') as save_invoices, patch(
'trytond.modules.account_invoice.invoice.Pool'
) as PoolMock:
PoolMock.return_value.get.return_value = move_model
Invoice.validate_invoice([invoice])
self.assertIs(invoice.move, move)
invoice.get_move.assert_called_once_with()
invoice.do_lot_invoicing.assert_called_once_with()
move_model.save.assert_called_once_with([move])
clean_moves.assert_called_once_with([move])
save_invoices.assert_called()
@with_transaction()
def test_validate_invoice_sets_number_for_customer_invoice(self):
'validating customer invoices now assigns the invoice number'
Invoice = Pool().get('account.invoice')
move = Mock()
invoice = Invoice()
invoice.type = 'out'
invoice.move = None
invoice.get_move = Mock(return_value=move)
invoice.do_lot_invoicing = Mock()
move_model = Mock()
with patch.object(Invoice, '_check_taxes'), patch.object(
Invoice, '_store_cache'), patch.object(
Invoice, 'set_number') as set_number, patch.object(
Invoice, 'cleanMoves'), patch.object(
Invoice, 'save'), patch(
'trytond.modules.account_invoice.invoice.Pool'
) as PoolMock:
PoolMock.return_value.get.return_value = move_model
Invoice.validate_invoice([invoice])
set_number.assert_called_once_with([invoice])
del ModuleTestCase

View File

@@ -181,11 +181,7 @@ class InvoiceLine(metaclass=PoolMeta):
company=self.invoice.company.id, date=accounting_date):
anglo_saxon_move_lines = self._get_anglo_saxon_move_lines(
cost, type_)
lot_has_sale_invoice = (
self.lot
and (self.lot.sale_invoice_line_prov
or self.lot.sale_invoice_line))
if type_ == 'in_supplier' and lot_has_sale_invoice and not self.fee:
if type_ == 'in_supplier' and (self.lot.sale_invoice_line_prov or self.lot.sale_invoice_line) and not self.fee:
anglo_saxon_move_lines_ = self._get_anglo_saxon_move_lines(cost, 'out_customer')
result.extend(anglo_saxon_move_lines)
result.extend(anglo_saxon_move_lines_)

View File

@@ -2,8 +2,8 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook/page[@id='invoice']" position="before">
<page string="Analytics" id="analytic_accounts">
<xpath expr="/form/notebook" position="inside">
<page string="Analytic" id="analytic_accounts">
<field name="analytic_accounts" colspan="4"/>
</page>
</xpath>

View File

@@ -1,16 +1,11 @@
from trytond.pool import Pool
from . import automation,rules,freight_booking,booking_report,cron #, document
def register():
Pool.register(
automation.AutomationDocument,
rules.AutomationRuleSet,
freight_booking.FreightBookingInfo,
booking_report.FreightBookingShipmentReport,
booking_report.FreightBookingShipmentReportLinkStart,
cron.Cron,
cron.AutomationCron,
module='automation', type_='model')
Pool.register(
booking_report.FreightBookingShipmentReportLink,
module='automation', type_='wizard')
from trytond.pool import Pool
from . import automation,rules,freight_booking,cron #, document
def register():
Pool.register(
automation.AutomationDocument,
rules.AutomationRuleSet,
freight_booking.FreightBookingInfo,
cron.Cron,
cron.AutomationCron,
module='automation', type_='model')

View File

@@ -10,7 +10,6 @@ import io
import logging
import json
import re
import traceback
logger = logging.getLogger(__name__)
@@ -63,57 +62,25 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
@ModelView.button
def run_ocr(cls, docs):
for doc in docs:
logger.info(
"RUN_OCR_START doc=%s type=%s state=%s incoming=%s",
getattr(doc, 'id', None), getattr(doc, 'type', None),
getattr(doc, 'state', None),
getattr(getattr(doc, 'document', None), 'id', None))
try:
if doc.type == 'weight_report':
# Décoder le fichier depuis le champ Binary
if not doc.document:
raise ValueError("No incoming document linked")
file_data = doc.document.data or b""
file_name = doc.document.name or "document"
url = "http://automation-service:8006/ocr"
logger.info(
"RUN_OCR_FILE doc=%s incoming=%s name=%s size=%s "
"first_20=%r last_20=%r",
getattr(doc, 'id', None), doc.document.id,
file_name, len(file_data), file_data[:20],
file_data[-20:])
logger.info(f"File size: {len(file_data)} bytes")
logger.info(f"First 20 bytes: {file_data[:20]}")
logger.info(f"Last 20 bytes: {file_data[-20:]}")
if not file_data:
raise ValueError("Incoming document has no binary data")
file_name = doc.document.name or "document"
# Envoyer le fichier au service OCR
logger.info(
"RUN_OCR_HTTP_REQUEST doc=%s url=%s file_name=%s "
"size=%s",
getattr(doc, 'id', None), url, file_name,
len(file_data))
response = requests.post(
url,
files={"file": (file_name, io.BytesIO(file_data))},
timeout=120,
"http://automation-service:8006/ocr",
files={"file": (file_name, io.BytesIO(file_data))}
)
logger.info(
"RUN_OCR_HTTP_RESPONSE doc=%s status=%s reason=%s "
"elapsed=%s content_type=%s body_start=%r",
getattr(doc, 'id', None), response.status_code,
response.reason, response.elapsed,
response.headers.get('content-type'),
response.text[:1000])
response.raise_for_status()
data = response.json()
ocr_text = data.get("ocr_text", "") or ""
logger.info(
"RUN_OCR_JSON doc=%s keys=%s ocr_text_len=%s "
"ocr_text_start=%r",
getattr(doc, 'id', None), list(data.keys()),
len(ocr_text), ocr_text[:500])
doc.ocr_text = ocr_text
logger.info("RUN_OCR_RESPONSE:%s",data)
doc.ocr_text = data.get("ocr_text", "")
doc.state = "ocr_done"
doc.notes = (doc.notes or "") + "OCR done\n"
else:
@@ -145,17 +112,9 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
doc.notes = (doc.notes or "") + " SO updated"
except Exception as e:
logger.error(
"RUN_OCR_FAILED doc=%s type=%s error=%s traceback=%s",
getattr(doc, 'id', None), getattr(doc, 'type', None),
e, traceback.format_exc())
doc.state = "error"
doc.notes = (doc.notes or "") + f"OCR error: {e}\n"
doc.save()
logger.info(
"RUN_OCR_END doc=%s state=%s ocr_text_len=%s",
getattr(doc, 'id', None), getattr(doc, 'state', None),
len(getattr(doc, 'ocr_text', None) or ""))
# -------------------------------------------------------
# STRUCTURE (doctr)
# -------------------------------------------------------
@@ -289,10 +248,29 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
ShipmentWR.save([swr])
doc.notes = (doc.notes or "") + f"Shipment found: {sh[0].number}\n"
logger.info("BL_NUMBER:%s",sh[0].bl_number)
doc.notes = (
(doc.notes or "")
+ "Global WR linked to shipment. "
+ "Create remote lot WRs from the weight report form.\n")
if sh[0].incoming_moves:
factor_net = wr.net_landed_kg / wr.bales if wr.bales else 1
factor_gross = wr.gross_landed_kg / wr.bales if wr.bales else 1
for move in sh[0].incoming_moves:
lot = move.lot
if lot.lot_type == 'physic':
wr_payload = {
"chunk_key": lot.lot_chunk_key,
"gross_weight": float(round(Decimal(lot.lot_qt) * factor_gross,5)),
"net_weight": float(round(Decimal(lot.lot_qt) * factor_net,5)),
"tare_total": float(round(wr.tare_kg * (Decimal(lot.lot_qt) / wr.bales),5)) ,
"bags": int(lot.lot_qt),
"surveyor_code": sh[0].controller.get_alf(),
"place_key": sh[0].to_location.get_places(),
"report_date": int(wr.report_date.strftime("%Y%m%d")),#wr.report_date.isoformat() if wr.report_date else None,
"weight_date": int(wr.weight_date.strftime("%Y%m%d")),#wr.weight_date.isoformat() if wr.weight_date else None,
"agent": sh[0].agent.get_alf(),
"forwarder_ref": sh[0].returned_id
}
logger.info("PAYLOAD:%s",wr_payload)
data = doc.create_weight_report(wr_payload)
doc.notes = (doc.notes or "") + f"WR created in Fintrade: {data.get('success')}\n"
doc.notes = (doc.notes or "") + f"WR key: {data.get('weight_report_key')}\n"
# if cls.rule_set.ocr_required:[]
# cls.run_ocr([doc])
@@ -315,4 +293,4 @@ class AutomationDocument(ModelSQL, ModelView, Workflow):
# except Exception as e:
# doc.state = "error"
# doc.notes = (doc.notes or "") + f"Pipeline error: {e}\n"
doc.save()
doc.save()

View File

@@ -1,193 +0,0 @@
import json
from sql import Literal, Null
from sql.functions import CurrentTimestamp
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.wizard import Button, StateTransition, StateView, Wizard
from trytond.exceptions import UserError
class FreightBookingShipmentReport(ModelSQL, ModelView):
"Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report'
automation_document = fields.Many2One(
'automation.document', "Automation Document")
bl_number = fields.Function(fields.Char("BL Number"), 'get_bl_number')
shipment = fields.Function(fields.Many2One(
'stock.shipment.in', "Shipment"), 'get_shipment')
status = fields.Function(fields.Selection([
('found', "Found"),
('error', "Error"),
], "Status"), 'get_status')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('id', 'DESC'),
]
@classmethod
def table_query(cls):
AutomationDocument = Pool().get('automation.document')
document = AutomationDocument.__table__()
return document.select(
Literal(None).as_('create_uid'),
CurrentTimestamp().as_('create_date'),
Literal(None).as_('write_uid'),
Literal(None).as_('write_date'),
document.id.as_('id'),
document.id.as_('automation_document'),
where=(
(document.type == 'weight_report')
& (document.metadata_json != Null)))
@classmethod
def _extract_bl_number(cls, metadata_json):
if not metadata_json:
return None
try:
metadata = json.loads(str(metadata_json))
except (TypeError, ValueError):
return None
def find_value(value):
if isinstance(value, dict):
for key in (
'bl_no', 'bl_number', 'bl', 'BL_Number',
'BL number', 'B/L No'):
bl_number = value.get(key)
if bl_number:
return str(bl_number).strip()
for item in value.values():
bl_number = find_value(item)
if bl_number:
return bl_number
elif isinstance(value, list):
for item in value:
bl_number = find_value(item)
if bl_number:
return bl_number
return None
return find_value(metadata)
@classmethod
def _get_report_data(cls, reports):
ShipmentIn = Pool().get('stock.shipment.in')
bl_numbers = {}
for report in reports:
document = report.automation_document
bl_number = cls._extract_bl_number(
document.metadata_json if document else None)
bl_numbers[report.id] = bl_number
shipments_by_bl = {}
for bl_number in {b for b in bl_numbers.values() if b}:
shipments = ShipmentIn.search([
('bl_number', '=', bl_number),
], limit=1)
if not shipments:
shipments = ShipmentIn.search([
('bl_number', 'ilike', bl_number),
], limit=1)
shipments_by_bl[bl_number] = shipments[0] if shipments else None
return bl_numbers, shipments_by_bl
@classmethod
def get_bl_number(cls, reports, name):
bl_numbers, _ = cls._get_report_data(reports)
return bl_numbers
@classmethod
def get_shipment(cls, reports, name):
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
return {
report.id: (
shipments_by_bl[bl_numbers[report.id]].id
if bl_numbers[report.id]
and shipments_by_bl.get(bl_numbers[report.id])
else None)
for report in reports
}
@classmethod
def get_status(cls, reports, name):
bl_numbers, shipments_by_bl = cls._get_report_data(reports)
return {
report.id: (
'found'
if bl_numbers[report.id]
and shipments_by_bl.get(bl_numbers[report.id])
else 'error')
for report in reports
}
class FreightBookingShipmentReportLinkStart(ModelView):
"Link Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report.link.start'
bl_number = fields.Char("BL Number", readonly=True)
shipment = fields.Many2One(
'stock.shipment.in', "Shipment", required=True)
class FreightBookingShipmentReportLink(Wizard):
"Link Freight Booking Shipment Report"
__name__ = 'freight.booking.shipment.report.link'
start = StateView(
'freight.booking.shipment.report.link.start',
'automation.freight_booking_shipment_report_link_start_form',
[
Button('Cancel', 'end', 'tryton-cancel'),
Button('Link', 'link', 'tryton-ok', default=True),
])
link = StateTransition()
def default_start(self, fields):
Report = Pool().get('freight.booking.shipment.report')
active_ids = Transaction().context.get('active_ids') or []
reports = Report.browse(active_ids)
error_reports = [
report for report in reports
if report.status == 'error' and report.bl_number]
if len(error_reports) > 1:
raise UserError(
"Link one error line at a time to avoid overwriting the "
"shipment BL number.")
return {
'bl_number': error_reports[0].bl_number if error_reports else None,
}
def transition_link(self):
Report = Pool().get('freight.booking.shipment.report')
ShipmentIn = Pool().get('stock.shipment.in')
active_ids = Transaction().context.get('active_ids') or []
reports = Report.browse(active_ids)
error_reports = [
report for report in reports
if report.status == 'error' and report.bl_number]
if not error_reports:
raise UserError(
"Select at least one error line with a BL number.")
if len(error_reports) > 1:
raise UserError(
"Link one error line at a time to avoid overwriting the "
"shipment BL number.")
shipment = self.start.shipment
shipment.bl_number = error_reports[0].bl_number
ShipmentIn.save([shipment])
return 'end'
def end(self):
return 'reload'

View File

@@ -1,73 +0,0 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view" id="freight_booking_shipment_report_tree">
<field name="model">freight.booking.shipment.report</field>
<field name="type">tree</field>
<field name="name">freight_booking_shipment_report_tree</field>
</record>
<record model="ir.action.act_window" id="act_freight_booking_shipment_report">
<field name="name">Freight Booking Shipment Report</field>
<field name="res_model">freight.booking.shipment.report</field>
</record>
<record model="ir.action.act_window.view" id="act_freight_booking_shipment_report_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="freight_booking_shipment_report_tree"/>
<field name="act_window" ref="act_freight_booking_shipment_report"/>
</record>
<record model="ir.ui.view" id="freight_booking_shipment_report_link_start_form">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="type">form</field>
<field name="name">freight_booking_shipment_report_link_start_form</field>
</record>
<record model="ir.action.wizard" id="act_freight_booking_shipment_report_link">
<field name="name">Link to Shipment</field>
<field name="wiz_name">freight.booking.shipment.report.link</field>
</record>
<record model="ir.action.keyword" id="keyword_freight_booking_shipment_report_link">
<field name="keyword">form_action</field>
<field name="model">freight.booking.shipment.report,-1</field>
<field name="action" ref="act_freight_booking_shipment_report_link"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report">
<field name="model">freight.booking.shipment.report</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_link_start_automation">
<field name="model">freight.booking.shipment.report.link.start</field>
<field name="group" ref="group_automation"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_freight_booking_shipment_report_automation">
<field name="model">freight.booking.shipment.report</field>
<field name="group" ref="group_automation"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<menuitem
name="Freight Booking Shipment Report"
action="act_freight_booking_shipment_report"
parent="menu_automation"
sequence="11"
id="menu_freight_booking_shipment_report" />
</data>
</tryton>

View File

@@ -11,7 +11,6 @@ import traceback
logger = logging.getLogger(__name__)
class Cron(metaclass=PoolMeta):
__name__ = 'ir.cron'
@@ -19,11 +18,9 @@ class Cron(metaclass=PoolMeta):
def __setup__(cls):
super().__setup__()
cls.method.selection.append(
('automation.cron|update_shipment',
"Update Shipment from freight booking info")
('automation.cron|update_shipment', "Update Shipment from freight booking info")
)
class AutomationCron(ModelSQL, ModelView):
"Automation Cron"
__name__ = 'automation.cron'
@@ -33,7 +30,7 @@ class AutomationCron(ModelSQL, ModelView):
('weekly', "Weekly"),
('monthly', "Monthly"),
], "Frequency", required=True,
help="How frequently rates must be updated.")
help="How frequently rates must be updated.")
last_update = fields.Date("Last Update", required=True)
@@ -73,23 +70,22 @@ class AutomationCron(ModelSQL, ModelView):
))
rows = cursor.fetchall()
logger.info(f"Nombre total de lignes a traiter : {len(rows)}")
logger.info(f"Nombre total de lignes à traiter : {len(rows)}")
# Premiere transaction : creation des objets de reference
# ---- PREMIÈRE TRANSACTION : Création des objets de référence ----
with Transaction().new_transaction() as trans1:
try:
logger.info(
"Debut de la creation des objets de reference...")
logger.info("Début de la création des objets de référence...")
parties_to_save = []
vessels_to_save = []
locations_to_save = []
parties_cache = {}
vessels_cache = {}
locations_cache = {}
# Collecter les donnees des objets de reference
# Collecter les données des objets de référence
for row in rows:
(
si_number, si_date, si_quantity, si_unit,
@@ -101,44 +97,47 @@ class AutomationCron(ModelSQL, ModelView):
comments, fintrade_booking_key
) = row
# Fonction pour obtenir ou créer un Party
def get_or_create_party(name):
if not name:
return None
name_upper = str(name).strip().upper()
if name_upper in parties_cache:
return parties_cache[name_upper]
existing = Party.search(
[('name', '=', name_upper)], limit=1)
# Chercher d'abord dans la base
existing = Party.search([('name', '=', name_upper)], limit=1)
if existing:
parties_cache[name_upper] = existing[0]
return existing[0]
# Créer un nouveau
new_p = Party()
new_p.name = name_upper
parties_cache[name_upper] = new_p
parties_to_save.append(new_p)
return new_p
# Fonction pour obtenir ou créer un Vessel
def get_or_create_vessel(name):
if not name:
return None
name_upper = str(name).strip().upper()
if name_upper in vessels_cache:
return vessels_cache[name_upper]
existing = Vessel.search(
[('vessel_name', '=', name_upper)], limit=1)
existing = Vessel.search([('vessel_name', '=', name_upper)], limit=1)
if existing:
vessels_cache[name_upper] = existing[0]
return existing[0]
new_v = Vessel()
new_v.vessel_name = name_upper
vessels_cache[name_upper] = new_v
vessels_to_save.append(new_v)
return new_v
# Fonction pour obtenir ou créer une Location
def get_or_create_location(name, type_):
if not name:
return None
@@ -146,15 +145,16 @@ class AutomationCron(ModelSQL, ModelView):
key = f"{name_upper}_{type_}"
if key in locations_cache:
return locations_cache[key]
existing = Location.search([
('name', '=', name_upper),
('type', '=', type_),
('type', '=', type_)
], limit=1)
if existing:
locations_cache[key] = existing[0]
return existing[0]
new_loc = Location()
new_loc.name = name_upper
new_loc.type = type_
@@ -162,40 +162,40 @@ class AutomationCron(ModelSQL, ModelView):
locations_to_save.append(new_loc)
return new_loc
# Collecter les objets à créer
_ = get_or_create_party(carrier_name)
_ = get_or_create_party(agent_name)
_ = get_or_create_vessel(vessel_name)
_ = get_or_create_location(loading_name, 'supplier')
_ = get_or_create_location(destination_name, 'customer')
# Sauvegarder tous les objets de référence
if parties_to_save:
logger.info(f"Creation de {len(parties_to_save)} parties...")
logger.info(f"Création de {len(parties_to_save)} parties...")
Party.save(parties_to_save)
if vessels_to_save:
logger.info(f"Creation de {len(vessels_to_save)} vessels...")
logger.info(f"Création de {len(vessels_to_save)} vessels...")
Vessel.save(vessels_to_save)
if locations_to_save:
logger.info(
f"Creation de {len(locations_to_save)} locations...")
logger.info(f"Création de {len(locations_to_save)} locations...")
Location.save(locations_to_save)
trans1.commit()
logger.info(
"Premiere transaction commitee : objets de reference crees")
logger.info("Première transaction commitée : objets de référence créés")
except Exception as e:
trans1.rollback()
logger.error(
f"Erreur dans la creation des objets de reference : {e}")
logger.error(f"Erreur dans la création des objets de référence : {e}")
logger.error(traceback.format_exc())
raise
# Transactions individuelles pour chaque shipment
# ---- TRANSACTIONS INDIVIDUELLES pour chaque shipment ----
successful_shipments = 0
failed_shipments = []
# Recréer le curseur après la nouvelle transaction
cursor2 = Transaction().connection.cursor()
cursor2.execute(*t.select(
t.ShippingInstructionNumber,
@@ -218,76 +218,7 @@ class AutomationCron(ModelSQL, ModelView):
))
rows2 = cursor2.fetchall()
def normalize_bl_number(value):
value = str(value or '').strip()
if value.lower() in {'none', 'null'}:
return ''
if value.upper() in {
'MULTIPLE BL NUMBERS FOUND',
'MULTIPLE B/L NUMBERS FOUND',
'NO BL NUMBER FOUND',
'NO B/L NUMBER FOUND',
}:
return ''
return value
def normalize_booking_number(value):
value = str(value or '').strip()
if value.lower() in {'none', 'null'}:
return ''
return value
def sync_shipment_from_booking(shipment, bl_number, bl_date, etd_date,
controller):
changed = False
columns = []
values = []
booking_bl_number = normalize_bl_number(bl_number)
shipment_bl_number = normalize_bl_number(shipment.bl_number)
if booking_bl_number and booking_bl_number != shipment_bl_number:
logger.info(
"FREIGHT_BOOKING_SYNC_BL shipment=%s reference=%s "
"old_bl=%s new_bl=%s",
shipment.id, shipment.reference,
shipment.bl_number, booking_bl_number)
shipment.bl_number = booking_bl_number
columns.append('bl_number')
values.append(booking_bl_number)
changed = True
if bl_date and shipment.bl_date != bl_date:
logger.info(
"FREIGHT_BOOKING_SYNC_BL_DATE shipment=%s reference=%s "
"old_bl_date=%s new_bl_date=%s",
shipment.id, shipment.reference,
shipment.bl_date, bl_date)
shipment.bl_date = bl_date
columns.append('bl_date')
values.append(bl_date)
changed = True
if etd_date and shipment.etd != etd_date:
logger.info(
"FREIGHT_BOOKING_SYNC_ETD shipment=%s reference=%s "
"old_etd=%s new_etd=%s",
shipment.id, shipment.reference,
shipment.etd, etd_date)
shipment.etd = etd_date
columns.append('etd')
values.append(etd_date)
changed = True
if controller and shipment.controller_target != controller:
shipment.controller_target = controller
columns.append('controller_target')
values.append(controller)
changed = True
if changed:
table = ShipmentIn.__table__()
table_columns = [getattr(table, column) for column in columns]
cursor = Transaction().connection.cursor()
cursor.execute(*table.update(
table_columns, values, where=table.id == shipment.id))
return changed
for i, row in enumerate(rows2, 1):
(
si_number, si_date, si_quantity, si_unit,
@@ -298,98 +229,46 @@ class AutomationCron(ModelSQL, ModelView):
etd_date, bl_date, controller,
comments, fintrade_booking_key
) = row
si_reference = normalize_booking_number(si_number)
logger.info(
"Traitement shipment %s/%s : SI %s reference=%s",
i, len(rows2), si_number, si_reference)
logger.info(f"Traitement shipment {i}/{len(rows2)} : SI {si_number}")
# ---- TRANSACTION INDIVIDUELLE pour ce shipment ----
try:
with Transaction().new_transaction() as trans_shipment:
logger.info(
"Debut transaction pour SI %s reference=%s",
si_number, si_reference)
existing_shipments = ShipmentIn.search([
('reference', '=', si_reference)
])
if existing_shipments:
if len(existing_shipments) > 1:
logger.info(
"FREIGHT_BOOKING_DUPLICATE_SHIPMENT_REFERENCE "
"reference=%s shipment_ids=%s",
si_reference,
[shipment.id for shipment in existing_shipments])
for existing in existing_shipments:
sync_shipment_from_booking(
existing, bl_number, bl_date, etd_date,
controller)
shipment = existing_shipments[0]
if shipment.incoming_moves:
logger.info(
"Shipment %s existe deja avec lots, "
"booking data synchronized",
si_reference)
trans_shipment.commit()
continue
logger.info(
"Shipment %s existe deja sans lots, verification freight_booking_lots",
si_reference)
with Transaction().set_context(
_purchase_trade_skip_physical_lot_tolerance_warning=True):
inv_date, inv_nb = (
shipment._create_lots_from_fintrade())
shipment = ShipmentIn(shipment.id)
if shipment.incoming_moves:
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
if not shipment.fees:
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(
inv_date, inv_nb)
ShipmentIn.save([shipment])
logger.info(
"Shipment %s mis a jour avec %s incoming move(s)",
si_reference, len(shipment.incoming_moves))
else:
logger.info(
"Shipment %s existe sans lots et aucun lot disponible pour l'instant",
si_reference)
logger.info(f"Début transaction pour SI {si_number}")
# Vérifier si le shipment existe déjà
existing_shipment = ShipmentIn.search([
('reference', '=', si_number)
], limit=1)
if existing_shipment:
logger.info(f"Shipment {si_number} existe déjà, ignoré")
trans_shipment.commit()
continue
# Récupérer les objets (maintenant ils existent dans la base)
carrier = None
if carrier_name:
carrier_list = Party.search([
('name', '=',
str(carrier_name).strip().upper())
], limit=1)
carrier_list = Party.search([('name', '=', str(carrier_name).strip().upper())], limit=1)
if carrier_list:
carrier = carrier_list[0]
logger.info(
f"Carrier trouve pour {si_number}: {carrier.name}")
logger.info(f"Carrier trouvé pour {si_number}: {carrier.name}")
else:
logger.warning(
f"Carrier NON TROUVE pour {si_number}: '{carrier_name}'")
logger.warning(f"Carrier NON TROUVÉ pour {si_number}: '{carrier_name}'")
agent = None
agent_list = Party.search([
('name', '=', str(agent_name or 'TBN').strip().upper())
], limit=1)
agent_list = Party.search([('name', '=', str(agent_name or 'TBN').strip().upper())], limit=1)
if agent_list:
agent = agent_list[0]
vessel = None
if vessel_name:
vessel_list = Vessel.search([
('vessel_name', '=',
str(vessel_name).strip().upper())
], limit=1)
vessel_list = Vessel.search([('vessel_name', '=', str(vessel_name).strip().upper())], limit=1)
if vessel_list:
vessel = vessel_list[0]
loc_from = None
if loading_name:
loc_from_list = Location.search([
@@ -398,7 +277,7 @@ class AutomationCron(ModelSQL, ModelView):
], limit=1)
if loc_from_list:
loc_from = loc_from_list[0]
loc_to = None
if destination_name:
loc_to_list = Location.search([
@@ -407,19 +286,19 @@ class AutomationCron(ModelSQL, ModelView):
], limit=1)
if loc_to_list:
loc_to = loc_to_list[0]
# Vérification critique du carrier
if not carrier:
error_msg = (
f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} "
f"(valeur: '{carrier_name}')")
error_msg = f"ERREUR CRITIQUE: Carrier manquant pour SI {si_number} (valeur: '{carrier_name}')"
logger.error(error_msg)
raise ValueError(error_msg)
# Créer le shipment
shipment = ShipmentIn()
shipment.reference = si_reference
shipment.reference = si_number
shipment.from_location = loc_from
shipment.to_location = loc_to
shipment.carrier = None # carrier
shipment.carrier = None #carrier
shipment.supplier = agent
shipment.agent = agent
shipment.vessel = vessel
@@ -428,30 +307,21 @@ class AutomationCron(ModelSQL, ModelView):
shipment.bl_date = bl_date
shipment.etd = etd_date
shipment.etad = shipment.bl_date + timedelta(days=20)
# Sauvegarder ce shipment uniquement
ShipmentIn.save([shipment])
inv_date,inv_nb = shipment._create_lots_from_fintrade()
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(inv_date,inv_nb)
ShipmentIn.save([shipment])
with Transaction().set_context(
_purchase_trade_skip_physical_lot_tolerance_warning=True):
inv_date, inv_nb = shipment._create_lots_from_fintrade()
shipment = ShipmentIn(shipment.id)
if shipment.incoming_moves:
shipment.controller = shipment.get_controller()
shipment.controller_target = controller
shipment.create_fee(shipment.controller)
shipment.instructions = shipment.get_instructions_html(
inv_date, inv_nb)
ShipmentIn.save([shipment])
else:
logger.info(
"Shipment %s cree sans lots : fees/instructions "
"non crees, voir FINTRADE_CLEANUP_NEEDED",
si_number)
trans_shipment.commit()
if shipment.incoming_moves:
successful_shipments += 1
logger.info(f"Shipment {si_number} cree avec succes")
successful_shipments += 1
logger.info(f"✓ Shipment {si_number} créé avec succès")
except Exception as e:
# Cette transaction échoue mais les autres continuent
error_details = {
'si_number': si_number,
'carrier_name': carrier_name,
@@ -459,8 +329,8 @@ class AutomationCron(ModelSQL, ModelView):
'traceback': traceback.format_exc()
}
failed_shipments.append(error_details)
logger.error(f"ERREUR pour shipment {si_number}: {e}")
logger.error(f"ERREUR pour shipment {si_number}: {e}")
logger.error(f" Carrier: '{carrier_name}'")
logger.error(f" Agent: '{agent_name}'")
logger.error(f" Vessel: '{vessel_name}'")
@@ -468,22 +338,24 @@ class AutomationCron(ModelSQL, ModelView):
for line in traceback.format_exc().split('\n'):
if line.strip():
logger.error(f" {line}")
# ---- RÉSUMÉ FINAL ----
logger.info("=" * 60)
logger.info("RESUME DE L'EXECUTION")
logger.info("RÉSUMÉ DE L'EXÉCUTION")
logger.info("=" * 60)
logger.info(f"Total de shipments a traiter : {len(rows2)}")
logger.info(f"Shipments crees avec succes : {successful_shipments}")
logger.info(f"Shipments en echec : {len(failed_shipments)}")
logger.info(f"Total de shipments à traiter : {len(rows2)}")
logger.info(f"Shipments créés avec succès : {successful_shipments}")
logger.info(f"Shipments en échec : {len(failed_shipments)}")
if failed_shipments:
logger.info("\nDetail des echecs :")
logger.info("\nDétail des échecs :")
for i, error in enumerate(failed_shipments, 1):
logger.info(f" {i}. SI {error['si_number']}:")
logger.info(f" Carrier: '{error['carrier_name']}'")
logger.info(f" Erreur: {error['error']}")
logger.info("\nAnalyse des carriers problematiques :")
# Log supplémentaire pour debug
logger.info("\nAnalyse des carriers problématiques :")
problematic_carriers = {}
for error in failed_shipments:
carrier = error['carrier_name']
@@ -491,16 +363,15 @@ class AutomationCron(ModelSQL, ModelView):
problematic_carriers[carrier] += 1
else:
problematic_carriers[carrier] = 1
for carrier, count in problematic_carriers.items():
logger.info(f" Carrier '{carrier}' : {count} echec(s)")
existing = Party.search([
('name', '=', str(carrier).strip().upper())
], limit=1)
logger.info(f" Carrier '{carrier}' : {count} échec(s)")
# Vérifier si ce carrier existe dans la base
existing = Party.search([('name', '=', str(carrier).strip().upper())], limit=1)
if existing:
logger.info(
f" -> EXISTE DANS LA BASE (ID: {existing[0].id})")
logger.info(f" → EXISTE DANS LA BASE (ID: {existing[0].id})")
else:
logger.info(" -> N'EXISTE PAS DANS LA BASE")
logger.info("=" * 60)
logger.info(f" N'EXISTE PAS DANS LA BASE")
logger.info("=" * 60)

View File

@@ -7,5 +7,4 @@ depends:
xml:
automation.xml
freight_booking.xml
booking_report.xml
cron.xml
cron.xml

View File

@@ -1,6 +0,0 @@
<form>
<label name="bl_number"/>
<field name="bl_number"/>
<label name="shipment"/>
<field name="shipment"/>
</form>

View File

@@ -1,7 +0,0 @@
<tree>
<field name="bl_number"/>
<field name="status" widget="badge"
badge_colors="found:#16a34a,Found:#16a34a,error:#ef4444,Error:#ef4444"/>
<field name="shipment"/>
<field name="automation_document"/>
</tree>

View File

@@ -133,19 +133,6 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
if number.type == 'iban':
return number.number
@staticmethod
def _bic_compact(bic):
return getattr(bic, 'compact', bic)
@classmethod
def _bic_matches(cls, first, second):
def normalize(value):
value = cls._bic_compact(value)
if len(value) == 11 and value.endswith('XXX'):
value = value[:8]
return value
return normalize(first) == normalize(second)
@classmethod
def validate(cls, accounts):
super().validate(accounts)
@@ -159,7 +146,7 @@ class Account(DeactivableMixin, ModelSQL, ModelView):
iban = IBAN(self.iban)
bic = BIC(self.bank.bic)
if (iban.bic
and not self._bic_matches(iban.bic, bic)
and iban.bic != bic
and (
iban.country_code != bic.country_code
or (iban.bank_code or iban.branch_code)

View File

@@ -41,16 +41,6 @@ class BankTestCase(ModuleTestCase):
bank.bic = 'foo'
bank.save()
@with_transaction()
def test_bic_matches_primary_branch(self):
"Test BIC matches main branch"
pool = Pool()
Account = pool.get('bank.account')
self.assertTrue(Account._bic_matches('CIALCHBBXXX', 'CIALCHBB'))
self.assertTrue(Account._bic_matches('CIALCHBB', 'CIALCHBBXXX'))
self.assertFalse(Account._bic_matches('CIALCHBB123', 'CIALCHBB'))
@with_transaction()
def test_iban_format(self):
'Test IBAN format'

View File

@@ -2,4 +2,9 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//page[@id='costs']" position="inside">
<field name="before_carriages" colspan="4"/>
<field name="after_carriages" colspan="4"/>
</xpath>
</data>

View File

@@ -295,60 +295,23 @@ class Incoming(DeactivableMixin, Workflow, ModelSQL, ModelView):
@ModelView.button
@Workflow.transition('processing')
def process(cls, documents, with_children=False):
logger.info(
"DOCUMENT_INCOMING_PROCESS_CLICK documents=%s with_children=%s",
[
{
'id': document.id,
'name': document.name,
'type': document.type,
'state': document.state,
}
for document in documents
],
with_children)
transaction = Transaction()
context = transaction.context
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
logger.info(
"DOCUMENT_INCOMING_PROCESS_QUEUE documents=%s queue_batch=%s",
[document.id for document in documents],
transaction.context.get('queue_batch'))
cls.__queue__.proceed(documents, with_children=with_children)
@classmethod
@ModelView.button
@Workflow.transition('done')
def proceed(cls, documents, with_children=False):
logger.info(
"DOCUMENT_INCOMING_PROCEED_START documents=%s with_children=%s",
[
{
'id': document.id,
'name': document.name,
'type': document.type,
'state': document.state,
'active': document.active,
'result': str(document.result) if document.result else None,
}
for document in documents
],
with_children)
pool = Pool()
Attachment = pool.get('ir.attachment')
results = defaultdict(list)
attachments = []
for document in documents:
if document.result or not document.active:
logger.info(
"DOCUMENT_INCOMING_PROCEED_SKIP document=%s "
"result=%s active=%s",
document.id, document.result, document.active)
continue
logger.info(
"DOCUMENT_INCOMING_PROCEED_DISPATCH document=%s method=%s",
document.id, f'_process_{document.type}')
document.result = getattr(document, f'_process_{document.type}')()
results[document.result.__class__].append(document.result)
attachment = Attachment(

View File

@@ -7,9 +7,6 @@ from trytond.modules.document_incoming.exceptions import (
DocumentIncomingProcessError)
from trytond.pool import Pool, PoolMeta
import json
import logging
logger = logging.getLogger(__name__)
class IncomingConfiguration(metaclass=PoolMeta):
@@ -21,13 +18,6 @@ class IncomingConfiguration(metaclass=PoolMeta):
class Incoming(metaclass=PoolMeta):
__name__ = 'document.incoming'
created_at = fields.Function(
fields.Date("Created At"),
'get_created_at')
result_notes = fields.Function(
fields.Text("Result Notes"),
'get_result_notes')
@classmethod
def __setup__(cls):
super().__setup__()
@@ -35,32 +25,21 @@ class Incoming(metaclass=PoolMeta):
('weight_report', "Weight Report"))
cls.type.selection.append(
('controller', "Controller"))
cls._order = [('create_date', 'DESC')]
@classmethod
def _get_results(cls):
return super()._get_results() | {'automation.document'}
def _process_weight_report(self):
logger.info(
"DOCUMENT_INCOMING_WR_START incoming=%s name=%s type=%s "
"data_size=%s",
getattr(self, 'id', None), getattr(self, 'name', None),
getattr(self, 'type', None),
len(getattr(self, 'data', None) or b""))
WR = Pool().get('automation.document')
wr = WR()
wr.document = self.id
wr.type = 'weight_report'
wr.state = 'draft'
WR.save([wr])
logger.info(
"DOCUMENT_INCOMING_WR_AUTOMATION_CREATED incoming=%s "
"automation_document=%s",
getattr(self, 'id', None), getattr(wr, 'id', None))
WR.run_ocr([wr])
WR.run_metadata([wr])
WR.run_pipeline([wr])
return wr
def _process_controller(self):
@@ -75,25 +54,6 @@ class Incoming(metaclass=PoolMeta):
return wr
def get_created_at(self, name=None):
create_date = getattr(self, 'create_date', None)
if not create_date:
return None
return create_date.date()
@classmethod
def order_created_at(cls, tables):
table, _ = tables[None]
return [table.create_date]
def get_result_notes(self, name=None):
result = getattr(self, 'result', None)
if not result:
return ''
if getattr(result, '__name__', None) == 'automation.document':
return getattr(result, 'notes', '') or ''
return ''
# @property
# def supplier_invoice_company(self):
# pass

View File

@@ -8,10 +8,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="document_incoming.document_incoming_configuration_view_form"/>
<field name="name">document_incoming_configuration_form</field>
</record>
<record model="ir.ui.view" id="document_incoming_view_list_wr">
<field name="model">document.incoming</field>
<field name="inherit" ref="document_incoming.document_incoming_view_list"/>
<field name="name">document_incoming_list</field>
</record>
</data>
</tryton>

View File

@@ -2,9 +2,7 @@
version=7.2.0
depends:
document_incoming
automation
ir
party
xml:
document.xml
view/document_incoming_list.xml

View File

@@ -1,11 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='name']" position="before">
<field name="created_at" expand="2"/>
</xpath>
<xpath expr="/tree/field[@name='result']" position="after">
<field name="result_notes" expand="2"/>
</xpath>
</data>

View File

@@ -24,14 +24,8 @@ class Lot(ModelSQL, ModelView):
lot_name = fields.Char("Lot")
number = fields.Char("Number", readonly=True)
lot_qt = fields.Float("Quantity", required=False,
states={
'readonly': Eval('lot_type') == 'virtual',
})
lot_unit = fields.Many2One('product.uom', "Unit", required=False,
states={
'readonly': Eval('lot_type') == 'virtual',
})
lot_qt = fields.Float("Quantity",required=False)
lot_unit = fields.Many2One('product.uom', "Unit",required=False)
lot_product = fields.Many2One('product.product', "Product")
lot_type = fields.Selection([
('virtual', 'Open'),
@@ -492,7 +486,8 @@ class Lot(ModelSQL, ModelView):
st = state_id
else:
st = self.lot_state.id
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
logger.info("GET_HIST_QT:%s",st)
lot = [e for e in self.lot_hist if e.quantity_type.id == st][0]
qt = round(lot.quantity,5)
gross_qt = round(lot.gross_quantity,5)
return qt, gross_qt

View File

@@ -36,7 +36,7 @@
<notebook colspan="4">
<page string="General" id="general">
<field name="lot_childs" colspan="4" mode="tree,form" view_ids="lot.lot_view_tree_sequence2,lot.lot_view_form"/>
<field name="lot_hist" colspan="4" readonly="1"/>
<field name="lot_hist" colspan="4"/>
</page>
<page string="Accounting" id="accounting">
<field name="pivot" widget="html_viewer" height="600" colspan="4"/>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0"?>
<form col="4">
<label name="lot"/>
<field name="lot" readonly="1"/>
<field name="lot"/>
<label name="quantity_type"/>
<field name="quantity_type" readonly="1"/>
<field name="quantity_type"/>
<label name="quantity"/>
<field name="quantity" readonly="1"/>
<field name="quantity"/>
<label name="gross_quantity"/>
<field name="gross_quantity" readonly="1"/>
<field name="gross_quantity"/>
</form>

View File

@@ -1,4 +1,4 @@
<tree>
<tree editable="1">
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>

View File

@@ -1,4 +1,4 @@
<tree>
<tree editable="1">
<field name="lot"/>
<field name="quantity_type"/>
<field name="quantity"/>

View File

@@ -3,6 +3,9 @@
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_price_value_unique">
<field name="text">Only one price value is allowed for each price index and price date.</field>
</record>
<record model="ir.message" id="msg_party_code_unique">
<field name="text">The code on party must be unique.</field>
</record>

View File

@@ -36,51 +36,30 @@ class Price(
('composite', 'Composite'),
], 'Index type')
price_type = fields.Many2One('price.fixtype', "Fixation type")
price_unit = fields.Many2One('product.uom', "Unit")
price_currency = fields.Many2One('currency.currency', "Currency")
enable_linked_currency = fields.Boolean("Linked currency")
linked_currency = fields.Many2One('currency.linked', "Linked currency",
domain=[
('currency', '=', Eval('price_currency')),
],
states={
'invisible': ~Eval('enable_linked_currency'),
'required': Eval('enable_linked_currency'),
}, depends=['enable_linked_currency', 'price_currency'])
price_area = fields.Many2One('price.area',"Market area")
price_calendar = fields.Many2One('price.calendar',"Calendar")
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
price_composite = fields.One2Many('price.composite','price',"Composites")
price_product = fields.One2Many('price.product', 'price', "Product")
price_unit = fields.Many2One('product.uom', "Unit")
price_currency = fields.Many2One('currency.currency', "Currency")
price_area = fields.Many2One('price.area',"Market area")
price_calendar = fields.Many2One('price.calendar',"Calendar")
price_values = fields.One2Many('price.price_value', 'price', "Prices Values")
price_composite = fields.One2Many('price.composite','price',"Composites")
price_product = fields.One2Many('price.product', 'price', "Product")
price_ct_size = fields.Numeric("Ct size")
def get_qt(self,nb_ct,unit):
Uom = Pool().get('product.uom')
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
def _price_in_main_currency(self, price):
price = Decimal(str(price or 0))
if self.enable_linked_currency and self.linked_currency:
factor = Decimal(self.linked_currency.factor or 0)
if factor:
price *= factor
return price
def get_price_per_qt(self,price,unit,currency):
price_qt = Decimal(0)
Uom = Pool().get('product.uom')
Currency = Pool().get('currency.currency')
price = self._price_in_main_currency(price)
unit_factor = Decimal(str(
Uom.compute_qty(unit, float(1), self.price_unit) or 0))
if currency != self.price_currency:
rates = Currency._get_rate([self.price_currency])
if rates[self.price_currency.id]:
price_qt = price * unit_factor * Decimal(str(
rates[self.price_currency.id]))
else:
price_qt = price * unit_factor
return round(price_qt,4)
def get_qt(self,nb_ct,unit):
Uom = Pool().get('product.uom')
return round(Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit)),4)
def get_price_per_qt(self,price,unit,currency):
price_qt = float(0)
Uom = Pool().get('product.uom')
Currency = Pool().get('currency.currency')
if currency != self.price_currency:
rates = Currency._get_rate([self.price_currency])
if rates[self.price_currency.id]:
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit) * float(rates[self.price_currency.id])
else:
price_qt = float(price) * Uom.compute_qty(unit, float(1), self.price_unit)
return round(price_qt,4)
def get_amount_nb_ct(self,price,nb_ct,unit,currency):
amount = Decimal(0)
@@ -89,22 +68,17 @@ class Price(
amount = Decimal(self.get_price_per_qt(price,unit,currency)) * Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit))
return round(amount,4)
def get_price(self,dt,unit,currency,last=False, relative_last=False):
price = float(0)
PV = Pool().get('price.price_value')
if self.price_values:
dt = dt.strftime("%Y-%m-%d")
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
if not pv and last:
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
elif not pv and relative_last:
pv = PV.search([
('price','=',self.id),
('price_date','<=',dt),
], order=[('price_date', 'DESC')])
if pv:
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
return round(price,4)
def get_price(self,dt,unit,currency,last=False):
price = float(0)
PV = Pool().get('price.price_value')
if self.price_values:
dt = dt.strftime("%Y-%m-%d")
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
if not pv and last:
pv = PV.search([('price','=',self.id)],order=[('price_date', 'DESC')])
if pv:
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
return round(price,4)
class FixType(ModelSQL,ModelView):
"Fixation type"

View File

@@ -9,7 +9,7 @@ from trytond.i18n import gettext
from trytond.model import (
DeactivableMixin, Index, ModelSQL, ModelView, MultiValueMixin, Unique,
ValueMixin, convert_from, fields, sequence_ordered)
from trytond.model.exceptions import AccessError
from trytond.model.exceptions import AccessError, ValidationError
from trytond.pool import Pool
from trytond.pyson import Bool, Eval
from trytond.tools import is_full_text, lstrip_wildcard
@@ -35,16 +35,37 @@ class PriceValue(
}), 'get_price_index')
price_date = fields.Date("Price date")
price_value = fields.Float("Price value")
open_price = fields.Float("Open price")
low_price = fields.Float("Low price")
mid_price = fields.Float("Mid price")
price_value = fields.Float("Price value")
open_price = fields.Float("Open price")
low_price = fields.Float("Low price")
high_price = fields.Float("High price")
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
@classmethod
def validate(cls, price_values):
super().validate(price_values)
cls.check_unique_price_date(price_values)
@classmethod
def check_unique_price_date(cls, price_values):
domains = []
for price_value in price_values:
if not price_value.price or not price_value.price_date:
continue
domain = [
('price', '=', price_value.price.id),
('price_date', '=', price_value.price_date),
]
if price_value.id:
domain.append(('id', '!=', price_value.id))
domains.append(['AND'] + domain)
if domains and cls.search(['OR'] + domains, limit=1):
raise ValidationError(
gettext('price.msg_price_value_unique'))
def get_price_index(self, name):
if self.price:
return self.price.price_index
return None
class PriceValueReport(
ModelSQL, ModelView):
@@ -56,10 +77,9 @@ class PriceValueReport(
r_price_desc = fields.Char("Description")
r_price_date = fields.Date("Price date")
r_price_value = fields.Float("Price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_mid_price = fields.Float("Mid price")
r_high_price = fields.Float("High price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_high_price = fields.Float("High price")
@classmethod
def table_query(cls):
@@ -88,11 +108,10 @@ class PriceValueReport(
pp.price_desc.as_('r_price_desc'),
pv.price_date.as_('r_price_date'),
pv.price_value.as_('r_price_value'),
pv.open_price.as_('r_open_price'),
pv.low_price.as_('r_low_price'),
pv.mid_price.as_('r_mid_price'),
pv.high_price.as_('r_high_price'),
where=wh)
pv.open_price.as_('r_open_price'),
pv.low_price.as_('r_low_price'),
pv.high_price.as_('r_high_price'),
where=wh)
return query
@@ -119,10 +138,9 @@ class PriceValueReport2(
r_price_desc = fields.Char("Description")
r_price_date = fields.Date("Price date")
r_price_value = fields.Float("Price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_mid_price = fields.Float("Mid price")
r_high_price = fields.Float("High price")
r_open_price = fields.Float("Open price")
r_low_price = fields.Float("Low price")
r_high_price = fields.Float("High price")
r_naphta_fob_low = fields.Float("Naph F Med l", digits=(1,2), help="Naphta Fob PAAAI00 low")
r_naphta_fob_high = fields.Float("Naph F Med h", digits=(1,2), help="Naphta Fob PAAAI00 high")
r_naphta_fob_mid = fields.Float("Naph F Med m", digits=(1,2), help="Naphta Fob PAAAI00 mid")
@@ -175,10 +193,9 @@ class PriceValueReport2(
Max(pp.price_desc).as_('r_price_desc'),
pv.price_date.as_('r_price_date'),
Max(pv.price_value).as_('r_price_value'),
Max(pv.open_price).as_('r_open_price'),
Max(pv.low_price).as_('r_low_price'),
Max(pv.mid_price).as_('r_mid_price'),
Max(pv.high_price).as_('r_high_price'),
Max(pv.open_price).as_('r_open_price'),
Max(pv.low_price).as_('r_low_price'),
Max(pv.high_price).as_('r_high_price'),
Max(Case((pv.price==15, pv.low_price),else_=0)).as_('r_naphta_fob_low'),
Max(Case((pv.price==15, pv.high_price),else_=0)).as_('r_naphta_fob_high'),
Max(Case((pv.price==15, (pv.high_price-pv.low_price)/2 + pv.low_price),else_=0)).as_('r_naphta_fob_mid'),

View File

@@ -12,14 +12,10 @@
<field name="price_type"/>
<label name="price_unit"/>
<field name="price_unit"/>
<label name="price_currency"/>
<field name="price_currency"/>
<label name="enable_linked_currency"/>
<field name="enable_linked_currency"/>
<label name="linked_currency"/>
<field name="linked_currency"/>
<label name="price_area"/>
<field name="price_area"/>
<label name="price_currency"/>
<field name="price_currency"/>
<label name="price_area"/>
<field name="price_area"/>
<label name="price_calendar"/>
<field name="price_calendar"/>
<label name="price_ct_size"/>

View File

@@ -2,9 +2,8 @@
<tree>
<field name="r_price_index"/>
<field name="r_price_desc"/>
<field name="r_price_date"/>
<field name="r_price_value"/>
<field name="r_low_price"/>
<field name="r_mid_price"/>
<field name="r_high_price"/>
</tree>
<field name="r_price_date"/>
<field name="r_price_value"/>
<field name="r_low_price"/>
<field name="r_high_price"/>
</tree>

View File

@@ -8,12 +8,10 @@
<label name="price_value"/>
<field name="price_value"/>
<label name="open_price"/>
<field name="open_price"/>
<label name="low_price"/>
<field name="low_price"/>
<label name="mid_price"/>
<field name="mid_price"/>
<label name="high_price"/>
<field name="high_price"/>
<field name="open_price"/>
<label name="low_price"/>
<field name="low_price"/>
<label name="high_price"/>
<field name="high_price"/>
</group>
</form>

View File

@@ -9,7 +9,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="price_value" fill="1" empty="0"/>
<field name="open_price" fill="1" empty="0"/>
<field name="low_price" fill="1" empty="0"/>
<field name="mid_price" fill="1" empty="0"/>
<field name="high_price" fill="1" empty="0"/>
</y>
</graph>

View File

@@ -2,9 +2,8 @@
<tree>
<field name="price_index"/>
<field name="price_date"/>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="mid_price"/>
<field name="high_price"/>
</tree>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="high_price"/>
</tree>

View File

@@ -2,9 +2,8 @@
<tree editable="1">
<field name="price_index"/>
<field name="price_date"/>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="mid_price"/>
<field name="high_price"/>
</tree>
<field name="price_value"/>
<field name="open_price"/>
<field name="low_price"/>
<field name="high_price"/>
</tree>

View File

@@ -519,8 +519,6 @@ class Purchase(
@property
def taxable_lines(self):
Date = Pool().get('ir.date')
tax_date = getattr(self, 'purchase_date', None) or Date.today()
taxable_lines = []
# In case we're called from an on_change we have to use some sensible
# defaults
@@ -531,7 +529,7 @@ class Purchase(
getattr(line, 'taxes', None) or [],
getattr(line, 'unit_price', None) or Decimal(0),
getattr(line, 'quantity', None) or 0,
tax_date,
None,
))
return taxable_lines
@@ -955,22 +953,19 @@ class Purchase(
@classmethod
@ModelView.button
# @Workflow.transition('confirmed')
# @set_employee('confirmed_by')
@Workflow.transition('confirmed')
@set_employee('confirmed_by')
def confirm(cls, purchases):
# Confirm is intentionally disabled: clicking the button must not move
# the purchase to Confirmed or trigger downstream processing.
pass
# pool = Pool()
# Line = pool.get('purchase.line')
# cls.set_purchase_date(purchases)
#
# cls.write(purchases, {'state': 'confirmed'})
# lines = list(sum((p.lines for p in purchases), ()))
# Line._validate(lines, ['unit_price'])
#
# transaction = Transaction()
# context = transaction.context
pool = Pool()
Line = pool.get('purchase.line')
transaction = Transaction()
context = transaction.context
cls.set_purchase_date(purchases)
cls.write(purchases, {'state': 'confirmed'})
lines = list(sum((p.lines for p in purchases), ()))
Line._validate(lines, ['unit_price'])
# cls.store_cache(purchases)
# for process_after, sub_purchases in groupby(
# purchases, lambda p: p.process_after):
@@ -1018,22 +1013,16 @@ class Purchase(
for purchase in purchases:
logger.info("PROCESS_INVOICE:%s",action)
invoice = purchase.create_invoice(lots,action)
if not invoice:
logger.info(
"PROCESS_INVOICE_NO_INVOICE: purchase=%s action=%s",
getattr(purchase, 'id', None), action)
continue
if action == 'prov':
invoice.reference = 'Provisional'
elif action == 'service':
invoice.reference = 'Service'
else:
invoice.reference = 'Final'
invoices[purchase] = invoice
if invoice:
invoices[purchase] = invoice
if invoices:
cls._save_invoice(invoices,prepayment)
return invoices
cls._save_invoice(invoices,prepayment)
@classmethod
def _save_invoice(cls, invoices,prepayment=None):
@@ -1862,83 +1851,6 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
def on_change_with_currency(self, name=None):
return self.purchase.currency if self.purchase else None
@staticmethod
def _record_id(record):
return getattr(record, 'id', record)
@classmethod
def _get_last_fee_invoice_line(cls, fee, lot):
fee_id = cls._record_id(fee)
lot_id = cls._record_id(lot)
if not fee_id or not lot_id:
return
InvoiceLine = Pool().get('account.invoice.line')
lines = InvoiceLine.search([
('fee', '=', fee_id),
('lot', '=', lot_id),
('quantity', '>', 0),
('invoice.state', '!=', 'cancelled'),
], order=[
('invoice.invoice_date', 'DESC'),
('invoice.id', 'DESC'),
('id', 'DESC'),
], limit=1)
return lines[0] if lines else None
@staticmethod
def _fee_invoice_line_changed(previous_line, invoice_line):
if not previous_line:
return True
previous_quantity = Decimal(str(previous_line.quantity or 0))
quantity = Decimal(str(invoice_line.quantity or 0))
previous_price = Decimal(str(previous_line.unit_price or 0))
price = Decimal(str(invoice_line.unit_price or 0))
return previous_quantity != quantity or previous_price != price
@classmethod
def _get_fee_reversal_invoice_line(cls, previous_line, origin):
if not previous_line:
return
InvoiceLine = Pool().get('account.invoice.line')
reversal_line, = InvoiceLine.copy([previous_line], default={
'invoice': None,
'quantity': -previous_line.quantity,
'unit_price': previous_line.unit_price,
'party': previous_line.invoice.party,
'origin': str(origin),
})
return reversal_line
def _get_service_fee_invoice_lines(self, invoice_line, lot):
Fee = Pool().get('fee.fee')
fee = Fee.search(['purchase','=',self.purchase.id])
if not fee:
return [invoice_line]
fee = fee[0]
invoice_line.fee = fee
fee.warn_percent_price_partial_lots()
if fee.mode == 'lumpsum':
invoice_line.quantity = 1
elif fee.mode == 'pprice':
invoice_line.quantity = 1
elif fee.mode == 'ppack':
invoice_line.quantity = fee.quantity
else:
invoice_line.quantity = fee.get_fee_lots_qt()
if getattr(fee, 'state', None) != 'invoiced':
return [invoice_line]
previous_line = self._get_last_fee_invoice_line(fee, lot)
if not self._fee_invoice_line_changed(previous_line, invoice_line):
return []
reversal_line = self._get_fee_reversal_invoice_line(
previous_line, self)
if reversal_line:
return [reversal_line, invoice_line]
return [invoice_line]
def get_invoice_line(self,lots=None,action=None):
'Return a list of invoice line for purchase line'
pool = Pool()
@@ -2018,9 +1930,21 @@ class Line(sequence_ordered(), ModelSQL, ModelView):
invoice_line.unit_price = self.unit_price
invoice_line.product = self.product
invoice_line.stock_moves = []
lines.extend(
self._get_service_fee_invoice_lines(invoice_line, l))
continue
Fee = Pool().get('fee.fee')
fee = Fee.search(['purchase','=',self.purchase.id])
if fee:
invoice_line.fee = fee[0]
if fee[0].mode == 'lumpsum':
invoice_line.quantity = 1
elif fee[0].mode == 'ppack':
invoice_line.quantity = fee[0].quantity
else:
state_id = 0
LotQtType = Pool().get('lot.qt.type')
lqt = LotQtType.search([('name','=','BL')])
if lqt:
state_id = lqt[0].id
invoice_line.quantity = fee[0].get_fee_lots_qt(state_id)
lines.append(invoice_line)
logger.info("GETINVLINE:%s",self.product.type)

View File

@@ -92,9 +92,25 @@ this repository contains the full copyright notices and license terms. -->
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_quotation">
<field name="name">Validated</field>
id="act_purchase_form_domain_service">
<field name="name">Services</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_quotation">
<field name="name">Validation</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="['AND',('state', '=', 'quotation'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
@@ -102,7 +118,7 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_confirmed">
<field name="name">Confirmed</field>
<field name="sequence" eval="20"/>
<field name="sequence" eval="30"/>
<field name="domain" eval="['AND',('state', '=', 'confirmed'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
@@ -110,31 +126,15 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_processing">
<field name="name">Processing</field>
<field name="sequence" eval="30"/>
<field name="domain" eval="['AND',('state', '=', 'processing'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="40"/>
<field name="domain" eval="['AND',('state', '=', 'draft'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_service">
<field name="name">Services</field>
<field name="sequence" eval="50"/>
<field name="domain" eval="[('line_type', '=', 'service')]" pyson="1"/>
<field name="domain" eval="['AND',('state', '=', 'processing'),('line_type', '=', 'goods')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_purchase_form"/>
</record>
<record model="ir.action.act_window.domain"
id="act_purchase_form_domain_exception">
<field name="name">Exception</field>
<field name="sequence" eval="60"/>
<field name="sequence" eval="50"/>
<field name="domain"
eval="['OR', ('invoice_state', '=', 'exception'), ('shipment_state', '=', 'exception')]"
pyson="1"/>
@@ -153,6 +153,7 @@ this repository contains the full copyright notices and license terms. -->
action="act_purchase_form"
sequence="10"
id="menu_purchase_form"/>
<record model="ir.action.act_window" id="act_purchase_invoice_relate">
<field name="name">Purchases</field>
<field name="res_model">purchase.purchase</field>

View File

@@ -85,7 +85,7 @@ this repository contains the full copyright notices and license terms. -->
<button name="handle_invoice_exception" icon="tryton-forward"/>
<button name="handle_shipment_exception" icon="tryton-forward"/>
<button name="confirm" icon="tryton-ok"/>
<button name="process" invisible="1"/>
<button name="process"/>
<button name="manual_invoice" icon="tryton-forward"/>
</group>
<field name="party_lang" invisible="1" colspan="6"/>

View File

@@ -2,4 +2,18 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='warehouse']" position="after">
<label name="carrier"/>
<field name="carrier"/>
</xpath>
<xpath expr="//page[@id='other']" position="before">
<page id="costs" string="Costs">
<label name="cost_used"/>
<group id="cost_used" col="-1" colspan="3">
<field name="cost_used" xexpand="0"/>
<field name="cost_currency_used"/>
<field name="cost_edit" xexpand="0" xalign="0"/>
</group>
</page>
</xpath>
</data>

View File

@@ -1,254 +0,0 @@
# AGENTS.md - Module `purchase_trade`
Ce guide complete le `AGENTS.md` racine.
Pour ce module, les regles locales ci-dessous priment.
## 1) Perimetre metier
Le module `purchase_trade` etend les flux achat/vente Tryton avec une logique
de negoce physique:
- contrats d'achat (`purchase.purchase`, `purchase.line`)
- contrats de vente (`sale.sale`, `sale.line`)
- lots physiques et virtuels
- matching achat/vente
- shipments et execution logistique
- frais (`fee.fee`)
- templates de documents metier et facture
## 2) Fichiers pivots
- Contrats achat:
- `modules/purchase_trade/purchase.py`
- Contrats vente:
- `modules/purchase_trade/sale.py`
- Lots / matching / invoicing:
- `modules/purchase_trade/lot.py`
- Shipments / lien facture-lot:
- `modules/purchase_trade/stock.py`
- Fees:
- `modules/purchase_trade/fee.py`
- Bridge facture / templates:
- `modules/purchase_trade/invoice.py`
- Vues:
- `modules/purchase_trade/view/*.xml`
- Actions module:
- `modules/purchase_trade/*.xml`
- Manifest:
- `modules/purchase_trade/tryton.cfg`
## 3) Documentation locale a lire en priorite
- Regles metier:
- `modules/purchase_trade/docs/business-rules.md`
- Documentation business publiee dans le wiki:
- `modules/purchase_trade/docs/business/*.md`
- Sources de verite de la documentation business generee:
- `modules/purchase_trade/docs_source/business/*.md`
- Regles templates:
- `modules/purchase_trade/docs/template-rules.md`
- Catalogue des proprietes templates:
- `modules/purchase_trade/docs/template-properties.md`
- Padding facture provisoire vente / validation comptable:
- `modules/purchase_trade/docs/padding-invoice-accounting.md`
## 4) Invariants metier a preserver
- Un lot `virtual` est la reference d'ouverture de quantite pour une `purchase.line`.
- Une `sale.line` doit aussi avoir au minimum un lot `virtual`; une valuation
cote sale ne doit donc pas disparaitre juste parce que le lot est `open`.
- Le lot physique est le pont principal entre:
- `purchase.line`
- `sale.line`
- shipment
- facture
- Pour remonter d'une facture vers shipment / BL / controller / fret:
- privilegier le lot physique
- ne pas multiplier des chemins d'acces concurrents
- Pour les champs de colis (`NB BALES`) dans les templates facture:
- la source de verite est `line.lot.lot_qt`
- sur une facture, sommer les `lot_qt` des lignes de facture
- tenir compte du signe de la ligne de facture pour les notes finales
- ne pas proratiser depuis le poids (`net` / `gross`)
- Le `FREIGHT VALUE` d'un template facture vient du `fee.fee` du shipment
dont le produit est `Maritime freight`.
- Pour `stock/insurance.fodt`, le `Amount insured` doit venir en priorite de
`110%` du total des `incoming_moves` (fallback fee `Insurance` si aucun
montant incoming calculable).
- Pour le surveyor du certificat d'assurance shipment, la priorite est:
`shipment.surveyor` -> `shipment.controller` -> fournisseur du fee
`Insurance`.
- Pour `payment_order.fodt`, utiliser des proprietes
`invoice.report_payment_order_*` plutot que des tokens legacy `<...>`.
- Ajouter un champ de template dans `Document Templates` ne rend pas le report
visible dans la fiche: il faut aussi l'action `ir.action.report` +
`ir.action.keyword` (`form_print`) cote `account.invoice`.
- Le wizard `Create contracts` en mode `matched` peut maintenant partir de
plusieurs `lot.qt`, mais doit conserver un matching par lot source et laisser
`created_by_code = True` sur les lignes creees pour ne pas declencher les
creations automatiques de lots dans les validations.
- En valuation / PnL:
- la valeur stockee dans `type` est la cle technique (`pur. priced`,
`sale priced`, `pur. fee`, etc.), pas le label affiche dans l'UI
- les references doivent rester coherentes avec le type de lot:
`Purchase/Open`, `Purchase/Physic`, `Sale/Open`, `Sale/Physic`
- pour une sale matchee, les lignes de valuation purchase generees sur un lot
physique doivent aussi renseigner `sale` et `sale_line` afin de remonter
dans l'onglet PnL de la sale
- une sale non matchee doit etre valorisable "sale-first" et alimenter
`valuation.valuation` / `valuation.valuation.line`
- si une `sale.line` `basis` n'a ni `price_summary` ni `lot_price_sale`,
creer quand meme une ligne `sale priced` avec `price = 0` et `amount = 0`
plutot que de ne rien generer
- le MTM ne doit etre renseigne que pour `pur. priced`, `sale priced` et
`derivative`; jamais pour les fees
- `mtm_price` doit afficher le prix brut de valorisation (sans ratio), alors
que `mtm` reste le montant calcule selon la logique de strategie
- En pricing:
- le `unit_price` doit rester un prix de base, hors `premium`
- le `premium` doit impacter le prix total economique et donc le `amount`,
aussi bien en `priced` qu'en `basis`
- dans `pricing.pricing` en saisie manuelle, l'utilisateur renseigne
seulement `quantity` et `settl_price`
- `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` et
`eod_price` sont des valeurs derivees et ne doivent pas etre saisies a la
main
- en manuel, `fixed_qt` = cumul des `quantity` du groupe trie par
`pricing_date`
- en manuel, `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`
- en manuel, `unfixed_qt_price` = `settl_price` de la ligne
- pour les documents commerciaux / facture, une ligne `basis` affiche le
`premium` comme prix visible, pas le prix economique total
- si `linked currency` est active, le `premium` est saisi dans la devise /
unite liee (ex: `USC/LB`) puis converti vers le repere de la ligne pour le
calcul du `amount`
- en `basis + linked currency`, le `linked_price` doit representer le prix
basis brut (hors premium) dans la devise liee; le `unit_price` reste ce
prix brut converti, et le `premium` converti est ajoute seulement dans
l'`amount`
- si `linked currency` est cochee, `linked_price`, `linked_currency` et
`linked_unit` sont requis
- dans les forms, presenter le bloc prix dans l'ordre:
`price_type` -> linked fields -> `premium` -> `unit_price` -> `amount`
- en valuation `basis`, le premium s'applique a chaque composant, pas
uniquement a une ligne de resume
- pour une ligne `basis` sans `price_summary`, la valuation fallback doit
utiliser `unit_price + premium` (et pas `unit_price` seul)
- a la validation d'une `sale.line`, si un lot virtuel est cree et qu'aucun
matching purchase n'existe, il faut lancer `generate_from_sale_line()` pour
alimenter le PnL sale-first
- En padding de facture provisoire vente:
- le padding est saisi globalement dans `lot.invoice`
- il est uniquement applique aux factures provisoires cote vente
- il est reparti par lot et stocke dans `lot.sale_invoice_padding`
- la ligne facture affiche `Inc. padding` depuis le lot
- le padding augmente `account.invoice.line.quantity`, donc le move principal
de facture inclut deja le montant padding
- les comptes de padding viennent de `account.configuration`:
`Default Sale Padding` et `Default Accrual Padding`
- la section `Padding` doit rester avant la section `Invoice` dans
`Financial / Configuration`
- la provisoire cree un `additional_move` debit `Default Sale Padding` /
credit `Default Accrual Padding`
- la finale cree l'inverse en reprenant le montant depuis
`lot.sale_invoice_line_prov`, donc avec le prix, la devise, la date et le
taux de la provisoire
- le wizard final doit calculer son delta depuis la quantite provisoire hors
padding: `sale_invoice_line_prov.quantity - sale_invoice_padding`
- En modification de quantite contractuelle:
- `quantity_theorical` est la source contractuelle; `quantity` peut refleter
l'execute physique.
- le `lot.qt` libre doit etre resynchronise sur le solde ouvert reel:
`quantity_theorical - lots physiques - lot.qt deja matches/shippes`.
- ne jamais ajuster le `lot.qt` libre uniquement par delta si des lots
physiques existent deja.
- les fees suivent les lots effectifs: physiques s'il en existe dans
`fee.lots`, sinon virtuel.
- conserver le lien `fee.lots` vers le virtuel comme fallback; ne pas le
supprimer lors de la creation d'un physique.
- le PnL fee applique la meme regle: full open tant qu'il n'y a que le
virtuel, puis uniquement les physiques.
- detail durable:
`modules/purchase_trade/docs/business-rules.md` BR-PT-020 / BR-PT-021.
- En execution controller / SLA:
- les objectifs de repartition controller utilisent `party.execution.area`
(`country.region`).
- les couts SLA utilisent `party.execution.place` et peuvent matcher par
`country`, par `location`, ou par couple `country + location`.
- la creation du fee controller depuis shipment part de
`shipment.to_location`; le pays de matching est
`shipment.to_location.country`.
- priorite cout SLA: couple pays+location, puis location seule, puis pays
seul.
## 5) Conventions de modification
### Documentation business
- Ne pas modifier directement une page generee sous
`modules/purchase_trade/docs/business/` si elle contient le commentaire
`Generated from docs_source/business`.
- Toute regle business nouvelle ou modifiee doit etre editee dans
`modules/purchase_trade/docs_source/business/`.
- Apres modification des sources business, regenerer le wiki avec:
`python modules/purchase_trade/docs/tools/render_business_docs.py`
- Avant de rendre une modification documentaire, verifier que le rendu publie
est synchronise avec:
`python modules/purchase_trade/docs/tools/render_business_docs.py --check`
- Les pages francaises et anglaises miroir doivent rester synchronisees.
- Le rendu publie doit rester lisible dans MkDocs meme sans extensions
optionnelles: eviter les marqueurs bruts `!!!` et `:material-...:` dans les
fichiers publies.
1. Modifier la logique metier dans le fichier pivot le plus proche.
2. Si un template `.fodt` devient complexe, deplacer la logique dans une
propriete Python `report_*`.
3. Pour une facture trade, preferer enrichir `modules/purchase_trade/invoice.py`
plutot que surcharger lourdement le `.fodt`.
4. Si une regle metier durable change, mettre a jour
`docs/business-rules.md`.
5. Si une convention de template change, mettre a jour
`docs/template-rules.md`.
6. Pour les vues XML Tryton de ce module, utiliser `editable="1"` sur les
`<tree>` editables; ne pas utiliser `editable="bottom"`.
7. Ne pas ajouter `keyword_open` / `open_keyword` sur les vues `<tree>`:
ces attributs ne sont pas supportes par Tryton.
8. Si une regle de texte par defaut durable est demandee sur achat/vente,
preferer un singleton de configuration expose dans un menu fonctionnel
existant plutot qu'un menu technique `purchase_trade`.
## 6) Pieges connus
- Plusieurs actions de report `account.invoice` peuvent sembler rendre le meme
document a cause du cache `invoice_report_cache`.
- Les reports alternatifs (`Final Invoice`, `Prepayment`, etc.) ne doivent pas
reutiliser le cache du report standard sans verification.
- Pour les donnees achat/vente partagees, ne pas supposer qu'une facture de
vente doit lire directement sur la `sale.line`: souvent, la verite metier
passe par le lot physique et/ou la `account.invoice.line`.
- Les templates `invoice_ict*` peuvent partager les memes proprietes Python;
si une regle doit valoir pour provisional et final, la mettre dans
`modules/purchase_trade/invoice.py` plutot que dupliquer dans les `.fodt`.
- Dans les ecrans PnL, le label `Sale price` correspond au type stocke
`sale priced`; idem pour `Pur. price` / `pur. priced`.
- Une ligne `basis` sans resume de pricing peut sinon disparaitre de la
valuation si aucun fallback explicite a `0` n'est prevu.
- Le calcul du prix peut diverger entre `unit_price`, `linked_price`,
`lot_price` et valuation si le premium n'est pas traite explicitement dans
chaque maillon.
- Sur `account.invoice`, le workflow `Validate` doit maintenant aligner
fournisseur et client pour:
- creation du `account.move`
- attribution du `number`
- `Post` ne doit pas reintroduire une difference de session/fresh login cote
client
- Pour le padding provisoire vente, ne pas changer la quantite physique du lot:
l'ecart doit rester porte par `lot.sale_invoice_padding` et visible sur la
facture via `Inc. padding`.
## 7) Definition of done (module `purchase_trade`)
- Le flux achat/vente/lot cible reste coherent.
- Les impacts templates/facture ont ete verifies conceptuellement.
- Les docs locales ont ete mises a jour si une nouvelle regle durable a emerge.
- Le patch reste minimal et local au domaine demande.

View File

@@ -5,11 +5,9 @@ from trytond.pool import Pool
from . import (
account,
configuration,
purchase,
sale,
global_reporting,
ctrm_reporting,
stock,
derivative,
lot,
@@ -34,11 +32,9 @@ from . import (
dimension,
weight_report,
backtoback,
service,
invoice,
duplicate,
coffee,
)
service,
invoice,
)
def register():
Pool.register(
@@ -59,76 +55,16 @@ def register():
lc.LCMT700,
lc.LCMessage,
lc.CreateLCStart,
global_reporting.GRConfiguration,
ctrm_reporting.CTRMPhysicalPosition,
ctrm_reporting.CTRMPhysicalPositionContext,
ctrm_reporting.CTRMFinancialPosition,
ctrm_reporting.CTRMFinancialPositionContext,
ctrm_reporting.CTRMNetPosition,
ctrm_reporting.CTRMNetPositionContext,
ctrm_reporting.CTRMOpenPosition,
ctrm_reporting.CTRMOpenPositionContext,
ctrm_reporting.CTRMLongShortProductStrategy,
ctrm_reporting.CTRMLongShortCommodityDirection,
ctrm_reporting.CTRMLongShortPeriodProduct,
ctrm_reporting.CTRMLongShortPricingStatus,
ctrm_reporting.CTRMLongShortDetail,
ctrm_reporting.CTRMPositionExposure,
ctrm_reporting.CTRMPositionExposureContext,
ctrm_reporting.CTRMRealizedPnl,
ctrm_reporting.CTRMRealizedPnlContext,
ctrm_reporting.CTRMMtmPnl,
ctrm_reporting.CTRMMtmPnlContext,
ctrm_reporting.CTRMPnlExplain,
ctrm_reporting.CTRMPnlExplainContext,
ctrm_reporting.CTRMPnlDimension,
ctrm_reporting.CTRMPnlDimensionContext,
ctrm_reporting.CTRMCreditRisk,
ctrm_reporting.CTRMCreditRiskContext,
ctrm_reporting.CTRMShippingLogistics,
ctrm_reporting.CTRMShippingLogisticsContext,
ctrm_reporting.CTRMInventory,
ctrm_reporting.CTRMInventoryContext,
ctrm_reporting.CTRMContractPerformance,
ctrm_reporting.CTRMContractPerformanceContext,
ctrm_reporting.CTRMScheduling,
ctrm_reporting.CTRMSchedulingContext,
ctrm_reporting.CTRMAccruals,
ctrm_reporting.CTRMAccrualsContext,
ctrm_reporting.CTRMSettlements,
ctrm_reporting.CTRMSettlementsContext,
ctrm_reporting.CTRMCashFlow,
ctrm_reporting.CTRMCashFlowContext,
ctrm_reporting.CTRMGLReconciliation,
ctrm_reporting.CTRMGLReconciliationContext,
configuration.Configuration,
global_reporting.GRConfiguration,
pricing.ImportPricesStart,
pricing.ImportPricesResult,
coffee.CoffeeSample,
coffee.CoffeeCuppingCriterion,
coffee.CoffeeCuppingSession,
coffee.CoffeeCuppingSessionSample,
coffee.CoffeeCuppingCup,
coffee.CoffeeCuppingResult,
coffee.CoffeeCuppingResultLine,
coffee.CoffeeLabAnalysis,
coffee.PurchaseLine,
coffee.SaleLine,
duplicate.TradeCustomDuplicateStart,
module='purchase_trade', type_='model')
Pool.register(
configuration.PurchaseConfiguration,
module='purchase', type_='model')
Pool.register(
configuration.SaleConfiguration,
module='sale', type_='model')
Pool.register(
incoming.ImportSwift,
incoming.PrepareDocuments,
incoming.AnalyzeConditions,
lc.CreateLCWizard,
pricing.ImportPrices,
duplicate.TradeCustomDuplicate,
module='purchase_trade', type_='wizard'
)
Pool.register(
@@ -150,15 +86,6 @@ def register():
party.PartyExecutionPlace,
payment_term.PaymentTerm,
payment_term.PaymentTermLine,
stock.CharterRateType,
stock.CharterConditionRate,
stock.CharterConditionLaytimeStartRule,
stock.CharterCondition,
stock.CharterParty,
purchase.ContractClause,
purchase.ContractTemplate,
purchase.ContractTemplateLine,
purchase.ContractClauseSelection,
purchase.Purchase,
purchase.Line,
purchase.Estimated,
@@ -177,7 +104,6 @@ def register():
open_position.OpenPositionContext,
optional.OptionalScenario,
fee.Fee,
fee.FeeRule,
fee.FeeLots,
purchase.FeeLots,
valuation.Valuation,
@@ -185,9 +111,6 @@ def register():
valuation.ValuationDyn,
valuation.ValuationReport,
valuation.ValuationReportContext,
valuation.ValuationProcessDimension,
valuation.ValuationProcessStart,
valuation.ValuationProcessResult,
derivative.Derivative,
derivative.DerivativeMatch,
derivative.MatchWizardStart,
@@ -195,18 +118,16 @@ def register():
derivative.DerivativeReportContext,
fee.FeeReport,
fee.FeeContext,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PurchasePnlGraph,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
stock.Location,
stock.InvoiceLine,
stock.ShipmentIn,
stock.ShipmentControllerEmailStart,
stock.ShipmentWR,
stock.ShipmentIn,
stock.ShipmentWR,
stock.ShipmentInternal,
stock.ShipmentOut,
stock.StatementOfFacts,
@@ -214,19 +135,16 @@ def register():
stock.ImportSoFStart,
stock.RevaluateStart,
stock.Account,
stock.AccountMoveLine,
stock.AccountMove,
stock.ContainerType,
stock.ShipmentContainer,
lot.Lot,
lot.LotQt,
lot.LotReport,
lot.LotContext,
lot.LotShippingStart,
lot.LotPlanTransportStart,
lot.LotPlanTransportLine,
lot.LotMarkFinishedStart,
lot.LotMatchingStart,
stock.AccountMoveLine,
stock.AccountMove,
stock.ContainerType,
stock.ShipmentContainer,
lot.Lot,
lot.LotQt,
lot.LotReport,
lot.LotContext,
lot.LotShippingStart,
lot.LotMatchingStart,
lot.LotWeighingStart,
lot.LotAddLot,
lot.LotInvoicingLot,
@@ -236,12 +154,10 @@ def register():
lot.LotImportLot,
lot.LotInvoiceStart,
lot.LotMatchingLot,
lot.LotWeighingLot,
lot.ContractsStart,
lot.ContractDetail,
lot.MirrorStart,
lot.MirrorLine,
lot.LotFCR,
lot.LotWeighingLot,
lot.ContractsStart,
lot.ContractDetail,
lot.LotFCR,
lot.LotMove,
lot.LotAccountingGraph,
workflow.ExecutionPlan,
@@ -257,18 +173,17 @@ def register():
purchase.DocTypeTemplate,
purchase.PurchaseStrategy,
purchase.PriceComposition,
purchase.PremiumComposition,
purchase.QualityAnalysis,
purchase.Assay,
purchase.AssayLine,
purchase.AssayElement,
purchase.AssayUnit,
purchase.PayableRule,
purchase.PenaltyRule,
purchase.PenaltyRuleTier,
purchase.ConcentrateTerm,
backtoback.Backtoback,
dimension.AnalyticDimension,
purchase.PenaltyRule,
purchase.PenaltyRuleTier,
purchase.ConcentrateTerm,
backtoback.Backtoback,
dimension.AnalyticDimension,
dimension.AnalyticDimensionValue,
dimension.AnalyticDimensionAssignment,
weight_report.WeightReport,
@@ -276,13 +191,6 @@ def register():
Pool.register(
account.PhysicalTradeIFRS,
module='purchase_trade', type_='model')
Pool.register(
configuration.AccountConfiguration,
configuration.AccountConfigurationDefaultAccount,
invoice.InvoiceLineLotWeight,
invoice.InvoicePaddingReport,
invoice.InvoicePaddingContext,
module='purchase_trade', type_='model')
Pool.register(
invoice.Invoice,
invoice.InvoiceLine,
@@ -308,8 +216,6 @@ def register():
purchase.PurchaseCrop,
module='lot', type_='model')
Pool.register(
sale.ContractClauseSelection,
sale.CharterCondition,
sale.Sale,
sale.SaleLine,
sale.SaleCreatePurchaseInput,
@@ -332,56 +238,37 @@ def register():
sale.OpenPosition,
sale.Backtoback,
sale.AnalyticDimensionAssignment,
sale.PriceComposition,
sale.PremiumComposition,
module='sale', type_='model')
sale.PriceComposition,
module='sale', type_='model')
Pool.register(
lot.LotShipping,
lot.LotPlanTransport,
lot.LotMatching,
lot.LotGoMatching,
lot.LotShipping,
lot.LotMatching,
#lot.LotMatchingUnit,
lot.LotWeighing,
lot.CreateContracts,
lot.CreateMirror,
lot.LotUnmatch,
lot.LotMarkFinished,
lot.LotUnship,
lot.LotWeighing,
lot.CreateContracts,
lot.LotUnmatch,
lot.LotUnship,
lot.LotRemove,
lot.LotInvoice,
lot.LotAdding,
lot.LotImporting,
stock.ShipmentControllerEmail,
stock.FindVessel,
lot.LotAdding,
lot.LotImporting,
stock.FindVessel,
stock.SofUpdate,
stock.Revaluate,
purchase.GoToBi,
purchase_prepayment.CreatePrepaymentWizard,
purchase.PurchaseAllocationsWizard,
purchase.InvoicePayment,
stock.ImportSoFWizard,
dashboard.BotWizard,
dashboard.DashboardLoader,
forex.ForexReport,
purchase.PnlReport,
stock.ImportSoFWizard,
dashboard.BotWizard,
dashboard.DashboardLoader,
forex.ForexReport,
purchase.PnlReport,
purchase.PositionReport,
valuation.ValuationProcess,
derivative.DerivativeMatchWizard,
module='purchase', type_='wizard')
Pool.register(
sale.SaleCreatePurchase,
sale.SaleAllocationsWizard,
module='sale', type_='wizard')
Pool.register(
invoice.InvoiceReport,
invoice.SaleReport,
invoice.PurchaseReport,
stock.ShipmentShippingReport,
stock.ShipmentInsuranceReport,
stock.ShipmentCOOReport,
stock.ShipmentPackingListReport,
stock.ShipmentLinkageReport,
stock.LotReportLinkageReport,
stock.LotReportLinkageFinalReport,
module='purchase_trade', type_='report')

View File

@@ -1,10 +1,9 @@
# account.py
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import PoolMeta
from trytond.pyson import Eval
__all__ = [
'PhysicalTradeIFRS',
]
__all__ = ['PhysicalTradeIFRS']
__metaclass__ = PoolMeta

View File

@@ -1,12 +1,6 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view" id="account_configuration_view_form">
<field name="model">account.configuration</field>
<field name="inherit" ref="account.configuration_view_form"/>
<field name="name">account_configuration_form</field>
</record>
<record model="res.group" id="group_physical_trade_ifrs">
<field name="name">Physical Trade IFRS</field>
</record>

File diff suppressed because it is too large Load Diff

View File

@@ -1,395 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="coffee_sample_view_tree">
<field name="model">coffee.sample</field>
<field name="type">tree</field>
<field name="name">coffee_sample_tree</field>
</record>
<record model="ir.ui.view" id="coffee_sample_view_form">
<field name="model">coffee.sample</field>
<field name="type">form</field>
<field name="name">coffee_sample_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_criterion_view_tree">
<field name="model">coffee.cupping.criterion</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_criterion_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_criterion_view_form">
<field name="model">coffee.cupping.criterion</field>
<field name="type">form</field>
<field name="name">coffee_cupping_criterion_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_view_tree">
<field name="model">coffee.cupping.session</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_session_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_view_form">
<field name="model">coffee.cupping.session</field>
<field name="type">form</field>
<field name="name">coffee_cupping_session_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_tree">
<field name="model">coffee.cupping.session.sample</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_session_sample_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_session_sample_view_form">
<field name="model">coffee.cupping.session.sample</field>
<field name="type">form</field>
<field name="name">coffee_cupping_session_sample_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_cup_view_tree">
<field name="model">coffee.cupping.cup</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_cup_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_cup_view_form">
<field name="model">coffee.cupping.cup</field>
<field name="type">form</field>
<field name="name">coffee_cupping_cup_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_view_tree">
<field name="model">coffee.cupping.result</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_result_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_view_form">
<field name="model">coffee.cupping.result</field>
<field name="type">form</field>
<field name="name">coffee_cupping_result_form</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_line_view_tree">
<field name="model">coffee.cupping.result.line</field>
<field name="type">tree</field>
<field name="name">coffee_cupping_result_line_tree</field>
</record>
<record model="ir.ui.view" id="coffee_cupping_result_line_view_form">
<field name="model">coffee.cupping.result.line</field>
<field name="type">form</field>
<field name="name">coffee_cupping_result_line_form</field>
</record>
<record model="ir.ui.view" id="coffee_lab_analysis_view_tree">
<field name="model">coffee.lab.analysis</field>
<field name="type">tree</field>
<field name="name">coffee_lab_analysis_tree</field>
</record>
<record model="ir.ui.view" id="coffee_lab_analysis_view_form">
<field name="model">coffee.lab.analysis</field>
<field name="type">form</field>
<field name="name">coffee_lab_analysis_form</field>
</record>
<record model="ir.action.act_window" id="act_coffee_sample_form">
<field name="name">Coffee Samples</field>
<field name="res_model">coffee.sample</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_sample_view_tree"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_sample_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_sample_view_form"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_pending">
<field name="name">Pending</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', 'in', ['requested', 'received', 'sent_to_lab', 'under_review'])]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expiring">
<field name="name">Expiring soon</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('expires_soon', '=', True)]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_expired">
<field name="name">Expired</field>
<field name="sequence" eval="30"/>
<field name="domain" eval="[('is_expired', '=', True)]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_approved">
<field name="name">Approved</field>
<field name="sequence" eval="40"/>
<field name="domain" eval="[('state', '=', 'approved')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_coffee_sample_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_coffee_sample_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_cupping_session_form">
<field name="name">Cupping Sessions</field>
<field name="res_model">coffee.cupping.session</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_cupping_session_view_tree"/>
<field name="act_window" ref="act_coffee_cupping_session_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_session_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_cupping_session_view_form"/>
<field name="act_window" ref="act_coffee_cupping_session_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_cupping_criterion_form">
<field name="name">Cupping Criteria</field>
<field name="res_model">coffee.cupping.criterion</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_cupping_criterion_view_tree"/>
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_cupping_criterion_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_cupping_criterion_view_form"/>
<field name="act_window" ref="act_coffee_cupping_criterion_form"/>
</record>
<record model="ir.action.act_window" id="act_coffee_lab_analysis_form">
<field name="name">Lab Analyses</field>
<field name="res_model">coffee.lab.analysis</field>
</record>
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_tree">
<field name="sequence" eval="10"/>
<field name="view" ref="coffee_lab_analysis_view_tree"/>
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
</record>
<record model="ir.action.act_window.view" id="act_coffee_lab_analysis_form_view_form">
<field name="sequence" eval="20"/>
<field name="view" ref="coffee_lab_analysis_view_form"/>
<field name="act_window" ref="act_coffee_lab_analysis_form"/>
</record>
<menuitem name="Coffee" parent="purchase.menu_purchase" sequence="18"
id="menu_coffee_purchase"/>
<menuitem name="Samples" parent="menu_coffee_purchase" sequence="10"
action="act_coffee_sample_form" id="menu_coffee_sample_purchase"/>
<menuitem name="Lab Analyses" parent="menu_coffee_purchase"
sequence="15" action="act_coffee_lab_analysis_form"
id="menu_coffee_lab_analysis_purchase"/>
<menuitem name="Cupping Sessions" parent="menu_coffee_purchase"
sequence="20" action="act_coffee_cupping_session_form"
id="menu_coffee_cupping_session_purchase"/>
<menuitem name="Cupping Criteria" parent="menu_coffee_purchase"
sequence="30" action="act_coffee_cupping_criterion_form"
id="menu_coffee_cupping_criterion_purchase"/>
<menuitem name="Coffee" parent="sale.menu_sale" sequence="18"
id="menu_coffee_sale"/>
<menuitem name="Samples" parent="menu_coffee_sale" sequence="10"
action="act_coffee_sample_form" id="menu_coffee_sample_sale"/>
<menuitem name="Lab Analyses" parent="menu_coffee_sale"
sequence="15" action="act_coffee_lab_analysis_form"
id="menu_coffee_lab_analysis_sale"/>
<menuitem name="Cupping Sessions" parent="menu_coffee_sale"
sequence="20" action="act_coffee_cupping_session_form"
id="menu_coffee_cupping_session_sale"/>
<menuitem name="Cupping Criteria" parent="menu_coffee_sale"
sequence="30" action="act_coffee_cupping_criterion_form"
id="menu_coffee_cupping_criterion_sale"/>
<record model="ir.ui.menu" id="menu_coffee_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sample_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_session_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_purchase">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_sample_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_lab_analysis_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_session_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.ui.menu" id="menu_coffee_cupping_criterion_sale">
<field name="active" eval="False"/>
</record>
<record model="ir.model.button" id="coffee_sample_receive_button">
<field name="model">coffee.sample</field>
<field name="name">receive</field>
<field name="string">Receive</field>
</record>
<record model="ir.model.button" id="coffee_sample_send_to_lab_button">
<field name="model">coffee.sample</field>
<field name="name">send_to_lab</field>
<field name="string">Send to lab</field>
</record>
<record model="ir.model.button" id="coffee_sample_review_button">
<field name="model">coffee.sample</field>
<field name="name">review</field>
<field name="string">Review</field>
</record>
<record model="ir.model.button" id="coffee_sample_approve_button">
<field name="model">coffee.sample</field>
<field name="name">approve</field>
<field name="string">Approve</field>
</record>
<record model="ir.model.button" id="coffee_sample_reject_button">
<field name="model">coffee.sample</field>
<field name="name">reject</field>
<field name="string">Reject</field>
</record>
<record model="ir.model.button" id="coffee_sample_evaluate_quality_button">
<field name="model">coffee.sample</field>
<field name="name">evaluate_quality</field>
<field name="string">Evaluate quality</field>
</record>
<record model="ir.model.button" id="coffee_sample_expire_button">
<field name="model">coffee.sample</field>
<field name="name">expire</field>
<field name="string">Expire</field>
</record>
<record model="ir.model.button" id="coffee_sample_archive_button">
<field name="model">coffee.sample</field>
<field name="name">archive</field>
<field name="string">Archive</field>
</record>
<record model="ir.model.button" id="coffee_sample_reset_to_requested_button">
<field name="model">coffee.sample</field>
<field name="name">reset_to_requested</field>
<field name="string">Reset</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_prepare_button">
<field name="model">coffee.cupping.session</field>
<field name="name">prepare</field>
<field name="string">Prepare</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_start_button">
<field name="model">coffee.cupping.session</field>
<field name="name">start</field>
<field name="string">Start</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_done_button">
<field name="model">coffee.cupping.session</field>
<field name="name">done</field>
<field name="string">Done</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_cancel_button">
<field name="model">coffee.cupping.session</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="coffee_cupping_session_draft_button">
<field name="model">coffee.cupping.session</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_send_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">send</field>
<field name="string">Send</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_receive_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">receive</field>
<field name="string">Receive</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_accept_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">accept</field>
<field name="string">Accept</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_reject_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">reject</field>
<field name="string">Reject</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_cancel_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">cancel</field>
<field name="string">Cancel</field>
</record>
<record model="ir.model.button" id="coffee_lab_analysis_draft_button">
<field name="model">coffee.lab.analysis</field>
<field name="name">draft</field>
<field name="string">Draft</field>
</record>
<record model="ir.model.access" id="access_coffee_sample">
<field name="model">coffee.sample</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_criterion">
<field name="model">coffee.cupping.criterion</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_session">
<field name="model">coffee.cupping.session</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_session_sample">
<field name="model">coffee.cupping.session.sample</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_cup">
<field name="model">coffee.cupping.cup</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_result">
<field name="model">coffee.cupping.result</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_cupping_result_line">
<field name="model">coffee.cupping.result.line</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.access" id="access_coffee_lab_analysis">
<field name="model">coffee.lab.analysis</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

View File

@@ -1,154 +0,0 @@
from trytond.pool import Pool
from trytond.transaction import Transaction
import datetime
ITSA_COMPANY_NAME = 'ITSA'
def record_id(record):
return getattr(record, 'id', record)
def is_itsa_company(company=None):
if company is None:
company_id = Transaction().context.get('company')
if not company_id:
return False
Company = Pool().get('company.company')
company = Company(company_id)
party = getattr(company, 'party', None)
return bool(party and party.name == ITSA_COMPANY_NAME)
def _search_first(model_name, domains):
Model = Pool().get(model_name)
for domain in domains:
try:
records = Model.search(domain, limit=1)
except Exception:
continue
if records:
return records[0]
def default_itsa_currency():
currency = _search_first('currency.currency', [
[('code', '=', 'USD')],
[('name', '=', 'USD')],
[('symbol', '=', 'USD')],
])
return record_id(currency) if currency else None
def default_itsa_price_fix_type():
fix_type = _search_first('price.fixtype', [
[('name', '=', 'Market Price')],
[('name', '=', 'Market price')],
[('name', 'ilike', 'Market Price')],
])
return record_id(fix_type) if fix_type else None
def default_itsa_price_calendar():
calendar = _search_first('price.calendar', [
[('name', '=', 'Argus Sulphuric Acid')],
[('name', 'ilike', 'Argus Sulphuric Acid')],
])
return record_id(calendar) if calendar else None
def default_itsa_unit():
unit = _search_first('product.uom', [
[('symbol', '=', 'Mt')],
[('name', '=', 'Mt')],
[('symbol', '=', 'MT')],
[('name', '=', 'MT')],
])
return record_id(unit) if unit else None
def default_itsa_bulk_unit():
unit = _search_first('product.uom', [
[('symbol', '=', 'Bulk')],
[('name', '=', 'Bulk')],
[('symbol', '=', 'bulk')],
[('name', '=', 'bulk')],
])
return record_id(unit) if unit else None
def _company_from_values(values):
company = values.get('company')
if company and hasattr(company, 'party'):
return company
company_id = record_id(company) or Transaction().context.get('company')
if not company_id:
return None
return Pool().get('company.company')(company_id)
def _dimension(name):
return _search_first('analytic.dimension', [
[('code', '=', name)],
[('name', '=', name)],
[('code', 'ilike', '%%%s%%' % name)],
[('name', 'ilike', '%%%s%%' % name)],
])
def _book_year_suffix(today=None):
if today is None:
today = Pool().get('ir.date').today()
year = today.year
if today < datetime.date(year, 4, 1):
year -= 1
return '%02d' % (year % 100)
def _book_value(dimension, today=None):
if not dimension:
return None
suffix = _book_year_suffix(today)
return _search_first('analytic.dimension.value', [
[
('dimension', '=', record_id(dimension)),
('code', 'ilike', '%%%s%%' % suffix),
],
[
('dimension', '=', record_id(dimension)),
('name', 'ilike', '%%%s%%' % suffix),
],
])
def default_itsa_analytic_dimension_assignments(company=None, today=None):
if not is_itsa_company(company):
return []
assignments = []
book = _dimension('Book')
if book:
value = _book_value(book, today=today)
assignments.append({
'dimension': record_id(book),
'value': record_id(value) if value else None,
})
strategy = _dimension('Strategy')
if strategy:
assignments.append({
'dimension': record_id(strategy),
'value': None,
})
return assignments
def add_itsa_analytic_dimension_defaults(values):
if values.get('analytic_dimensions'):
return
company = _company_from_values(values)
assignments = default_itsa_analytic_dimension_assignments(company=company)
if assignments:
values['analytic_dimensions'] = [('create', assignments)]

View File

@@ -1,292 +0,0 @@
from sql import Table
from sql.operators import Exists
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
class PurchaseConfiguration(metaclass=PoolMeta):
__name__ = 'purchase.configuration'
allow_modification_after_validation = fields.Boolean(
"Autorise modification after validation")
auto_hedging = fields.Boolean("Auto hedge")
auto_hedging_over = fields.Boolean("Over hedge")
@classmethod
def default_allow_modification_after_validation(cls):
return False
@classmethod
def default_auto_hedging(cls):
return False
@classmethod
def default_auto_hedging_over(cls):
return False
class SaleConfiguration(metaclass=PoolMeta):
__name__ = 'sale.configuration'
allow_modification_after_validation = fields.Boolean(
"Autorise modification after validation")
auto_hedging = fields.Boolean("Auto hedge")
auto_hedging_over = fields.Boolean("Over hedge")
@classmethod
def default_allow_modification_after_validation(cls):
return False
@classmethod
def default_auto_hedging(cls):
return False
@classmethod
def default_auto_hedging_over(cls):
return False
class AccountConfiguration(metaclass=PoolMeta):
__name__ = 'account.configuration'
default_sale_padding_account = fields.MultiValue(fields.Many2One(
'account.account', "Default Sale Padding",
domain=[
('closed', '!=', True),
('type.revenue', '=', True),
('company', '=', Eval('context', {}).get('company', -1)),
]))
default_accrual_padding_account = fields.MultiValue(fields.Many2One(
'account.account', "Default Accrual Padding",
domain=[
('closed', '!=', True),
('type.statement', '=', 'balance'),
('company', '=', Eval('context', {}).get('company', -1)),
]))
@classmethod
def multivalue_model(cls, field):
pool = Pool()
if field in {
'default_sale_padding_account',
'default_accrual_padding_account',
}:
return pool.get('account.configuration.default_account')
return super().multivalue_model(field)
class AccountConfigurationDefaultAccount(metaclass=PoolMeta):
__name__ = 'account.configuration.default_account'
default_sale_padding_account = fields.Many2One(
'account.account', "Default Sale Padding",
domain=[
('closed', '!=', True),
('type.revenue', '=', True),
('company', '=', Eval('company', -1)),
])
default_accrual_padding_account = fields.Many2One(
'account.account', "Default Accrual Padding",
domain=[
('closed', '!=', True),
('type.statement', '=', 'balance'),
('company', '=', Eval('company', -1)),
])
class Configuration(ModelSingleton, ModelSQL, ModelView):
"Purchase Trade Configuration"
__name__ = 'purchase_trade.configuration'
_REPORT_LABELS = (
('sale_report_label', 'sale', 'report_sale', 'Proforma'),
('sale_commission_report_label', 'account_invoice',
'report_sale_commission_ict', 'Commission invoice Sale'),
('sale_bill_report_label', 'sale', 'report_bill', 'Draft'),
('invoice_report_label', 'account_invoice', 'report_invoice',
'Invoice'),
('invoice_cndn_report_label', 'account_invoice',
'report_invoice_ict_final', 'CN/DN'),
('invoice_commission_cndn_report_label', 'account_invoice',
'report_invoice_ict_commission_final', 'CN/DN Commission'),
('invoice_prepayment_report_label', 'account_invoice',
'report_prepayment', 'Prepayment'),
('invoice_packing_list_report_label', 'purchase_trade',
'report_invoice_packing_list', 'Packing List'),
('invoice_payment_order_report_label', 'purchase_trade',
'report_payment_order', 'Payment Order'),
('purchase_report_label', 'purchase', 'report_purchase', 'Purchase'),
('purchase_commission_report_label', 'account_invoice',
'report_purchase_commission_ict', 'Commission invoice Purchase'),
('shipment_shipping_report_label', 'stock',
'report_shipment_in_shipping', 'Shipping instructions'),
('shipment_insurance_report_label', 'purchase_trade',
'report_shipment_in_insurance', 'Insurance'),
('shipment_coo_report_label', 'purchase_trade',
'report_shipment_in_coo', 'COO'),
('shipment_packing_list_report_label', 'purchase_trade',
'report_shipment_in_packing_list', 'Packing List'),
('shipment_linkage_report_label', 'purchase_trade',
'report_shipment_in_linkage', 'Linkage'),
)
_COFFEE_MENU_XML_IDS = (
'menu_coffee_purchase',
'menu_coffee_sample_purchase',
'menu_coffee_lab_analysis_purchase',
'menu_coffee_cupping_session_purchase',
'menu_coffee_cupping_criterion_purchase',
'menu_coffee_sale',
'menu_coffee_sample_sale',
'menu_coffee_lab_analysis_sale',
'menu_coffee_cupping_session_sale',
'menu_coffee_cupping_criterion_sale',
)
_ORPHAN_ACT_WINDOW_XML_IDS = (
'act_itsa_operations_workflow',
'act_tradon_processes',
)
pricing_rule = fields.Text("Pricing Rule")
active_coffee_compatibility = fields.Boolean(
"Active coffee compatibility")
sale_report_template = fields.Char("Sale Template")
sale_report_label = fields.Char("Sale Menu Label")
sale_commission_report_template = fields.Char(
"Sale Commission Template")
sale_commission_report_label = fields.Char(
"Sale Commission Menu Label")
sale_bill_report_template = fields.Char("Sale Bill Template")
sale_bill_report_label = fields.Char("Sale Bill Menu Label")
sale_final_report_template = fields.Char("Sale Final Template")
invoice_report_template = fields.Char("Invoice Template")
invoice_report_label = fields.Char("Invoice Menu Label")
invoice_cndn_report_template = fields.Char("CN/DN Template")
invoice_cndn_report_label = fields.Char("CN/DN Menu Label")
invoice_commission_cndn_report_template = fields.Char(
"CN/DN Commission Template")
invoice_commission_cndn_report_label = fields.Char(
"CN/DN Commission Menu Label")
invoice_prepayment_report_template = fields.Char("Prepayment Template")
invoice_prepayment_report_label = fields.Char("Prepayment Menu Label")
invoice_packing_list_report_template = fields.Char("Packing List Template")
invoice_packing_list_report_label = fields.Char("Packing List Menu Label")
invoice_payment_order_report_template = fields.Char("Payment Order Template")
invoice_payment_order_report_label = fields.Char(
"Payment Order Menu Label")
purchase_report_template = fields.Char("Purchase Template")
purchase_report_label = fields.Char("Purchase Menu Label")
purchase_commission_report_template = fields.Char(
"Purchase Commission Template")
purchase_commission_report_label = fields.Char(
"Purchase Commission Menu Label")
shipment_shipping_report_template = fields.Char("Shipping Template")
shipment_shipping_report_label = fields.Char("Shipping Menu Label")
shipment_insurance_report_template = fields.Char("Insurance Template")
shipment_insurance_report_label = fields.Char("Insurance Menu Label")
shipment_coo_report_template = fields.Char("COO Template")
shipment_coo_report_label = fields.Char("COO Menu Label")
shipment_packing_list_report_template = fields.Char("Packing List Template")
shipment_packing_list_report_label = fields.Char(
"Packing List Menu Label")
shipment_linkage_report_template = fields.Char("Linkage Template")
shipment_linkage_report_label = fields.Char("Linkage Menu Label")
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
if module_name == 'purchase_trade':
cls._cleanup_orphan_act_window_xml_ids()
@classmethod
def _cleanup_orphan_act_window_xml_ids(cls):
cursor = Transaction().connection.cursor()
model_data = Table('ir_model_data')
act_window = Table('ir_action_act_window')
cursor.execute(*model_data.delete(
where=(model_data.module == 'purchase_trade')
& (model_data.model == 'ir.action.act_window')
& model_data.fs_id.in_(cls._ORPHAN_ACT_WINDOW_XML_IDS)
& ~Exists(act_window.select(
act_window.id,
where=act_window.id == model_data.db_id))))
@classmethod
def default_active_coffee_compatibility(cls):
return False
@classmethod
def create(cls, vlist):
records = super().create(vlist)
cls._sync_report_labels(records)
active = any(
bool(values.get(
'active_coffee_compatibility',
cls.default_active_coffee_compatibility()))
for values in vlist)
cls._sync_coffee_menu_visibility(records, active=active)
cls._clear_coffee_view_cache()
return records
@classmethod
def write(cls, *args):
actions = list(zip(args[::2], args[1::2]))
active = None
for records, values in actions:
if 'active_coffee_compatibility' in values:
active = bool(values['active_coffee_compatibility'])
super().write(*args)
records = sum(args[::2], [])
cls._sync_report_labels(records)
cls._sync_coffee_menu_visibility(records, active=active)
if active is not None:
cls._clear_coffee_view_cache()
@classmethod
def _sync_report_labels(cls, records):
if not records:
return
pool = Pool()
ModelData = pool.get('ir.model.data')
ActionReport = pool.get('ir.action.report')
to_write = []
for record in records:
for field_name, module, xml_id, default_label in cls._REPORT_LABELS:
label = (getattr(record, field_name, '') or '').strip()
action_id = ModelData.get_id(module, xml_id)
action = ActionReport(action_id)
target_label = label or default_label
if getattr(action, 'name', '') != target_label:
to_write.extend(([action], {'name': target_label}))
if to_write:
with Transaction().set_user(0):
ActionReport.write(*to_write)
@classmethod
def _sync_coffee_menu_visibility(cls, records=None, active=None):
if active is None:
configurations = cls.search([], limit=1)
active = bool(
configurations
and configurations[0].active_coffee_compatibility)
pool = Pool()
ModelData = pool.get('ir.model.data')
Menu = pool.get('ir.ui.menu')
menus = []
for xml_id in cls._COFFEE_MENU_XML_IDS:
try:
menu_id = ModelData.get_id('purchase_trade', xml_id)
except Exception:
continue
menus.append(Menu(menu_id))
if menus:
with Transaction().set_user(0):
Menu.write(menus, {'active': active})
@classmethod
def _clear_coffee_view_cache(cls):
ModelView._fields_view_get_cache.clear()

View File

@@ -1,72 +0,0 @@
<tryton>
<data>
<record model="ir.ui.view" id="purchase_trade_configuration_view_form">
<field name="model">purchase_trade.configuration</field>
<field name="type">form</field>
<field name="name">configuration_form</field>
</record>
<record model="ir.ui.view" id="purchase_trade_template_configuration_view_form">
<field name="model">purchase_trade.configuration</field>
<field name="type">form</field>
<field name="name">template_configuration_form</field>
</record>
<record model="ir.ui.view" id="purchase_configuration_view_form">
<field name="model">purchase.configuration</field>
<field name="inherit" ref="purchase.purchase_configuration_view_form"/>
<field name="name">purchase_configuration_form</field>
</record>
<record model="ir.ui.view" id="sale_configuration_view_form">
<field name="model">sale.configuration</field>
<field name="inherit" ref="sale.sale_configuration_view_form"/>
<field name="name">sale_configuration_form</field>
</record>
<record model="ir.action.act_window" id="act_purchase_trade_configuration_form">
<field name="name">Trade Configuration</field>
<field name="res_model">purchase_trade.configuration</field>
</record>
<record model="ir.action.act_window.view" id="act_purchase_trade_configuration_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_trade_configuration_view_form"/>
<field name="act_window" ref="act_purchase_trade_configuration_form"/>
</record>
<record model="ir.action.act_window" id="act_purchase_trade_template_configuration_form">
<field name="name">Document Templates</field>
<field name="res_model">purchase_trade.configuration</field>
</record>
<record model="ir.action.act_window.view" id="act_purchase_trade_template_configuration_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="purchase_trade_template_configuration_view_form"/>
<field name="act_window" ref="act_purchase_trade_template_configuration_form"/>
</record>
<menuitem
name="Trade Configuration"
parent="price.menu_price"
action="act_purchase_trade_configuration_form"
sequence="10"
id="menu_purchase_trade_configuration"
icon="tryton-settings"/>
<menuitem
name="Trade Configuration"
parent="purchase.menu_configuration"
action="act_purchase_trade_configuration_form"
sequence="90"
id="menu_purchase_trade_configuration_purchase"
icon="tryton-settings"/>
<menuitem
name="Trade Configuration"
parent="sale.menu_configuration"
action="act_purchase_trade_configuration_form"
sequence="90"
id="menu_purchase_trade_configuration_sale"
icon="tryton-settings"/>
<menuitem
name="Document Templates"
parent="document_incoming.menu_configuration"
action="act_purchase_trade_template_configuration_form"
sequence="20"
id="menu_purchase_trade_template_configuration"
icon="tryton-settings"/>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

View File

@@ -12,15 +12,9 @@ class Cron(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
cls.method.selection.append(
('forex.cron|update_forex', "Update Forex Prices"))
cls.method.selection.append(
('valuation.valuation|update_daily_snapshot',
"Update Valuation Snapshot"))
cls.method.selection.append(
('pricing.pricing|update_daily_pricing',
"Update Pricing"))
super().__setup__()
cls.method.selection.append(
('forex.cron|update_forex', "Update Forex Prices"))
class PriceCron(ModelSQL, ModelView):
"Price Cron"

View File

@@ -36,22 +36,10 @@
<field name="model" search="[('model', '=', 'forex.cron')]"/>
</record> -->
<record model="ir.cron" id="cron_cron">
<field name="method">forex.cron|update_forex</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
<record model="ir.cron" id="valuation_daily_snapshot_cron">
<field name="method">valuation.valuation|update_daily_snapshot</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
<record model="ir.cron" id="pricing_daily_update_cron">
<field name="method">pricing.pricing|update_daily_pricing</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
</data>
</tryton>
<record model="ir.cron" id="cron_cron">
<field name="method">forex.cron|update_forex</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">days</field>
</record>
</data>
</tryton>

File diff suppressed because it is too large Load Diff

View File

@@ -1,486 +0,0 @@
<tryton>
<data>
<record model="ir.ui.view" id="ctrm_position_physical_context_view_form">
<field name="model">ctrm.reporting.position.physical.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_physical_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_physical_view_list">
<field name="model">ctrm.reporting.position.physical</field>
<field name="type">tree</field>
<field name="name">ctrm_position_physical_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_physical">
<field name="name">Physical Position</field>
<field name="res_model">ctrm.reporting.position.physical</field>
<field name="context_model">ctrm.reporting.position.physical.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_physical_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_physical_view_list"/>
<field name="act_window" ref="act_ctrm_position_physical"/>
</record>
<record model="ir.ui.view" id="ctrm_position_financial_context_view_form">
<field name="model">ctrm.reporting.position.financial.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_financial_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_financial_view_list">
<field name="model">ctrm.reporting.position.financial</field>
<field name="type">tree</field>
<field name="name">ctrm_position_financial_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_financial">
<field name="name">Financial Position</field>
<field name="res_model">ctrm.reporting.position.financial</field>
<field name="context_model">ctrm.reporting.position.financial.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_financial_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_financial_view_list"/>
<field name="act_window" ref="act_ctrm_position_financial"/>
</record>
<record model="ir.ui.view" id="ctrm_position_net_context_view_form">
<field name="model">ctrm.reporting.position.net.context</field>
<field name="type">form</field>
<field name="name">ctrm_position_net_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_position_net_view_list">
<field name="model">ctrm.reporting.position.net</field>
<field name="type">tree</field>
<field name="name">ctrm_position_net_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_position_net">
<field name="name">Net Consolidated Position</field>
<field name="res_model">ctrm.reporting.position.net</field>
<field name="context_model">ctrm.reporting.position.net.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_position_net_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_position_net_view_list"/>
<field name="act_window" ref="act_ctrm_position_net"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_context_view_form">
<field name="model">ctrm.reporting.pnl.realized.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_realized_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_realized_view_list">
<field name="model">ctrm.reporting.pnl.realized</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_realized_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_realized">
<field name="name">Realized P&amp;L</field>
<field name="res_model">ctrm.reporting.pnl.realized</field>
<field name="context_model">ctrm.reporting.pnl.realized.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_realized_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_realized_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_realized"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_context_view_form">
<field name="model">ctrm.reporting.pnl.mtm.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_mtm_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_mtm_view_list">
<field name="model">ctrm.reporting.pnl.mtm</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_mtm_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_mtm">
<field name="name">Mark-to-Market</field>
<field name="res_model">ctrm.reporting.pnl.mtm</field>
<field name="context_model">ctrm.reporting.pnl.mtm.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_mtm_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_mtm_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_mtm"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_explain_context_view_form">
<field name="model">ctrm.reporting.pnl.explain.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_explain_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_explain_view_list">
<field name="model">ctrm.reporting.pnl.explain</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_explain_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_explain">
<field name="name">P&amp;L Explain</field>
<field name="res_model">ctrm.reporting.pnl.explain</field>
<field name="context_model">ctrm.reporting.pnl.explain.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_explain_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_explain_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_explain"/>
</record>
<record model="ir.ui.view" id="ctrm_pnl_dimension_context_view_form">
<field name="model">ctrm.reporting.pnl.dimension.context</field>
<field name="type">form</field>
<field name="name">ctrm_pnl_dimension_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_pnl_dimension_view_list">
<field name="model">ctrm.reporting.pnl.dimension</field>
<field name="type">tree</field>
<field name="name">ctrm_pnl_dimension_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_pnl_dimension">
<field name="name">P&amp;L by Dimension</field>
<field name="res_model">ctrm.reporting.pnl.dimension</field>
<field name="context_model">ctrm.reporting.pnl.dimension.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_pnl_dimension_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_pnl_dimension_view_list"/>
<field name="act_window" ref="act_ctrm_pnl_dimension"/>
</record>
<record model="ir.ui.view" id="ctrm_credit_risk_context_view_form">
<field name="model">ctrm.reporting.risk.credit.context</field>
<field name="type">form</field>
<field name="name">ctrm_credit_risk_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_credit_risk_view_list">
<field name="model">ctrm.reporting.risk.credit</field>
<field name="type">tree</field>
<field name="name">ctrm_credit_risk_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_credit_risk">
<field name="name">Credit Risk</field>
<field name="res_model">ctrm.reporting.risk.credit</field>
<field name="context_model">ctrm.reporting.risk.credit.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_credit_risk_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_credit_risk_view_list"/>
<field name="act_window" ref="act_ctrm_credit_risk"/>
</record>
<record model="ir.ui.view" id="ctrm_shipping_logistics_context_view_form">
<field name="model">ctrm.reporting.operations.shipping.context</field>
<field name="type">form</field>
<field name="name">ctrm_shipping_logistics_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_shipping_logistics_view_list">
<field name="model">ctrm.reporting.operations.shipping</field>
<field name="type">tree</field>
<field name="name">ctrm_shipping_logistics_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_shipping_logistics">
<field name="name">Shipping Logistics</field>
<field name="res_model">ctrm.reporting.operations.shipping</field>
<field name="context_model">ctrm.reporting.operations.shipping.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_shipping_logistics_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_shipping_logistics_view_list"/>
<field name="act_window" ref="act_ctrm_shipping_logistics"/>
</record>
<record model="ir.ui.view" id="ctrm_inventory_context_view_form">
<field name="model">ctrm.reporting.operations.inventory.context</field>
<field name="type">form</field>
<field name="name">ctrm_inventory_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_inventory_view_list">
<field name="model">ctrm.reporting.operations.inventory</field>
<field name="type">tree</field>
<field name="name">ctrm_inventory_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_inventory">
<field name="name">Inventory</field>
<field name="res_model">ctrm.reporting.operations.inventory</field>
<field name="context_model">ctrm.reporting.operations.inventory.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_inventory_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_inventory_view_list"/>
<field name="act_window" ref="act_ctrm_inventory"/>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_context_view_form">
<field name="model">ctrm.reporting.operations.contract.context</field>
<field name="type">form</field>
<field name="name">ctrm_contract_performance_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_contract_performance_view_list">
<field name="model">ctrm.reporting.operations.contract</field>
<field name="type">tree</field>
<field name="name">ctrm_contract_performance_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_contract_performance">
<field name="name">Contract Performance</field>
<field name="res_model">ctrm.reporting.operations.contract</field>
<field name="context_model">ctrm.reporting.operations.contract.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_contract_performance_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_contract_performance_view_list"/>
<field name="act_window" ref="act_ctrm_contract_performance"/>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_context_view_form">
<field name="model">ctrm.reporting.operations.scheduling.context</field>
<field name="type">form</field>
<field name="name">ctrm_scheduling_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_scheduling_view_list">
<field name="model">ctrm.reporting.operations.scheduling</field>
<field name="type">tree</field>
<field name="name">ctrm_scheduling_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_scheduling">
<field name="name">Scheduling</field>
<field name="res_model">ctrm.reporting.operations.scheduling</field>
<field name="context_model">ctrm.reporting.operations.scheduling.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_scheduling_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_scheduling_view_list"/>
<field name="act_window" ref="act_ctrm_scheduling"/>
</record>
<record model="ir.ui.view" id="ctrm_accruals_context_view_form">
<field name="model">ctrm.reporting.finance.accruals.context</field>
<field name="type">form</field>
<field name="name">ctrm_accruals_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_accruals_view_list">
<field name="model">ctrm.reporting.finance.accruals</field>
<field name="type">tree</field>
<field name="name">ctrm_accruals_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_accruals">
<field name="name">Accruals</field>
<field name="res_model">ctrm.reporting.finance.accruals</field>
<field name="context_model">ctrm.reporting.finance.accruals.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_accruals_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_accruals_view_list"/>
<field name="act_window" ref="act_ctrm_accruals"/>
</record>
<record model="ir.ui.view" id="ctrm_settlements_context_view_form">
<field name="model">ctrm.reporting.finance.settlements.context</field>
<field name="type">form</field>
<field name="name">ctrm_settlements_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_settlements_view_list">
<field name="model">ctrm.reporting.finance.settlements</field>
<field name="type">tree</field>
<field name="name">ctrm_settlements_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_settlements">
<field name="name">Settlements and Invoicing</field>
<field name="res_model">ctrm.reporting.finance.settlements</field>
<field name="context_model">ctrm.reporting.finance.settlements.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_settlements_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_settlements_view_list"/>
<field name="act_window" ref="act_ctrm_settlements"/>
</record>
<record model="ir.ui.view" id="ctrm_cashflow_context_view_form">
<field name="model">ctrm.reporting.finance.cashflow.context</field>
<field name="type">form</field>
<field name="name">ctrm_cashflow_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_cashflow_view_list">
<field name="model">ctrm.reporting.finance.cashflow</field>
<field name="type">tree</field>
<field name="name">ctrm_cashflow_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_cashflow">
<field name="name">Cash Flow Forecast</field>
<field name="res_model">ctrm.reporting.finance.cashflow</field>
<field name="context_model">ctrm.reporting.finance.cashflow.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_cashflow_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_cashflow_view_list"/>
<field name="act_window" ref="act_ctrm_cashflow"/>
</record>
<record model="ir.ui.view" id="ctrm_gl_reconciliation_context_view_form">
<field name="model">ctrm.reporting.finance.gl.context</field>
<field name="type">form</field>
<field name="name">ctrm_gl_reconciliation_context_form</field>
</record>
<record model="ir.ui.view" id="ctrm_gl_reconciliation_view_list">
<field name="model">ctrm.reporting.finance.gl</field>
<field name="type">tree</field>
<field name="name">ctrm_gl_reconciliation_list</field>
</record>
<record model="ir.action.act_window" id="act_ctrm_gl_reconciliation">
<field name="name">GL Reconciliation</field>
<field name="res_model">ctrm.reporting.finance.gl</field>
<field name="context_model">ctrm.reporting.finance.gl.context</field>
</record>
<record model="ir.action.act_window.view" id="act_ctrm_gl_reconciliation_view">
<field name="sequence" eval="10"/>
<field name="view" ref="ctrm_gl_reconciliation_view_list"/>
<field name="act_window" ref="act_ctrm_gl_reconciliation"/>
</record>
<menuitem
name="Positions"
parent="purchase_trade.menu_global_reporting"
sequence="10"
id="menu_ctrm_positions"
icon="tradon-reporting-positions"/>
<menuitem
name="Physical Position"
parent="menu_ctrm_positions_physical_group"
sequence="10"
action="act_ctrm_position_physical"
id="menu_ctrm_position_physical"/>
<menuitem
name="Financial Position"
parent="menu_ctrm_positions_financial_group"
sequence="10"
action="act_ctrm_position_financial"
id="menu_ctrm_position_financial"/>
<menuitem
name="Net Consolidated Position"
parent="menu_ctrm_positions"
sequence="30"
action="act_ctrm_position_net"
id="menu_ctrm_position_net"/>
<menuitem
name="Hedge Coverage"
parent="menu_ctrm_positions"
sequence="40"
id="menu_ctrm_position_hedge"/>
<menuitem
name="P&amp;L"
parent="purchase_trade.menu_global_reporting"
sequence="20"
id="menu_ctrm_pnl"
icon="tradon-reporting-pnl"/>
<menuitem
name="Realized P&amp;L"
parent="menu_ctrm_pnl"
sequence="10"
action="act_ctrm_pnl_realized"
id="menu_ctrm_pnl_realized"/>
<menuitem
name="Mark-to-Market"
parent="menu_ctrm_pnl"
sequence="20"
action="act_ctrm_pnl_mtm"
id="menu_ctrm_pnl_mtm"/>
<menuitem
name="P&amp;L Explain"
parent="menu_ctrm_pnl"
sequence="30"
action="act_ctrm_pnl_explain"
id="menu_ctrm_pnl_explain"/>
<menuitem
name="P&amp;L by Dimension"
parent="menu_ctrm_pnl"
sequence="40"
action="act_ctrm_pnl_dimension"
id="menu_ctrm_pnl_dimension"/>
<menuitem
name="Risk"
parent="purchase_trade.menu_global_reporting"
sequence="30"
id="menu_ctrm_risk"
icon="tradon-reporting-risk"/>
<menuitem
name="VaR"
parent="menu_ctrm_risk"
sequence="10"
action="risk.act_pos_report_form"
id="menu_ctrm_risk_var"/>
<menuitem
name="Stress Tests"
parent="menu_ctrm_risk"
sequence="20"
id="menu_ctrm_risk_stress"/>
<menuitem
name="Sensitivities"
parent="menu_ctrm_risk"
sequence="30"
id="menu_ctrm_risk_sensitivities"/>
<menuitem
name="Limit Monitoring"
parent="menu_ctrm_risk"
sequence="40"
id="menu_ctrm_risk_limits"/>
<menuitem
name="Credit Risk"
parent="menu_ctrm_risk"
sequence="50"
action="act_ctrm_credit_risk"
id="menu_ctrm_risk_credit"/>
<menuitem
name="Operations &amp; Logistics"
parent="purchase_trade.menu_global_reporting"
sequence="40"
id="menu_ctrm_operations"
icon="tradon-reporting-operations"/>
<menuitem
name="Shipping Logistics"
parent="menu_ctrm_operations"
sequence="10"
action="act_ctrm_shipping_logistics"
id="menu_ctrm_operations_shipping"/>
<menuitem
name="Inventory"
parent="menu_ctrm_operations"
sequence="20"
action="act_ctrm_inventory"
id="menu_ctrm_operations_inventory"/>
<menuitem
name="Contract Performance"
parent="menu_ctrm_operations"
sequence="30"
action="act_ctrm_contract_performance"
id="menu_ctrm_operations_contract"/>
<menuitem
name="Scheduling"
parent="menu_ctrm_operations"
sequence="40"
action="act_ctrm_scheduling"
id="menu_ctrm_operations_scheduling"/>
<menuitem
name="Finance &amp; Accounting"
parent="purchase_trade.menu_global_reporting"
sequence="50"
id="menu_ctrm_finance"
icon="tradon-reporting-finance"/>
<menuitem
name="Accruals"
parent="menu_ctrm_finance"
sequence="10"
action="act_ctrm_accruals"
id="menu_ctrm_finance_accruals"/>
<menuitem
name="Hedge Accounting"
parent="menu_ctrm_finance"
sequence="20"
id="menu_ctrm_finance_hedge_accounting"/>
<menuitem
name="Settlements and Invoicing"
parent="menu_ctrm_finance"
sequence="30"
action="act_ctrm_settlements"
id="menu_ctrm_finance_settlements"/>
<menuitem
name="Cash Flow Forecast"
parent="menu_ctrm_finance"
sequence="40"
action="act_ctrm_cashflow"
id="menu_ctrm_finance_cash_flow"/>
<menuitem
name="GL Reconciliation"
parent="menu_ctrm_finance"
sequence="50"
action="act_ctrm_gl_reconciliation"
id="menu_ctrm_finance_gl_reconciliation"/>
</data>
</tryton>

View File

@@ -193,29 +193,16 @@ class Dashboard(ModelSQL, ModelView):
self.chatbot = 'chatbot:' + json.dumps(dial, ensure_ascii=False)
logger.info("EXITONCHANGE",self.chatbot)
@staticmethod
def _empty_fx_rates():
return (None, None, None, None, None, None, None, None, None, None)
@classmethod
def _find_currency_by_code(cls, code):
Currency = Pool().get('currency.currency')
currencies = Currency.search([('name', '=', code)], limit=1)
if not currencies:
currencies = Currency.search([('code', '=', code)], limit=1)
return currencies[0] if currencies else None
def get_last_five_fx_rates(self, from_code='USD', to_code='EUR'):
"""
Retourne (dernier_taux, avant_dernier_taux) pour le couple de devises.
"""
Currency = Pool().get('currency.currency')
CurrencyRate = Pool().get('currency.currency.rate')
# Récupérer les devises EUR et USD
from_currency = self._find_currency_by_code(from_code)
to_currency = self._find_currency_by_code(to_code)
if not from_currency or not to_currency:
return self._empty_fx_rates()
from_currency = Currency.search([('name', '=', from_code)])[0]
to_currency = Currency.search([('name', '=', to_code)])[0]
# Recherche des taux de la devise de base (ex: USD)
rates = CurrencyRate.search(
@@ -225,7 +212,7 @@ class Dashboard(ModelSQL, ModelView):
)
if not rates:
return self._empty_fx_rates()
return None, None, None, None, None
# Calcul du taux EUR/USD
# Si la devise principale de la société est EUR, et que le taux stocké est
@@ -245,13 +232,7 @@ class Dashboard(ModelSQL, ModelView):
# last_rate = 1 / last_rate if last_rate else None
# prev_rate = 1 / prev_rate if prev_rate else None
return (
round(1/f1,6) if f1 else None,
round(1/f2,6) if f2 else None,
round(1/f3,6) if f3 else None,
round(1/f4,6) if f4 else None,
round(1/f5,6) if f5 else None,
d1, d2, d3, d4, d5)
return round(1/f1,6), round(1/f2,6) if f2 else None, round(1/f3,6) if f3 else None, round(1/f4,6) if f4 else None, round(1/f5,6) if f5 else None, d1, d2, d3, d4, d5
def get_tremor(self,name):
Date = Pool().get('ir.date')
@@ -449,7 +430,7 @@ class Dashboard(ModelSQL, ModelView):
News = Pool().get('news.news')
Date = Pool().get('ir.date')
news_list = News.search([('active', '=', True)], limit=5, order=[('publish_date', 'DESC')])
last_rate, prev_rate = self.get_last_five_fx_rates()[:2]
last_rate,prev_rate, = self.get_last_five_fx_rates()
if last_rate and prev_rate:
variation = ((last_rate - prev_rate) / prev_rate) * 100 if prev_rate else 0
direction = "📈" if variation > 0 else "📉"
@@ -768,11 +749,7 @@ class BotWizard(Wizard):
lms.lot_r_id = ls.getLotQt()[0].id
lms.lot_matched_qt = lp.lot_quantity
lms.lot_sale = s
Lot = Pool().get('lot.lot')
with Lot.skip_quantity_consistency():
LotQt.match_lots([lmp],[lms])
Lot.assert_lines_quantity_consistency(
[p.lines[0], s.lines[0]])
LotQt.match_lots([lmp],[lms])
Sale._process_shipment([s])
elif do_ == "add":
@@ -899,4 +876,4 @@ class BotAction(ModelSQL, ModelView):
@classmethod
def state_dashboard(cls):
return 1
return 1

View File

@@ -65,6 +65,7 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
value = fields.Many2One(
'analytic.dimension.value',
'Value',
required=True,
domain=[
('dimension', '=', Eval('dimension')),
],
@@ -75,4 +76,4 @@ class AnalyticDimensionAssignment(ModelSQL, ModelView):
'purchase.purchase',
'Purchase',
ondelete='CASCADE'
)
)

View File

@@ -0,0 +1,80 @@
# Backlog - Market Price Import
Scope: code changes related only to `BR-PT-004 - Market Price Import`.
## PT-BL-001 - Enforce uniqueness at model level
- Status: to be tested
- Implementation commit: `eaae2e5`
- Priority: high
- Source: [rules/market-price-import.md](../rules/market-price-import.md)
- Requirement: enforce uniqueness of `(price, price_date)` at model level without adding a database constraint yet.
- Work done:
- added model-level validation on `price.price_value`
- added Tryton message `price.msg_price_value_unique`
- added targeted tests for duplicate create and duplicate write validation
- Validation status:
- `git diff --check` passed
- automated Python tests still need to be run in a working Tryton Python environment
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- possibly `modules/price/price_value.py` if the validation belongs on the target model
- Expected tests:
- importing a row for an existing `(price, price_date)` skips or updates according to `Overwrite existing price`
- creating/editing duplicate `price.price_value` records at model level is rejected if validation is implemented on `price.price_value`
## PT-BL-002 - Use locale-aware slash date parsing
- Status: open
- Priority: medium
- Source: [rules/market-price-import.md](../rules/market-price-import.md)
- Requirement: prefer the user's/default locale date format for ambiguous slash dates, then fall back to the current parsing order only if no locale preference is available.
- Current behavior: `DD/MM/YYYY` is tried before `MM/DD/YYYY`.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- slash dates follow the configured/default locale when available
- slash dates keep a deterministic fallback when locale is unavailable
- existing accepted formats still work
## PT-BL-003 - Reject missing price_value
- Status: open
- Priority: high
- Source: [rules/market-price-import.md](../rules/market-price-import.md)
- Requirement: `price_value` is mandatory for imported rows. Missing `price_value` must be reported as a row error.
- Current behavior: empty `price_value` is imported as an empty value.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- missing `price_value` is reported in `errors`
- missing `high_price`, `low_price`, and `open_price` remain allowed
- valid `price_value` still imports and updates correctly
## PT-BL-004 - Detect duplicate rows inside the same Excel file
- Status: open
- Priority: high
- Source: [rules/market-price-import.md](../rules/market-price-import.md)
- Requirement: duplicate rows for the same `(price_index, price_date)` inside the same Excel file must be reported as row errors. The duplicate row must not import or update data.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- Expected tests:
- duplicate `(price_index, price_date)` rows in the same import produce row errors
- first occurrence behavior remains unchanged
- duplicate rows do not overwrite previous imported or updated values
## PT-BL-005 - Categorize import result errors
- Status: open
- Priority: medium
- Source: [rules/market-price-import.md](../rules/market-price-import.md)
- Requirement: distinguish business validation errors from file, parsing, and technical errors in the import result.
- Current behavior: row errors are grouped under one `Errors` section.
- Expected code impact:
- `modules/purchase_trade/pricing.py`
- `modules/purchase_trade/view/import_prices_result_form.xml` only if the result display needs more structure
- Expected tests:
- result message separates business validation errors from parsing/technical errors
- invalid workbook and missing required columns remain blocking `UserError`
- row-level errors still let the import continue

View File

@@ -1,163 +0,0 @@
# Proposition d'architecture des regles business CTRM
Statut: `proposal`
Date: `2026-05-11`
Scope: documentation metier `purchase_trade` / CTRM
## Objectif
Cette note garde la proposition de structure documentaire pour les regles
business trading. L'objectif est de pouvoir s'appuyer sur une source de verite
exhaustive sans charger trop de contexte quand une demande concerne seulement
un theme precis.
## Etat des lieux
- `modules/AGENTS.md` porte les regles courtes communes aux modules.
- `modules/purchase_trade/AGENTS.md` porte les invariants critiques du module
trading et les fichiers pivots.
- `modules/purchase_trade/docs/business-rules.md` est la base metier la plus
riche, mais elle melange regles canoniques, notes de session et quelques
doublons de numerotation.
- `notes/business_rules.md` sert de journal transverse de sessions.
- `notes/accounting/` documente plutot le standard comptable Tryton, les gaps
de reporting et les extensions comptables `purchase_trade`.
- Les fichiers specialises comme `fees.md`,
`padding-invoice-accounting.md`, `template-rules.md` et
`template-properties.md` sont de bons exemples de documentation ciblee.
## Principe recommande
Garder `purchase_trade` comme source de verite canonique pour le metier CTRM,
mais decouper les regles par theme.
`AGENTS.md` doit rester court et servir de routeur:
- charger seulement les invariants toujours utiles;
- indiquer quel fichier thematique lire selon la demande;
- eviter d'embarquer toute la documentation business a chaque intervention.
Les notes de session doivent rester historiques. Une decision devient
canonique seulement quand elle est promue dans un fichier thematique.
## Structure cible proposee
```text
modules/purchase_trade/docs/business/
README.md
INDEX.md
glossary.md
invariants.md
sessions.md
contracts.md
lots-and-quantities.md
matching.md
shipments-execution.md
pricing.md
fees.md
valuation-pnl-mtm.md
invoicing.md
accounting-bridge.md
payments-banking.md
reports-templates.md
risk-credit-forex.md
lots-management.md
```
## Role des fichiers
- `README.md`: explique la convention documentaire et le statut des fichiers.
- `INDEX.md`: routeur par theme, avec les fichiers a lire selon le sujet.
- `glossary.md`: vocabulaire stable du CTRM Tradon.
- `invariants.md`: principes structurants toujours vrais.
- `sessions.md`: journal historique non canonique.
- fichiers thematiques: source canonique exhaustive pour chaque domaine.
## Grands themes metier
- `contracts.md`: achats, ventes, lignes, dates, lieux, banques,
counterparties, quantite contractuelle, statut finished.
- `lots-and-quantities.md`: lots virtuels/physiques, `lot.qt`, historique,
weighing, split/merge, suppression de lot physique, solde ouvert.
- `matching.md`: matching purchase/sale, open matching via `lot.qt`, wizard
`Create contracts`, back-to-back, multi-lots.
- `shipments-execution.md`: shipments, dropship/inbound, controller, SLA,
locations, surveyor, weight reports, BL.
- `pricing.md`: `priced`, `basis`, components, manual pricing, summary,
linked currency, premium, settlement price.
- `fees.md`: fees ligne/shipment, lots effectifs, `% rate`, `ppack`,
maritime freight, insurance, declencheurs PnL.
- `valuation-pnl-mtm.md`: valuation achat/vente, sale-first, references,
MTM strategies, derivatives, exclusions fees.
- `invoicing.md`: provisional/final, facture achat/vente, padding, quantites
facturees, liens lot/facture, templates invoice.
- `accounting-bridge.md`: effets sur `account_invoice`, `account.move`,
taxes, devises, additional moves, validate/post.
- `payments-banking.md`: payment terms, echeances, comptes bancaires
party/company, payment order.
- `reports-templates.md`: Relatorio, proprietes `report_*`, documents trade,
regles XML, cache report.
- `risk-credit-forex.md`: credit risk, forex, FX revaluation, exposition.
- `lots-management.md`: rapports operationnels, filtres matching/side/shipping,
dimension, strategy.
## Format conseille pour une regle
```md
### BR-PT-VAL-007 - Le MTM ne s'applique pas aux fees
Theme: valuation-pnl-mtm
Status: active
Scope: purchase_trade
Code: valuation.py, fee.py
Depends on: BR-PT-FEE-002
Summary: Les lignes de fee ne portent jamais mtm, mtm_price ou strategy.
Rule:
- MTM autorise seulement pour `pur. priced`, `sale priced`, `derivative`.
- Fees: `pur. fee`, `sale fee`, `shipment fee`, `line fee` restent hors MTM.
Tests:
- valuation fee sans mtm
- valuation priced avec mtm
```
Les identifiants devraient encoder le theme (`LOT`, `MAT`, `PRI`, `FEE`,
`VAL`, `INV`, `ACC`, etc.) plutot qu'une numerotation globale unique. Cela
evite les collisions quand les regles sont ajoutees au fil des sessions.
## Routeur de contexte a ajouter plus tard dans `AGENTS.md`
```md
Si la demande concerne:
- lots, quantites, matching: lire `docs/business/lots-and-quantities.md` et
`docs/business/matching.md`
- pricing, premium, basis: lire `docs/business/pricing.md`
- fees, freight, insurance: lire `docs/business/fees.md`
- valuation, PnL, MTM: lire `docs/business/valuation-pnl-mtm.md`
- facture, padding, payment order: lire `docs/business/invoicing.md` et
`docs/business/accounting-bridge.md`
- templates `.fodt`: lire `docs/business/reports-templates.md` et
`docs/template-rules.md`
```
## Migration proposee
1. Creer `docs/business/INDEX.md`, `invariants.md` et `glossary.md`.
2. Decouper `docs/business-rules.md` par theme, sans changer le fond.
3. Deplacer les notes de session dans `sessions.md`.
4. Promouvoir uniquement les decisions durables dans les fichiers thematiques.
5. Reduire `modules/purchase_trade/AGENTS.md` a un memo court + routeur.
6. Garder `notes/accounting/` comme documentation comptable Tryton et comme
vue specialisee des extensions comptables `purchase_trade`.
## Risque actuel a traiter lors de la migration
Le risque principal n'est pas le manque de regles, mais leur dilution entre
plusieurs fichiers. Certaines decisions existent a la fois dans `AGENTS.md`,
`notes/business_rules.md`, `business-rules.md`, `fees.md` et les notes
accounting. La migration devra designer une source canonique par theme et
laisser les autres fichiers jouer leur role d'index, de journal ou de vue
specialisee.

View File

@@ -1,8 +1,8 @@
# Business Rules - Purchase Trade
Statut: `draft`
Version: `v0.8`
Derniere mise a jour: `2026-05-10`
Version: `v0.3`
Derniere mise a jour: `2026-05-07`
Owner metier: `a completer`
Owner technique: `a completer`
@@ -13,914 +13,45 @@ Owner technique: `a completer`
- Modules impactes:
- `purchase_trade`
- `lot`
- `price`
## 2) Glossaire
- `Purchase Line`: ligne d'achat.
- `quantity_theorical`: quantite theorique contractuelle de la ligne.
- `Virtual Lot`: lot unique de type `virtual` rattache a une `purchase.line`.
- `lot.qt`: table des quantites ouvertes, matchées ou shippées par lot.
- `Physical Lot`: lot physique de type `physic` utilise comme pont entre achat, vente et shipment.
- `lot.qt`: table des quantites ouvertes, matchees ou shippees par lot.
- `lot.qt ouvert`: enregistrement `lot.qt` avec `lot_p = virtual lot`, `lot_s = None` et sans shipment.
- `price.price`: index de prix marche.
- `price.price_value`: valeur datee d'un index de prix marche.
## 3) Regles metier
## 3) Catalogue des regles
### BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
| ID | Titre | Domaine | Detail |
| --- | --- | --- | --- |
| BR-PT-001 | Ajustement de la quantite theorique apres creation du contrat | Lot / Purchase | [rules/lot-quantity.md](rules/lot-quantity.md) |
| BR-PT-002 | Le lot physique est le pont metier entre purchase, sale et shipment | Lot / Navigation | [rules/lot-navigation.md](rules/lot-navigation.md) |
| BR-PT-003 | Le freight amount des templates facture vient du fee de shipment | Invoice / Freight | [rules/invoice-freight.md](rules/invoice-freight.md) |
| BR-PT-004 | Market Price Import | Pricing | [rules/market-price-import.md](rules/market-price-import.md) |
- Intent: conserver la coherence entre la quantite theorique de la ligne d'achat, le lot virtuel associe et les quantites ouvertes stockees dans `lot.qt`.
- Description:
- Quand `purchase.line.quantity_theorical` est modifiee apres creation du contrat, le systeme doit recalculer le delta entre l'ancienne et la nouvelle valeur.
- La regle s'applique au lot unique de type `virtual` rattache a la `purchase.line`.
- Conditions d'entree:
- Une `purchase.line` existe deja.
- Son champ `quantity_theorical` est modifie via `write`.
- Un lot `virtual` est rattache a la ligne.
- Resultat attendu:
- Si `delta > 0`:
- augmenter la quantite courante du lot `virtual` via `set_current_quantity` pour conserver l'historique `lot.qt.hist`
- augmenter le `lot.qt` ouvert existant
- si aucun `lot.qt` ouvert n'existe, en creer un nouveau avec le delta
- Si `delta < 0`:
- diminuer le `lot.qt` ouvert uniquement si la quantite ouverte disponible est suffisante
- diminuer la quantite courante du lot `virtual` du meme delta
- si aucun `lot.qt` ouvert n'existe ou si sa quantite est insuffisante, bloquer avec l'erreur `Please unlink or unmatch lot`
- Definition du `lot.qt` ouvert:
- `lot_p = virtual lot`
- `lot_s = None`
- `lot_shipment_in = None`
- `lot_shipment_internal = None`
- `lot_shipment_out = None`
- Exceptions:
- si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien
- Priorite:
- `bloquante`
- Source:
- `Decision metier documentee dans les commentaires de purchase_trade.purchase.Line.write`
## 4) Convention pour les nouvelles regles
### BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
Voir aussi [documentation-management.md](documentation-management.md) pour le workflow complet de gestion documentaire.
- Intent: disposer d'un chemin unique et stable pour retrouver les informations logistiques et de facturation reliees a un contrat d'achat ou de vente.
- Description:
- Le lot physique (`lot_type = physic`) porte simultanement le lien vers:
- la `purchase.line` via `lot.line`
- la `sale.line` via `lot.sale_line`
- le shipment via `lot.lot_shipment_in` / `lot.lot_shipment_internal` / `lot.lot_shipment_out`
- Pour toute logique qui doit naviguer entre achat, vente, shipment et facture, il faut privilegier ce lot physique comme source de verite.
- Resultat attendu:
- depuis une facture d'achat:
- remonter a la `purchase.line`
- puis au lot physique de la ligne
- puis au shipment et aux donnees logistiques associees
- depuis une facture de vente:
- remonter a la `sale.line`
- puis au lot physique matchant qui porte aussi la `purchase.line`
- puis au shipment et aux donnees logistiques associees
- Cas d'usage typiques:
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
- retrouver une facture provisoire liee au lot
- retrouver des fees rattaches au shipment
- Priorite:
- `structurante`
Ajouter une ligne au catalogue puis creer une fiche detaillee dans `docs/rules/`.
### BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
Structure recommandee:
- Intent: afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
- Description:
- Le `FREIGHT VALUE` d'une facture ne doit pas etre pris sur la facture elle-meme.
- Il doit etre calcule a partir du `fee.fee` rattache au shipment (`shipment_in`) du lot physique relie a la facture.
- Regle de navigation:
- retrouver le lot physique pertinent depuis la facture
- retrouver son shipment
- chercher le `fee.fee` avec:
- `shipment_in = shipment.id`
- `product.name = 'Maritime freight'`
- utiliser `fee.get_amount()` comme montant de fret
- Portee:
- s'applique aussi bien aux factures d'achat qu'aux factures de vente
- cote vente, la remontee doit passer par le lot physique qui fait le lien entre `purchase.line` et `sale.line`
- Priorite:
- `importante`
- Intent
- Scope
- Inputs
- Expected Behavior
- Edge Cases
- Impacted Files
- Tests
- Open Questions
### BR-PT-004 - La valuation doit couvrir les flux purchase et sale, y compris les sales non matchees
Les regles courtes peuvent rester dans ce catalogue temporairement, mais toute regle avec impact code, tests, edge cases ou flux transverse doit avoir son fichier dedie.
- Intent: obtenir un PnL coherent cote achat et cote vente, meme lorsqu'une
sale n'est pas encore matchee a une purchase.
- Description:
- Le flux historique de valuation part de `purchase.line` puis remonte vers
les ventes via les lots/lots matchants.
- Le systeme doit egalement savoir valoriser directement une `sale.line`
non matchee ("sale-first").
- Une sale non matchee doit creer des lignes dans
`valuation.valuation` et `valuation.valuation.line` afin d'apparaitre dans
l'onglet PnL de la sale.
- Resultat attendu:
- pour une `sale.line` non matchee, generer au minimum les types:
- `sale priced`
- `sale fee`
- `derivative` si la ligne porte des derives
- si la sale est matchee via un lot physique, les lignes purchase portees par
ce lot physique doivent aussi renseigner `sale` et `sale_line`
- une sale matchee doit donc voir:
- ses lignes `sale *`
- les lignes purchase portees par le lot physique partage
- avant creation du lot physique, si le matching existe seulement via
`lot.qt` (`lot_p` purchase ouvert -> `lot_s` sale ouvert), les lignes PnL
purchase-side doivent aussi renseigner `sale` et `sale_line` afin
d'apparaitre dans l'onglet PnL de la sale matchee
- un lot ouvert / virtuel avec quantite courante a zero ne doit pas generer
de lignes de fees PnL residuelles
- si plusieurs sales differentes sont matchees au meme lot ouvert, ne pas
attacher arbitrairement une sale unique aux lignes purchase-side
- Priorite:
- `structurante`
### BR-PT-005 - Les references de valuation doivent decrire la nature du lot de la ligne
- Intent: eviter les ambiguïtes dans les ecrans PnL entre lots `open` et lots
`physic`.
- Description:
- La reference affichee dans la valuation doit decrire la ligne elle-meme,
pas son vis-a-vis.
- Les references autorisees pour les lignes de prix sont:
- `Purchase/Open`
- `Purchase/Physic`
- `Sale/Open`
- `Sale/Physic`
- Resultat attendu:
- un lot `virtual` cote purchase ne doit jamais sortir avec la reference
`Purchase/Physic`
- un lot `virtual` cote sale ne doit jamais sortir avec la reference
`Sale/Physic`
- un lot physique matche peut produire:
- une ligne purchase en `Purchase/Physic`
- une ligne sale en `Sale/Physic`
- un open sale matche a un open purchase peut produire des quantites egales
tout en gardant des references differentes (`Purchase/Open` vs `Sale/Open`)
- Priorite:
- `importante`
### BR-PT-006 - Une sale basis sans prix detaille doit quand meme apparaitre en valuation
- Intent: ne pas perdre les lignes de PnL lorsque le detail de pricing n'est
pas encore renseigne.
- Description:
- Une `sale.line` de type `basis` peut exister avec un lot `virtual`, sans
`price_summary` et sans `lot_price_sale`.
- Dans ce cas, la valuation doit quand meme creer une ligne `sale priced`.
- Resultat attendu:
- si `price_summary` est vide:
- creer une ligne `sale priced`
- avec `price = 0`
- avec `amount = 0`
- avec un `state` de type `unfixed`
- si `lot_price_sale` est vide sur un lot sale, utiliser `sale_line.unit_price`
comme fallback quand il existe
- Priorite:
- `importante`
### BR-PT-007 - Le MTM de valuation ne s'applique pas aux fees
- Intent: distinguer les lignes de prix marquables au marche des lignes de
frais qui ne doivent pas etre mark-to-market.
- Description:
- Le systeme peut renseigner `mtm_price`, `mtm` et `strategy` uniquement pour:
- `pur. priced`
- `sale priced`
- `derivative`
- Les fees (`pur. fee`, `sale fee`, `shipment fee`, `line fee`) ne doivent
jamais porter de valorisation MTM.
- Resultat attendu:
- les lignes de fee doivent conserver:
- `mtm_price = NULL`
- `mtm = NULL`
- `strategy = NULL`
- `mtm_price` doit representer le prix brut de valorisation sans appliquer le
ratio de composant
- `mtm` reste le montant calcule selon la logique de strategie
- Priorite:
- `structurante`
### BR-PT-007-bis - Mark as finished ignore seulement le reliquat ouvert
- Intent: conserver le PnL reel des lots executes tout en masquant le reliquat
ouvert d'une ligne terminee.
- Description:
- Sur une `purchase.line` ou une `sale.line`, le champ `finished`
(`Mark as finished`) ne signifie pas que la ligne ne doit plus etre
valorisee.
- Il signifie seulement que les lots ouverts / virtuels restants ne doivent
plus alimenter la valuation.
- Dans `Lots Management`, la meme regle s'applique aux lignes `lot.qt`:
une ligne `lot.qt` doit etre masquee si son lot virtuel purchase
(`lot_p`) ou son lot virtuel sale (`lot_s`) est rattache a une ligne
marquee finie.
- Cette exclusion vaut meme si la ligne `lot.qt` est deja matchee ou liee a
un shipment.
- Resultat attendu:
- les lots physiques continuent de produire:
- PnL prix
- PnL fees
- les derivatives continuent de produire du PnL meme si la ligne est
marquee finie
- les lots virtuels / ouverts d'une ligne finie sont ignores
- les lots physiques restent visibles dans `Lots Management`, meme si leur
ligne purchase ou sale est marquee finie
- Priorite:
- `structurante`
### BR-PT-008 - Le premium fait partie du prix contractuel en `priced` et en `basis`
- Intent: garantir que le montant total valorise et facture reflete toujours le
premium/discount saisi sur la ligne.
- Description:
- Le `premium` d'une `purchase.line` ou `sale.line` doit impacter le prix
total quelle que soit la `price_type`.
- Cette regle vaut pour:
- les calculs de `amount`
- la valuation / PnL
- Resultat attendu:
- le `unit_price` reste le prix de base, hors premium
- en `priced`, le montant economique = `unit_price + premium`
- en `basis`, le premium s'ajoute aussi au prix total economique
- en valuation `basis`, le premium s'applique a chaque composant valorise
(ex: meme premium repete sur chaque bloc ICE)
- Exemple metier:
- `8.30 USC/LB 500 TONS ON ICE MCH'26`
- `8.30 USC/LB 500 TONS ON ICE MAY 26`
- le premium `8.30 USC/LB` s'applique a chaque composant
- Priorite:
- `structurante`
### BR-PT-009 - En linked currency, le premium est exprime dans la devise/unite liee
- Intent: respecter la facon dont les traders saisissent les prix sur certains
produits (ex: coton en `USC/LB`).
- Description:
- Quand `enable_linked_currency` est coche, le `premium` est saisi dans la
devise / unite liee, pas dans la devise / unite native de la ligne.
- Le systeme doit convertir ce premium vers le repere de la ligne pour les
calculs internes de montant et de valuation.
- Resultat attendu:
- `premium` est interprete dans le repere `linked_currency` / `linked_unit`
- le `unit_price` ne doit pas absorber ce premium
- les `amount` et valuations doivent refleter ce premium converti
- si `linked currency` est cochee, `linked_price`, `linked_currency` et
`linked_unit` sont obligatoires
- Priorite:
- `structurante`
### BR-PT-010 - En `basis + linked currency`, le linked price suit le basis brut
- Intent: rendre lisible la decomposition entre prix basis de marche et premium.
- Description:
- Quand une ligne est en `basis` et `linked currency`, le bloc
`linked_price` doit etre recalcule automatiquement.
- Ce `linked_price` doit representer le prix basis brut, hors premium.
- Le `unit_price` de la ligne doit rester ce prix brut converti.
- Le premium converti n'est ajoute qu'au niveau du `amount`.
- Resultat attendu:
- modification du basis -> mise a jour automatique du `linked_price`
- `linked_price` = base market / basis
- `unit_price` = `linked_price` converti
- `amount` = quantite * (`unit_price` + premium converti)
- Priorite:
- `importante`
### BR-PT-011 - Une sale line non matchee avec lot virtuel doit generer une valuation sale-first des la validation
- Intent: ne pas attendre un matching purchase pour afficher le PnL d'une sale
ouverte.
- Description:
- Lors de la validation d'une `sale.line`, le systeme peut creer un lot
`virtual`.
- Si aucun `lot.qt` ne relie ce lot a une `purchase.line`, il faut tout de
meme generer la valuation cote sale.
### BR-PT-012 - Le wizard Create contracts peut creer un seul achat matche a plusieurs open sales
- Intent: permettre la creation d'un contrat achat unique a partir de plusieurs
`lot.qt` de vente selectionnes.
- Description:
- En mode `matched`, le wizard `Create contracts` peut recevoir plusieurs
`lot.qt` selectionnes.
- Il doit creer un seul contrat, avec une ligne par lot source selectionne.
- Chaque ligne doit conserver son lot d'origine pour le matching.
- Resultat attendu:
- le wizard agrege les quantites de la selection
- il refuse une quantite saisie differente du total selectionne
- il conserve `created_by_code = True` sur les lignes creees pour ne pas
declencher les creations automatiques parasites lors des validations
- Priorite:
- `importante`
### BR-PT-013 - Le texte par defaut de pricing_rule est configure globalement
- Intent: centraliser un texte metier recurrent reutilise a la creation des
lignes achat et vente.
- Description:
- Le module expose un singleton `purchase_trade.configuration` avec un champ
texte `pricing_rule`.
- Toute nouvelle `purchase.line` et `sale.line` doit prendre ce texte comme
valeur par defaut de `pricing_rule`.
- Resultat attendu:
- la configuration est accessible depuis le menu `Prices`
- la valeur sert de defaut a la creation des lignes
- les lignes existantes ne sont pas modifiees retroactivement
- Priorite:
- `importante`
### BR-PT-014 - L'affectation d'un controller doit suivre l'ecart a l'objectif regional
- Intent: repartir les controllers selon les cibles definies dans l'onglet
`Execution` des `party.party`.
- Description:
- chaque ligne `party.execution` fixe une cible `% targeted` pour un
controller sur une `country.region`
- le `% achieved` est calcule a partir des `stock.shipment.in` deja affectes
a un controller dans cette zone
- la zone d'un shipment est determinee par `shipment.to_location.country`
- une region parente couvre aussi ses sous-regions
- Resultat attendu:
- pour une ligne `party.execution`, `achieved_percent` =
`shipments de la zone avec ce controller / shipments controles de la zone`
- le denominateur ne compte que les `stock.shipment.in` qui ont deja un
`controller`; les shipments encore non affectes ne biaisent donc pas la
statistique affichee
- lors d'un choix automatique de controller, la priorite va a la regle dont
l'ecart `targeted - achieved` est le plus eleve
- un controller a `80%` cible et `40%` reel doit donc passer avant un
controller a `50%` cible et `45%` reel sur la meme zone
- l'appartenance a la zone se lit depuis `shipment.to_location.country`, et
une region parente couvre aussi ses sous-regions
- Priorite:
- `importante`
### BR-PT-014-bis - Les couts SLA controller peuvent cibler pays et/ou lieu
- Intent: permettre de definir le cout d'un controller soit pour un pays, soit
pour une location, soit pour un couple pays + location.
- Description:
- Dans l'onglet `Execution` de `party.party`, les lignes SLA
(`party.execution.place`) peuvent porter:
- `country`
- `location`
- ou les deux.
- Lors de la creation automatique du fee controller sur un
`stock.shipment.in`, le systeme continue de partir de
`shipment.to_location`.
- Le pays utilise pour le matching est `shipment.to_location.country`.
- Priorite de matching:
- couple `country + location`
- puis `location` seule
- puis `country` seul
- Resultat attendu:
- un cout defini uniquement sur un pays s'applique a toutes les destinations
de ce pays.
- un cout defini uniquement sur une location s'applique a cette destination,
quel que soit le pays porte par la location.
- un cout defini sur le couple pays + location est le plus specifique et
prime les deux autres.
- Priorite:
- `importante`
### BR-PT-015 - Les weight reports distants par lot partent du weight report global attache au shipment
- Intent: separer la creation du `weight.report` global et l'export detaille
par lot vers le systeme distant.
- Description:
- l'automation cree le `weight.report` global et l'attache au
`stock.shipment.in`
- l'export FastAPI par lot ne part plus directement de l'automation
- l'utilisateur ouvre le `weight.report` voulu depuis le shipment et lance
l'action d'export depuis ce rapport
- Resultat attendu:
- le rapport choisi sert de base unique pour calculer les payloads par lot
- seuls les lots physiques des `incoming_moves` du shipment sont exportes
- l'action exige au minimum un `controller` et un `returned_id` sur le
shipment
- les cles renvoyees par le systeme distant et la date d'envoi sont
conservees sur le `weight.report` local
- Priorite:
- `importante`
### BR-PT-016 - En pricing manuel, seules la quantite fixee du jour et le prix de marche sont saisis
- Intent: simplifier la saisie utilisateur et garantir une coherence unique
entre les colonnes de `pricing.pricing`.
- Description:
- Pour une ligne de `pricing.pricing` en mode manuel, l'utilisateur ne doit
saisir que:
- `quantity`
- `settl_price`
- Les autres colonnes de suivi sont derivees automatiquement sur tout le
groupe metier (`line + component` ou `sale_line + component`) trie par
`pricing_date`.
- Resultat attendu:
- `fixed_qt` = cumul des `quantity`
- `fixed_qt_price` = moyenne ponderee cumulee des `settl_price`
- `unfixed_qt` = quantite de base de la ligne - `fixed_qt`
- `unfixed_qt_price` = dernier prix disponible de la courbe du composant
quand le composant est lie a une courbe; sinon fallback sur `settl_price`
de la ligne
- `eod_price` = moyenne ponderee entre jambe fixee et non fixee
- `last=True` reste unique par groupe et suit la plus grande `pricing_date`
- Hors scope:
- la generation automatique des lignes quand `pricing.component.auto = True`
ne doit pas changer de comportement
- Priorite:
- `structurante`
### BR-PT-017 - Le workflow Validate des factures client doit aussi attribuer le numero
- Intent: aligner le comportement des factures client et fournisseur au moment
de `Validate`.
- Description:
- Lors du workflow `Validate` sur `account.invoice`, une facture client
(`type = out`) doit maintenant:
- creer son `account.move`
- recevoir son `number`
- La numerotation ne doit plus etre repoussee au `Post` cote client.
- Resultat attendu:
- a l'issue de `Validate`, une facture fournisseur ou client possede deja:
- son `account.move`
- son `number`
- `Post` conserve son role de posting comptable sans reintroduire de
difference de session/fresh login cote client
- Priorite:
- `importante`
- Resultat attendu:
- apres creation du lot virtuel, si aucun matching purchase n'existe:
- appeler `Valuation.generate_from_sale_line(line)`
- creer au moins la ligne `sale priced` fallback si la ligne porte un prix
economique via le premium
- Priorite:
- `importante`
### BR-PT-018 - Les contrats distinguent le compte bancaire tiers du compte bancaire compagnie
- Intent: eviter de confondre le compte bancaire du client/fournisseur avec le
compte bancaire de la compagnie courante utilise pour encaisser ou payer.
- Description:
- Sur `sale.sale` et `purchase.purchase`, `bank_account` represente le compte
bancaire propre a la `party` du contrat.
- Sur `sale.sale` et `purchase.purchase`, `our_bank_account` represente le
compte bancaire utilise par la compagnie courante pour encaisser ou payer.
- `bank_account` est limite aux comptes bancaires de la party du contrat.
- `our_bank_account` reste librement selectionnable parmi les comptes
bancaires disponibles.
- Resultat attendu:
- si plusieurs comptes existent, le compte dont la devise correspond a la
devise du contrat est propose en priorite
- si aucun compte ne matche la devise, le premier compte disponible est
propose
- le champ `Our Bank Account` est pre-rempli depuis les comptes de la
compagnie quand possible, mais sa recherche n'est pas limitee a ces comptes
- Priorite:
- `importante`
### BR-PT-019 - Le padding de facture provisoire vente augmente la quantite facturee sans modifier le lot physique
- Intent: permettre de constituer une provision sur une facture provisoire
vente tout en gardant la trace de l'ecart avec la quantite reelle du lot.
- Description:
- Le wizard `lot.invoice` expose un padding global uniquement pour les
factures provisoires cote vente.
- Ce padding global est reparti egalement entre les lots selectionnes.
- La quantite de chaque ligne de facture provisoire vente est augmentee de la
part de padding du lot.
- Le padding ne modifie pas la quantite physique du lot.
- Resultat attendu:
- deux lots factures ensemble avec un padding global de `1000` recoivent
chacun `500` de padding
- la ligne facture affiche la quantite augmentee
- la ligne facture expose `Inc. padding`
- le lot conserve sa part de padding dans `sale_invoice_padding`
- Validation comptable:
- au `Validate`, le move principal de facture inclut deja le padding car il
est integre a `account.invoice.line.quantity`
- la provisoire cree un `additional_move` avec le couple de comptes configure
`Default Sale Padding` / `Default Accrual Padding`
- montant provisoire:
`lot.sale_invoice_padding * account.invoice.line.unit_price`
- la finale cree l'ecriture inverse pour exactement le montant padding
comptabilise lors de la provisoire
- le montant d'extourne finale doit etre relu depuis
`lot.sale_invoice_line_prov`, afin de reprendre le prix, la devise, la date
et le taux de la provisoire
- le calcul de quantite finale doit retirer le padding de la quantite
provisoire avant de calculer le delta
- voir `modules/purchase_trade/docs/padding-invoice-accounting.md`
- Priorite:
- `importante`
### BR-PT-012 - Fallback valuation basis sans summary: utiliser le prix economique de la ligne
- Intent: eviter qu'une valuation `basis` ouverte sorte a zero alors que la
ligne a bien une valeur economique via le premium.
- Description:
- Une ligne `basis` peut ne pas avoir encore de `price_summary`.
- Dans ce cas, la valuation fallback ne doit pas prendre `unit_price` seul si
celui-ci est brut et hors premium.
- Resultat attendu:
- le fallback valuation `basis` doit utiliser:
- `unit_price + premium converti`
- cette regle vaut au minimum pour:
- `sale.line` non matchee
- `purchase.line` sans summary
- Priorite:
- `importante`
### BR-PT-013 - Create Contracts multi-lots doit conserver un matching par lot source
- Intent: permettre la creation d'un seul contrat mirror a partir de plusieurs
open quantities sans perdre le lien lot-a-lot.
- Description:
- Le wizard `Create contracts` peut etre lance avec plusieurs `lot.qt`
selectionnes.
- En creation `matched`, le systeme doit creer un seul contrat avec une ligne
par lot source selectionne, et chaque ligne doit etre matchee avec son lot
d'origine.
- Resultat attendu:
- la quantite totale du wizard = somme des open quantities selectionnees
- le contrat cree porte plusieurs lignes si plusieurs lots source sont
selectionnes
- chaque ligne creee reutilise le `shipment_origin` et le lot source qui lui
correspondent
- `created_by_code` doit rester positionne sur les lignes creees par wizard
pour eviter la recreation automatique de lots virtuels dans les `validate`
de `purchase.line`, `sale.line` et `lot.lot`
- Priorite:
- `importante`
### BR-PT-014 - Delivery period: From doit rester inferieur ou egal a To
- Intent: eviter les periodes de livraison incoherentes sur les lignes achat et
vente.
- Description:
- Les champs `from_del` et `to_del` sont presents sur `purchase.line` et
`sale.line`.
- Si les deux dates sont renseignees, `from_del` ne doit jamais etre
posterieur a `to_del`.
- Resultat attendu:
- la sauvegarde d'une `purchase.line` ou `sale.line` est bloquee si
`from_del > to_del`
- une date ouverte reste autorisee si seulement une des deux bornes est
renseignee
- Priorite:
- `importante`
### BR-PT-015 - Pricing manuel: composant limite a la ligne courante
- Intent: eviter qu'une ligne de pricing saisie manuellement utilise un
composant rattache a une autre ligne de contrat.
- Description:
- Dans l'onglet `Pricing dates` d'une `purchase.line`, le champ
`pricing.pricing.price_component` doit proposer uniquement les composants
dont `pricing.component.line` est la ligne achat courante.
- Dans l'onglet `Pricing dates` d'une `sale.line`, il doit proposer
uniquement les composants dont `pricing.component.sale_line` est la ligne
vente courante.
- Une ligne de pricing sans composant reste possible pour le mode manuel sans
component.
- Resultat attendu:
- le domaine UI filtre les composants sur la ligne courante
- une validation serveur bloque aussi un composant appartenant a une autre
ligne
- Priorite:
- `importante`
### BR-PT-016 - Les fees `% rate` utilisent le delta de financement
- Intent: aligner le calcul des frais financiers `% rate` entre achat et vente.
- Description:
- Pour un `fee.fee` en mode `rate`, le calcul ne depend pas du
`payment_term`, ni de la date du jour, ni de `fee_date`.
- La periode de calcul est directement le champ `fin_int_delta` de la ligne
`Estimated date` avec `trigger = bldate`.
- Resultat attendu:
- purchase et sale appliquent la meme formule:
`amount = unit_price * quantity * (price / 100) * fin_int_delta / 360`
- le montant affiche sur `fee.fee` doit rester non signe: un
`fin_int_delta` negatif ne doit pas afficher un fee negatif
- le PnL applique seul le signe metier:
- `PAY` => montant negatif
- `REC` => montant positif
- si aucune Estimated Date `bldate` n'est renseignee, aucun montant `% rate`
n'est calcule
- Priorite:
- `importante`
### BR-PT-020 - Le solde `lot.qt` ouvert suit les lots physiques existants
- Intent: eviter qu'une hausse ou baisse de quantite contractuelle double le
reliquat ouvert quand des lots physiques existent deja.
- Description:
- Lorsqu'une `purchase.line.quantity_theorical` ou
`sale.line.quantity_theorical` est modifiee, le `lot.qt` libre ne doit pas
etre ajuste uniquement par delta.
- Le systeme doit recalculer le solde ouvert cible depuis la quantite
contractuelle, les lots physiques deja crees et les quantites deja
allouees dans des `lot.qt` matches ou shippes.
- Resultat attendu:
- quantite virtuelle cible =
`quantity_theorical - somme(lots physiques convertis dans l'unite ligne)`
- `lot.qt` libre non matche / non shippe =
`quantite virtuelle cible - somme(lot.qt deja matches ou shippes)`
- si le resultat devient negatif, bloquer avec
`Please unlink or unmatch lot`
- exemple: une ligne achat passee de `10000` a `20000` avec deja `10000`
physiques doit afficher `10000` ouverts et `10000` physiques, pas
`20000` ouverts plus `10000` physiques.
- Impacts fees:
- apres toute modification de `quantity_theorical`, les fees de la ligne sont
resynchronises avec la regle BR-PT-021.
- si le fee est encore uniquement porte par le lot virtuel, sa quantite suit
donc la nouvelle quantite virtuelle.
- si le fee possede deja des lots physiques, une hausse ou baisse uniquement
ouverte n'impacte pas sa quantite.
- Priorite:
- `structurante`
### BR-PT-021 - Les fees lies aux lots privilegient les physiques
- Intent: eviter qu'un fee cree sur un lot virtuel reste calcule sur la
quantite contractuelle totale apres creation de lots physiques.
- Detail implementation / PnL:
- voir `modules/purchase_trade/docs/fees.md`
- Description:
- Le lien `fee.lots` avec le lot virtuel est conserve comme fallback.
- Des qu'un fee possede au moins un lot physique dans `fee.lots`, les lots
physiques deviennent la base effective du fee et le virtuel est ignore pour
la quantite.
- Resultat attendu:
- si aucun physique n'existe, `fee.quantity` suit le lot virtuel rattache au
fee.
- si des physiques existent, `fee.quantity` suit uniquement la somme des lots
physiques rattaches au fee.
- pour `ppack`, la somme se fait sur `lot.lot_qt` des physiques.
- pour les modes quantitatifs (`perqt`, `rate`, `pprice`, `pcost`), la somme
se fait sur les quantites courantes converties des lots physiques.
- supprimer le dernier physique fait retomber le fee sur son lot virtuel,
puisque le lien virtuel est conserve.
- le PnL fee applique la meme selection: full open tant qu'il n'y a que le
virtuel, puis uniquement les lots physiques effectifs.
- Points de synchronisation obligatoires:
- creation d'un fee lie a une ligne ou a un shipment
- ajout d'un lot physique dans `fee.lots`
- modification de `purchase.line.quantity_theorical` ou
`sale.line.quantity_theorical`
- weighing / modification de quantite d'un lot physique
- suppression d'un lot physique ou suppression d'un lien `fee.lots`
- Regle de conception:
- ne pas supprimer le lien `fee.lots` vers le lot virtuel lors de la creation
de physiques.
- le virtuel reste le fallback permettant de revenir au cas ouvert si tous
les physiques sont supprimes.
- toute logique metier, comptable ou PnL doit utiliser les lots effectifs du
fee: physiques s'il y en a, sinon virtuels.
- Priorite:
- `structurante`
### BR-PT-022 - Remove physical lot restaure le lot.qt ouvert avec son contexte
- Intent: permettre d'annuler un lot physique cree par erreur sans perdre le
contexte metier de shipment ou de matching deja porte par ce lot.
- Description:
- L'action `Remove physical lot` peut supprimer un lot physique meme s'il est
shippe, matche, ou les deux.
- Dans ces cas, l'utilisateur doit recevoir un warning confirmable avant la
suppression.
- La suppression n'est autorisee que si le `stock.move` lie au lot physique
est encore en etat `draft`.
- Si le `stock.move` n'est pas en `draft`, l'action est bloquee car le flux
peut deja avoir des impacts stock/comptables.
- Resultat attendu:
- le `lot.move` et le `stock.move` correspondant sont supprimes avec le lot
physique.
- la quantite courante convertie du lot physique est restauree dans `lot.qt`.
- si le lot etait shippe, la ligne `lot.qt` restauree conserve le shipment
(`lot_shipment_in`, `lot_shipment_internal` ou `lot_shipment_out`).
- si le lot etait matche, la ligne `lot.qt` restauree conserve le lot sale
virtuel (`lot_s`).
- si une ligne `lot.qt` existe deja avec le meme lot purchase virtuel, le
meme shipment et le meme `lot_s`, la quantite est agregee sur cette ligne
plutot que de creer un doublon.
- si aucune ligne compatible n'existe, une nouvelle ligne `lot.qt` est creee.
- Priorite:
- `importante`
### BR-PT-023 - Lots Management separe matching, side et shipping status
- Intent: rendre le rapport `lot.report` exploitable sans melanger le statut de
matching, le sens achat/vente et l'avancement logistique.
- Description:
- Le filtre `Matching status` ne porte que le matching commercial:
`All`, `Matched`, `Not Matched`.
- Le filtre `Side` permet de lire:
`All`, `Purchase`, `Sale`.
- Le filtre `Shipping status` porte uniquement le lien `shipment_in` du
`lot.lot` ou du `lot.qt`.
- Les dates de contexte `As of` et `To` filtrent les contrats via
`purchase.purchase_date` et `sale.sale_date`.
- Si le filtre `Side` vaut `Purchase`, les bornes de dates s'appliquent au
`purchase_date`.
- Si le filtre `Side` vaut `Sale`, les bornes de dates s'appliquent au
`sale_date`.
- Si le filtre `Side` vaut `All`, une ligne est conservee si son
`purchase_date` ou son `sale_date` entre dans les bornes.
- Le filtre `Dimension` du rapport cible une valeur de dimension analytique
rattachee au contrat achat ou vente via `analytic.dimension.assignment`.
- Le filtre `Strategy` cible les strategies MTM rattachees aux lignes achat
ou vente (`purchase.strategy` / `sale.strategy`).
- Resultat attendu:
- `Unshipped`: aucun `shipment_in` lie au `lot.lot` ou au `lot.qt`.
- `Scheduled`: un `shipment_in` est lie et son etat est `draft`.
- `Shipped`: un `shipment_in` est lie et son etat est `started`.
- `Received`: un `shipment_in` est lie et son etat est `received` ou `done`.
- Le rapport affiche une colonne `Shipping status`.
- Le rapport affiche `Purchase Delivery Period` depuis la ligne achat et
`Sale Delivery Period` depuis la ligne vente; l'ancien champ mixte
`Del Period` ne doit plus prendre la periode sale comme fallback achat.
- Shipment type:
- Sur `stock.shipment.in` et dans `lot.report`, `Shipment Type` vaut
`Dropship` si `from_location.type = supplier` et
`to_location.type = customer`.
- Dans tous les autres cas, `Shipment Type` vaut `Inbound`.
- Priorite:
- `importante`
### BR-PT-024 - Create Contracts propage les lieux stock selon le flux miroir
- Intent: eviter une ressaisie des lieux logistiques quand un contrat miroir
est cree depuis une quantite ouverte.
- Description:
- Les champs concernes sont les `stock.location` `from_location` et
`to_location` des contrats achat et vente.
- Si le contrat source est en flux direct fournisseur -> client
(`from_location.type = supplier` et `to_location.type = customer`), le
contrat cree reprend le meme couple `from_location` / `to_location`.
- Ce flux correspond au mode `Dropship` affiche sur les shipments.
- Si un contrat vente est cree depuis un achat dont `to_location.type =
storage`, le `from_location` de la vente est pre-rempli avec ce
`to_location` achat.
- Si un contrat achat est cree depuis une vente dont `from_location.type =
storage`, le `to_location` de l'achat est pre-rempli avec ce
`from_location` vente.
- Resultat attendu:
- achat supplier -> customer vers vente:
`sale.from_location = purchase.from_location` et
`sale.to_location = purchase.to_location`.
- vente supplier -> customer vers achat:
`purchase.from_location = sale.from_location` et
`purchase.to_location = sale.to_location`.
- achat vers stock puis vente:
`sale.from_location = purchase.to_location`.
- vente depuis stock puis achat:
`purchase.to_location = sale.from_location`.
- Priorite:
- `importante`
## 4) Exemples concrets
### Exemple E1 - Augmentation simple
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 120`
- `lot.qt ouvert = 40`
- Attendu:
- lot `virtual` augmente de `20`
- `lot.qt ouvert` passe de `40` a `60`
### Exemple E2 - Augmentation sans lot.qt ouvert
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 110`
- aucun `lot.qt` ouvert
- Attendu:
- lot `virtual` augmente de `10`
- creation d'un `lot.qt` ouvert a `10`
### Exemple E3 - Diminution possible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 90`
- `lot.qt ouvert = 25`
- Attendu:
- lot `virtual` diminue de `10`
- `lot.qt ouvert` passe de `25` a `15`
### Exemple E4 - Diminution impossible
- Donnees:
- `ancienne quantity_theorical = 100`
- `nouvelle quantity_theorical = 80`
- `lot.qt ouvert = 5`
- Attendu:
- blocage avec `Please unlink or unmatch lot`
## 5) Impact code attendu
- Fichiers Python concernes:
- `modules/purchase_trade/purchase.py`
- `modules/purchase_trade/lot.py`
- `modules/purchase_trade/valuation.py`
- `modules/purchase_trade/sale.py`
## 6) Strategie de tests
Pour cette regle, couvrir au minimum:
- augmentation avec `lot.qt` ouvert existant
- augmentation sans `lot.qt` ouvert
- diminution possible
- diminution impossible avec erreur
- valuation purchase/sale sur lot physique matche
- valuation sale-first sur sale non matchee avec lot virtual
- valuation sale `basis` sans `price_summary`
- absence de MTM sur les fees
- premium en `priced`
- premium en `basis`
- premium en `linked currency`
- synchro `basis` -> `linked_price` -> `unit_price`
## 7) Notes de fin de session
### Session 2026-04-30 - PnL fees ouverts et `% rate`
- Les fees PnL ne doivent pas etre generes pour un lot ouvert / virtuel dont
la quantite courante est a zero.
- Cette regle evite les lignes PnL residuelles avec `quantity = 0` mais
`amount != 0`, notamment pour les fees `rate`, `ppack` et `lumpsum`.
- La logique est volontairement proche de `Mark as finished`: quand le reliquat
ouvert/virtuel n'est plus valorisable, ses fees ne le sont pas non plus.
- Les lots physiques restent hors de ce filtre.
- Pour les fees `% rate`, `fin_int_delta` est la periode absolue de calcul.
- Le calcul `% rate` ne depend plus de la date du jour, de `fee_date`, ni de
`BL date + delta` comme date de fin.
- Formule commune purchase/sale:
`amount = unit_price * quantity * (price / 100) * fin_int_delta / 360`.
- La ligne `Estimated date` avec `trigger = bldate` sert a porter
`fin_int_delta`; la date estimee n'entre pas dans le calcul du montant.
- Le montant fee affiche reste toujours non signe, meme si `fin_int_delta` est
negatif; le signe est applique uniquement en PnL via `PAY` / `REC`.
### Session 2026-05-09 - Lots Management et Apply matching
- Le rapport `Lots Management` separe maintenant les filtres:
`Matching status`, `Shipping status`, `Side`, `Dimension` et `Strategy`.
- Les bornes `As of` / `To` filtrent sur `purchase.purchase_date` et
`sale.sale_date` uniquement quand elles sont renseignees; elles ne doivent
pas filtrer par defaut a l'ouverture du report.
- Le filtre `Dimension` cible une valeur dynamique de dimension analytique via
`analytic.dimension.assignment.value`, pour les achats comme pour les ventes.
- Le filtre `Strategy` cible les strategies MTM rattachees aux lignes achat ou
vente (`purchase.strategy` / `sale.strategy`).
- `Apply matching` doit proposer les reliquats ouverts meme si le meme lot
purchase ou sale possede deja une autre ligne `lot.qt` matchee.
- Une ligne est exclue ou bloquee dans `Apply matching` seulement si la ligne
`lot.qt` selectionnee est elle-meme deja matchee:
- cote purchase: `lot_p` renseigne et `lot_s` vide
- cote sale: `lot_s` renseigne et `lot_p` vide
- Exemple: un achat de `1000` avec `800` deja matches et `200` encore ouverts
doit laisser le reliquat `200` selectionnable dans `Apply matching`.
- Dans `Link to transport`, le type de shipment reste impose a
`Shipment In`; l'utilisateur ne doit pas pouvoir basculer vers Out/Internal
depuis cette action.
- `Mark as finished` dans `Lots Management` masque uniquement les reliquats
ouverts / virtuels portes par `lot.qt`.
- Le filtre doit regarder les deux cotes presents sur la ligne `lot.qt`:
`lot_p.line.finished` cote purchase et `lot_s.sale_line.finished` cote sale,
meme quand le filtre `Side` vaut `All`.
- Les lots physiques ne doivent pas etre masques par ce flag; ils restent
visibles pour conserver l'historique execute.
### Session 2026-05-17 - Tolerances, jauges et Go to matching
- `Apply matching` est remplace cote utilisateur par `Go to matching`; l'action
legacy est conservee mais masquee.
- `Go to matching` precharge les lignes `lot.qt` ouvertes selectionnees depuis
`Lots Management`.
- Le matching ouvert peut depasser le solde ouvert strict si la quantite
projetee reste dans la tolerance de la ligne (`Qt max`).
- Les lignes de matching affichent `Qt min`, `Qt max` et une jauge
`Tolerance used`.
- Dans `Go to matching`, la jauge est dynamique: elle projette
`deja matche + Qt to match` contre `quantity_theorical`, avec bornes
`-tol_min` / `tol_max`.
- Les jauges de `purchase.line` et `sale.line` utilisent:
- la somme des lots physiques s'il en existe sur la ligne;
- sinon la somme des `lot.qt` rattaches a la ligne.
- Les jauges header `purchase.purchase` / `sale.sale` representent la moyenne
ponderee des jauges de lignes, au prorata de `quantity_theorical`.
- Cote `sale`, les quantites `lot.qt` sont lues en valeur absolue pour les
jauges de tolerance.
- Le widget SAO `tolerance_gauge` est autorise dans les schemas de vues
`form` et `tree` avec `min`, `max`, `min_field`, `max_field`, `center` et
`digits`.
- En formulaire de ligne (`purchase.line` / `sale.line`), ne pas encapsuler
`tolerance_gauge` et `targeted_qt` dans un sous-groupe `colspan="4"`: cela
decale visuellement le bloc vers la droite dans SAO. Les placer directement
dans la grille principale et forcer `xalign="0"` sur la jauge et sur
`targeted_qt` pour garder un alignement a gauche stable.
Dans les sections `Open Questions`, prefixer chaque question avec `Q:` et chaque reponse documentee avec `A:`.

View File

@@ -1,99 +0,0 @@
<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
# Index thematique des regles business
Statut: `migration partielle`
## Comment chercher une regle
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Contrats, dates, lieux, banques: <a href="contracts.md">contracts.md</a>
</li>
<li style="margin:0.38rem 0;">Lots virtuels, lots physiques, <code>lot.qt</code>, weighing: <a href="lots-and-quantities.md">FR</a> / <a href="lots-and-quantities.en.md">EN</a>
</li>
<li style="margin:0.38rem 0;">Matching, Create Contracts, back-to-back: <a href="matching.md">matching.md</a>
</li>
<li style="margin:0.38rem 0;">Shipments, controllers, SLA, weight reports: <a href="shipments-execution.md">shipments-execution.md</a>
</li>
<li style="margin:0.38rem 0;">Pricing manuel, basis, premium, linked currency: <a href="pricing.md">pricing.md</a>
</li>
<li style="margin:0.38rem 0;">Trading cafe, echantillons, cupping, qualite: <a href="coffee.md">FR</a> / <a href="coffee.en.md">EN</a>
</li>
<li style="margin:0.38rem 0;">Fees, freight, lots effectifs, quantités, <code>% rate</code>: <a href="fees.md">FR</a> / <a href="fees.en.md">EN</a>
</li>
<li style="margin:0.38rem 0;">Valuation, PnL, MTM, derivatives: <a href="valuation-pnl-mtm.md">FR</a> / <a href="valuation-pnl-mtm.en.md">EN</a>
</li>
<li style="margin:0.38rem 0;">Factures provisoires/finales, padding: <a href="invoicing.md">invoicing.md</a>
</li>
<li style="margin:0.38rem 0;">Impacts <code>account.move</code>, validate/post: <a href="accounting-bridge.md">accounting-bridge.md</a>
</li>
<li style="margin:0.38rem 0;">Comptes bancaires, payment terms, payment orders: <a href="payments-banking.md">payments-banking.md</a>
</li>
<li style="margin:0.38rem 0;">Relatorio, <code>.fodt</code>, proprietes <code>report_*</code>: <a href="reports-templates.md">reports-templates.md</a>
</li>
<li style="margin:0.38rem 0;">Risque, credit, forex: <a href="risk-credit-forex.md">risk-credit-forex.md</a>
</li>
<li style="margin:0.38rem 0;">Rapport Lots Management: <a href="lots-management.md">lots-management.md</a>
</li>
<li style="margin:0.38rem 0;">Diagnostics SQL des invariants: <a href="sql/README.md">sql/README.md</a>
</li>
</ul>
## Regles migrees dans cette premiere passe
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>BR-PT-CON-001</code>: texte par defaut de pricing rule.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-CON-002</code>: delivery period coherent.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-CON-003</code>: lieux stock propages dans Create Contracts.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-LOT-001</code>: cycle de vie des lots et des quantites.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-LOT-002</code>: quantity contractuelle, execute physique et ligne finie.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-LOT-003</code>: garde-fous Python et diagnostics SQL des invariants de quantite.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-MAT-001</code>: Create Contracts multi-lots.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-SHP-001</code>: affectation controller.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-SHP-002</code>: couts SLA controller.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-SHP-003</code>: weight reports distants.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-PRI-001</code>: premium dans priced et basis.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-PRI-002</code>: linked currency.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-PRI-003</code>: pricing manuel.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-COF-001</code>: echantillons cafe phase 1.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-FEE-001</code>: maritime freight depuis fee shipment.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-FEE-002</code>: lots effectifs des fees.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-FEE-003</code>: quantité du fee alignée sur les lots effectifs.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-FEE-004</code>: <code>% rate</code> via delta de financement.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-VAL-001</code>: valuation achat/vente et sale-first.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-VAL-002</code>: references de valuation.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-VAL-003</code>: MTM hors fees.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-VAL-004</code>: snapshot courant PnL et identite economique.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-INV-001</code>: padding facture provisoire vente.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-ACC-001</code>: Validate facture client attribue le numero.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-PAY-001</code>: comptes bancaires tiers vs compagnie.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-RPT-001</code>: templates trade via proprietes Python.
</li>
<li style="margin:0.38rem 0;"><code>BR-PT-LOTMGT-001</code>: filtres Lots Management.
</li>
</ul>

View File

@@ -1,137 +0,0 @@
<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
# Guide de lecture des règles business
Statut: `migration partielle`
Dernière mise à jour: `2026-05-13`
Ce dossier devient la source de lecture thématique publiée dans le wiki pour les
règles business du module `purchase_trade`.
Toutes les pages business publiées dans `modules/purchase_trade/docs/business/`
sont générées depuis une source de vérité plus sobre, rangée hors du dossier
wiki dans `modules/purchase_trade/docs_source/business/`.
Les pages publiées portent un commentaire `Generated from ...` en tête de
fichier et ne doivent pas être modifiées directement. Aucun contenu business ne
doit être ajouté ou modifié sans passer par cette source puis par le script de
génération.
Chaque page doit rester lisible par deux publics:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">les consultants, qui ont besoin d&#x27;une règle fonctionnelle stable sans détail de code inutile;
</li>
<li style="margin:0.38rem 0;">les développeurs, qui ont besoin des champs, modèles, fichiers et tests concernés pour appliquer la règle sans l&#x27;interpréter.
</li>
</ul>
## Convention de langues
Chaque page thématique durable doit exister en deux versions maintenues
ensemble:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">une page française, rédigée en français correct avec accents, typographie et formulations naturelles pour le wiki consultant;
</li>
<li style="margin:0.38rem 0;">une page anglaise miroir, portant le même contenu fonctionnel et technique.
</li>
</ul>
Convention de nommage:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">page française principale: <code>theme.md</code>;
</li>
<li style="margin:0.38rem 0;">page anglaise miroir: <code>theme.en.md</code>.
</li>
</ul>
Toute modification d'une règle business, d'un statut, d'un champ technique ou
d'un point de vigilance doit être reportée dans les deux pages au même moment.
Les deux pages doivent indiquer leur page miroir en en-tête.
## Convention source / wiki
Pour toute page business:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">éditer la source de vérité dans <code>modules/purchase_trade/docs_source/business/</code>;
</li>
<li style="margin:0.38rem 0;">régénérer la version wiki avec:
</li>
</ul>
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>python modules/purchase_trade/docs/tools/render_business_docs.py</code></pre>
Le rendu wiki privilégie du HTML simple et portable plutôt que des extensions
MkDocs optionnelles. Cela évite d'exposer dans le wiki des marqueurs non rendus
comme `!!!` ou `:material-...:`.
Les fichiers générés dans `modules/purchase_trade/docs/business/` sont des
artefacts de publication: ils peuvent être relus, mais toute correction doit
être reportée dans `docs_source/business/` avant régénération.
Avant de livrer une modification documentaire, vérifier que les pages publiées
sont à jour avec:
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>python modules/purchase_trade/docs/tools/render_business_docs.py --check</code></pre>
## Convention de rédaction
Pour chaque règle durable, utiliser autant que possible ce format:
<pre style="background:#263238; color:#eef7ff; padding:1rem; border-radius:0.35rem; overflow:auto;"><code>### BR-PT-THEME-001 - Titre court
Statut: active
Source: business-rules.md / note de session / décision projet
#### Règle consultant
Texte fonctionnel, sans nom de champ si ce n&#x27;est pas nécessaire.
#### Notes développeur
- Modèles/champs:
- Fichiers:
- Tests:
- Points de vigilance:</code></pre>
## Convention de validation
Quand une règle business devient structurante pour l'intégrité des données, elle
doit être accompagnée autant que possible de deux garde-fous:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">un check applicatif bloquant dans le code Python, appelé à la fin des flux qui modifient les données concernées;
</li>
<li style="margin:0.38rem 0;">un diagnostic SQL en lecture seule pour auditer les bases existantes ou les bases de test.
</li>
</ul>
Les diagnostics SQL du module sont rangés dans `business/sql/`. Ils ne
remplacent pas les règles applicatives: ils servent à retrouver et qualifier les
écarts déjà présents dans une base.
## Sources pendant la migration
Les anciennes pages ne sont pas supprimées à cette étape. Elles restent des
sources de vérification jusqu'à ce que chaque décision soit promue dans une
page thématique:
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/docs/business-rules.md</code>
</li>
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/docs/fees.md</code>
</li>
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/docs/padding-invoice-accounting.md</code>
</li>
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/docs/template-rules.md</code>
</li>
<li style="margin:0.38rem 0;"><code>modules/purchase_trade/docs/template-properties.md</code>
</li>
<li style="margin:0.38rem 0;"><code>notes/business_rules.md</code>
</li>
<li style="margin:0.38rem 0;"><code>notes/template_business_rules.md</code>
</li>
</ul>

View File

@@ -1,30 +0,0 @@
<!-- Generated from docs_source/business by docs/tools/render_business_docs.py. -->
# Pont comptable
Statut: `migration partielle`
## BR-PT-ACC-001 - Validate facture client attribue aussi le numero
Source: `BR-PT-017`
### Regle consultant
Une facture client doit recevoir son mouvement comptable et son numero des la
validation, comme une facture fournisseur.
### Notes developpeur
<ul style="margin:0.65rem 0 1rem 1.1rem; padding-left:1rem; list-style-type:disc;">
<li style="margin:0.38rem 0;">Cible: <code>account.invoice</code> avec <code>type = out</code>.
</li>
<li style="margin:0.38rem 0;">Workflow <code>Validate</code>: creer <code>account.move</code> et attribuer <code>number</code>.
</li>
<li style="margin:0.38rem 0;">Workflow <code>Post</code>: ne doit pas reintroduire une session fraiche specifique au flux client.
</li>
</ul>
## Notes de migration
Les notes comptables detaillees restent dans `notes/accounting/` tant qu'elles
n'ont pas ete promues ici.

Some files were not shown because too many files have changed in this diff Show More