Compare commits
29 Commits
main
...
9e33a577e8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e33a577e8 | ||
|
|
8cb570126a | ||
|
|
60d53b2d67 | ||
|
|
7f2e61c059 | ||
|
|
eaae2e5b40 | ||
|
|
8fb6d681a0 | ||
|
|
17934fe210 | ||
|
|
ef54b5d94b | ||
|
|
3a0ea871c2 | ||
|
|
1c8abc7c1e | ||
|
|
37bf6ba23b | ||
|
|
2e649aa61b | ||
|
|
735a72d23e | ||
|
|
ebf9b6c495 | ||
|
|
13d26ac41b | ||
|
|
b829b11791 | ||
|
|
4a056ef402 | ||
|
|
da65da79c0 | ||
|
|
aa6b3fb9ad | ||
|
|
b1dd118628 | ||
|
|
f1f9d157cc | ||
|
|
283b71fda9 | ||
|
|
f27dd5620e | ||
|
|
143f59c62e | ||
|
|
be6b6517a5 | ||
|
|
d96973310b | ||
|
|
39278c4483 | ||
|
|
4534ad86f1 | ||
|
|
10e8e5be9b |
17
.claude/settings.local.json
Normal file
17
.claude/settings.local.json
Normal 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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
deployment/vps-TradonDev_Instructions.md
|
||||
deployment/vps/46.202.173.47-credentials.md
|
||||
5
deployment/README.md
Normal file
5
deployment/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Deployment Notes
|
||||
|
||||
- Runbook onboarding SSH: `deployment/runbooks/vps-onboarding.md`
|
||||
- VPS 46.202.173.47 credentials: `deployment/vps/46.202.173.47-credentials.md`
|
||||
- VPS 46.202.173.47 quickstart: `deployment/vps/46.202.173.47-quickstart.md`
|
||||
57
deployment/runbooks/vps-onboarding.md
Normal file
57
deployment/runbooks/vps-onboarding.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Procedure - Ajouter un nouveau VPS (SSH)
|
||||
|
||||
Date de reference: 2026-04-07
|
||||
|
||||
## 1) Preparation locale (Windows)
|
||||
|
||||
1. Creer le dossier SSH local si absent:
|
||||
`New-Item -ItemType Directory -Force $env:USERPROFILE\.ssh`
|
||||
|
||||
2. Generer une cle dediee VPS:
|
||||
`ssh-keygen -t ed25519 -C "vps-deploy" -f $env:USERPROFILE\.ssh\vps_deploy_key`
|
||||
|
||||
3. Lire la cle publique:
|
||||
`Get-Content $env:USERPROFILE\.ssh\vps_deploy_key.pub`
|
||||
|
||||
## 2) Installer la cle sur le VPS
|
||||
|
||||
1. Se connecter au VPS avec mot de passe (premiere fois):
|
||||
`ssh <user>@<ip_vps>`
|
||||
|
||||
2. Sur le VPS, preparer le dossier SSH:
|
||||
`mkdir -p ~/.ssh`
|
||||
`chmod 700 ~/.ssh`
|
||||
|
||||
3. Ajouter la cle publique (une ligne complete):
|
||||
`echo "ssh-ed25519 ... vps-deploy" >> ~/.ssh/authorized_keys`
|
||||
`chmod 600 ~/.ssh/authorized_keys`
|
||||
|
||||
## 3) Tester la connexion par cle
|
||||
|
||||
Depuis Windows:
|
||||
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@<ip_vps>`
|
||||
|
||||
## 4) Test operationnel minimal
|
||||
|
||||
Creer un dossier distant:
|
||||
`ssh -i $env:USERPROFILE\.ssh\vps_deploy_key <user>@<ip_vps> "mkdir -p ~/test_codex_deploy && ls -ld ~/test_codex_deploy"`
|
||||
|
||||
## 5) Durcissement recommande
|
||||
|
||||
- Desactiver l'authentification par mot de passe apres validation de la cle.
|
||||
- Utiliser une cle dediee par environnement (dev/staging/prod).
|
||||
- Documenter user + IP + chemin de cle dans une fiche VPS separee.
|
||||
|
||||
## 6) Note importante - Contrainte sandbox Codex
|
||||
|
||||
- Si une commande SSH/SCP echoue avec un message proche de:
|
||||
- `Identity file ... not accessible: Permission denied`
|
||||
- Cause probable:
|
||||
- la session est en sandbox et ne peut pas lire la cle locale dans
|
||||
`C:\Users\<user>\.ssh\...`.
|
||||
- Action:
|
||||
- relancer la commande en mode `require_escalated` (hors sandbox) pour
|
||||
autoriser l'acces a la cle locale.
|
||||
- Exemple observe le 2026-04-07:
|
||||
- creation du dossier `/root/test` sur `46.202.173.47` reussie uniquement
|
||||
apres escalation.
|
||||
39
deployment/vps/46.202.173.47-quickstart.md
Normal file
39
deployment/vps/46.202.173.47-quickstart.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Commandes Rapides - VPS 46.202.173.47
|
||||
|
||||
Date de reference: 2026-04-07
|
||||
|
||||
## Cles SSH par utilisateur
|
||||
|
||||
| Utilisateur | Fichier cle locale |
|
||||
|---|---|
|
||||
| Laurent Barontini | `$env:USERPROFILE\.ssh\vps_deploy_key` |
|
||||
| Sylvain Duvernay | `$env:USERPROFILE\.ssh\id_ed25519` |
|
||||
|
||||
> Les commandes ci-dessous utilisent `id_ed25519` (Sylvain Duvernay).
|
||||
|
||||
## 1) Test SSH
|
||||
|
||||
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "echo ok"`
|
||||
|
||||
## 2) Creer un dossier test distant
|
||||
|
||||
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "mkdir -p ~/test_codex_deploy && ls -ld ~/test_codex_deploy"`
|
||||
|
||||
## 3) Lister home distant
|
||||
|
||||
`ssh -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47 "ls -la ~"`
|
||||
|
||||
## 4) Copier un fichier local vers le VPS
|
||||
|
||||
`scp -i $env:USERPROFILE\.ssh\id_ed25519 .\local.txt <user>@46.202.173.47:~/local.txt`
|
||||
|
||||
## 5) Recuperer un fichier du VPS
|
||||
|
||||
`scp -i $env:USERPROFILE\.ssh\id_ed25519 <user>@46.202.173.47:~/remote.txt .\remote.txt`
|
||||
|
||||
## 6) Depannage sandbox (Codex)
|
||||
|
||||
- Symptome:
|
||||
- `Identity file ... not accessible: Permission denied`
|
||||
- Correctif:
|
||||
- relancer la commande SSH/SCP en mode escalade (`require_escalated`).
|
||||
3
deployment/vps/vps-TradonDev.md
Normal file
3
deployment/vps/vps-TradonDev.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Serveur: 'VPS-62.72.36.116'
|
||||
Alias Name: 'VPS DEV'
|
||||
IP Address:'62.72.36.116'
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -38,12 +38,34 @@ class PriceValue(
|
||||
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
|
||||
high_price = fields.Float("High price")
|
||||
|
||||
@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):
|
||||
|
||||
@@ -55,15 +55,18 @@ def register():
|
||||
lc.LCMT700,
|
||||
lc.LCMessage,
|
||||
lc.CreateLCStart,
|
||||
global_reporting.GRConfiguration,
|
||||
module='purchase_trade', type_='model')
|
||||
global_reporting.GRConfiguration,
|
||||
pricing.ImportPricesStart,
|
||||
pricing.ImportPricesResult,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
incoming.ImportSwift,
|
||||
incoming.PrepareDocuments,
|
||||
incoming.AnalyzeConditions,
|
||||
lc.CreateLCWizard,
|
||||
module='purchase_trade', type_='wizard'
|
||||
)
|
||||
incoming.ImportSwift,
|
||||
incoming.PrepareDocuments,
|
||||
incoming.AnalyzeConditions,
|
||||
lc.CreateLCWizard,
|
||||
pricing.ImportPrices,
|
||||
module='purchase_trade', type_='wizard'
|
||||
)
|
||||
Pool.register(
|
||||
credit_risk.Party,
|
||||
credit_risk.CreditRiskRule,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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: done
|
||||
- Implementation commit: `eaae2e5`
|
||||
- Priority: high
|
||||
- Source: [business rules/market-price-import.md](../business%20rules/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: [business rules/market-price-import.md](../business%20rules/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: [business rules/market-price-import.md](../business%20rules/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: [business rules/market-price-import.md](../business%20rules/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: [business rules/market-price-import.md](../business%20rules/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
|
||||
|
||||
## PT-BL-006 - Fix missing default currency on created price index
|
||||
|
||||
- Status: open
|
||||
- Priority: high
|
||||
- Source bug: [BUG-PT-001](../bugs.md#bug-pt-001---missing-default-currency-in-price-index-automatic-creation)
|
||||
- Source rule: [business rules/market-price-import.md](../business%20rules/market-price-import.md)
|
||||
- Requirement: when the market price import automatically creates a missing `price.price`, the created price index must have default currency `USD`.
|
||||
- Current behavior: the created price index can miss the default currency even though the import specification expects `USD`.
|
||||
- Expected code impact:
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- Expected tests:
|
||||
- automatic price index creation assigns `price_currency` to the `USD` currency record
|
||||
- if the `USD` currency record cannot be found, behavior remains aligned with the rule for optional default references
|
||||
63
modules/purchase_trade/docs/bugs.md
Normal file
63
modules/purchase_trade/docs/bugs.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Bugs - Purchase Trade
|
||||
|
||||
This file is the bug register for `purchase_trade`.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Register every new bug in this file with a stable bug ID.
|
||||
2. Create a matching backlog entry for every new bug.
|
||||
3. Link the bug entry to the backlog entry.
|
||||
4. When fixed, record the implementation commit in the backlog entry.
|
||||
5. Keep the bug entry as history and update its status.
|
||||
|
||||
## Status Values
|
||||
|
||||
- `reported`
|
||||
- `confirmed`
|
||||
- `backlog-created`
|
||||
- `in-progress`
|
||||
- `to-be-tested`
|
||||
- `done`
|
||||
- `rejected`
|
||||
|
||||
## Bug Template
|
||||
|
||||
```md
|
||||
### BUG-PT-001 - Short title
|
||||
|
||||
- Status: `reported`
|
||||
- Area:
|
||||
- Reported date: `YYYY-MM-DD`
|
||||
- Reported by:
|
||||
- Related rule:
|
||||
- Backlog entry:
|
||||
- Severity:
|
||||
- Environment:
|
||||
- Description:
|
||||
- Steps to reproduce:
|
||||
- Expected behavior:
|
||||
- Actual behavior:
|
||||
- Evidence:
|
||||
- Notes:
|
||||
```
|
||||
|
||||
## Bugs
|
||||
|
||||
### BUG-PT-001 - Missing default currency in Price Index automatic creation
|
||||
|
||||
- Status: `backlog-created`
|
||||
- Area: Market Price Import
|
||||
- Reported date: `2026-05-08`
|
||||
- Reported by: user
|
||||
- Related rule: [BR-PT-004 - Market Price Import](business%20rules/market-price-import.md)
|
||||
- Backlog entry: [PT-BL-006](backlog/market-price-import-backlog.md#pt-bl-006---fix-missing-default-currency-on-created-price-index)
|
||||
- Severity:
|
||||
- Environment:
|
||||
- Description: during market price import, if a price index should be created, then the default currency defined to USD is missing.
|
||||
- Steps to reproduce:
|
||||
- import market prices with `Create price index if missing` enabled
|
||||
- include a `price_index` that does not already exist
|
||||
- Expected behavior: the automatically created `price.price` has default currency `USD`.
|
||||
- Actual behavior: the automatically created `price.price` has no default currency.
|
||||
- Evidence:
|
||||
- Notes:
|
||||
@@ -0,0 +1,50 @@
|
||||
# BR-PT-003 - Le freight amount des templates facture vient du fee de shipment
|
||||
|
||||
## Intent
|
||||
|
||||
Afficher dans les documents facture la vraie valeur de fret maritime rattachee au shipment du lot physique.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: templates facture achat et vente
|
||||
- Priorite: `importante`
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
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
|
||||
|
||||
La regle 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`.
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/invoice.py`
|
||||
- `modules/purchase_trade/fee.py`
|
||||
- `modules/purchase_trade/lot.py`
|
||||
- Templates facture `.fodt` concernes par `FREIGHT VALUE`
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir les changements au plus proche du flux modifie:
|
||||
|
||||
- facture achat avec lot physique, shipment et fee `Maritime freight`
|
||||
- facture vente qui remonte au lot physique puis au shipment
|
||||
- cas sans fee maritime
|
||||
- cas sans lot physique pertinent
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Preciser les templates exacts quand une demande cible un document facture particulier.
|
||||
59
modules/purchase_trade/docs/business rules/lot-navigation.md
Normal file
59
modules/purchase_trade/docs/business rules/lot-navigation.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# BR-PT-002 - Le lot physique est le pont metier entre purchase, sale et shipment
|
||||
|
||||
## 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.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: navigation metier entre achat, vente, shipment et facture
|
||||
- Priorite: `structurante`
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
## Typical Use Cases
|
||||
|
||||
- recuperer `bl_date`, `bl_number`, `controller`, `from_location`, `to_location`
|
||||
- retrouver une facture provisoire liee au lot
|
||||
- retrouver des fees rattaches au shipment
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/lot.py`
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- `modules/purchase_trade/sale.py`
|
||||
- `modules/purchase_trade/invoice.py`
|
||||
- Templates facture relies aux donnees logistiques, si le flux documentaire est concerne.
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir les changements au plus proche du flux modifie:
|
||||
|
||||
- navigation achat vers lot physique puis shipment
|
||||
- navigation vente vers lot physique puis shipment
|
||||
- cas sans lot physique pertinent
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Documenter au cas par cas les champs exposes au reporting facture quand ils dependent de cette navigation.
|
||||
106
modules/purchase_trade/docs/business rules/lot-quantity.md
Normal file
106
modules/purchase_trade/docs/business rules/lot-quantity.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# BR-PT-001 - Ajustement de la quantite theorique apres creation du contrat
|
||||
|
||||
## 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`.
|
||||
|
||||
## Scope
|
||||
|
||||
- Domaine: `purchase_trade`
|
||||
- Flux: modification de `purchase.line.quantity_theorical` apres creation du contrat
|
||||
- Priorite: `bloquante`
|
||||
|
||||
## Inputs
|
||||
|
||||
- Une `purchase.line` existe deja.
|
||||
- Son champ `quantity_theorical` est modifie via `write`.
|
||||
- Un lot unique de type `virtual` est rattache a la ligne.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
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`.
|
||||
|
||||
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`
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Si aucun lot `virtual` n'est trouve sur la ligne, la regle ne fait rien.
|
||||
|
||||
## Examples
|
||||
|
||||
### 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`
|
||||
|
||||
## Impacted Files
|
||||
|
||||
- `modules/purchase_trade/purchase.py`
|
||||
- `modules/purchase_trade/lot.py`
|
||||
|
||||
## Tests
|
||||
|
||||
Couvrir au minimum:
|
||||
|
||||
- augmentation avec `lot.qt` ouvert existant
|
||||
- augmentation sans `lot.qt` ouvert
|
||||
- diminution possible
|
||||
- diminution impossible avec erreur
|
||||
|
||||
## Source
|
||||
|
||||
- Decision metier documentee dans les commentaires de `purchase_trade.purchase.Line.write`.
|
||||
@@ -0,0 +1,171 @@
|
||||
# BR-PT-004 - Market Price Import
|
||||
|
||||
## Intent
|
||||
|
||||
Import dated market prices from an `.xlsx` Excel file into `price.price_value`, using `price.price` as the market price index.
|
||||
|
||||
## Scope
|
||||
|
||||
- Wizard: `purchase_trade.import_prices`
|
||||
- Input model: `purchase_trade.import_prices.start`
|
||||
- Result model: `purchase_trade.import_prices.result`
|
||||
- Target models:
|
||||
- `price.price`
|
||||
- `price.price_value`
|
||||
- Menu entry: under `price.menu_price`
|
||||
|
||||
## Inputs
|
||||
|
||||
The wizard reads only the first worksheet of an `.xlsx` file.
|
||||
|
||||
The first row is treated as the header row. Header names are normalized by lowercasing and removing non-alphanumeric characters, so labels such as `price_index`, `price index`, and `Price Index` map to the same field.
|
||||
|
||||
Required columns:
|
||||
|
||||
- `price_index`
|
||||
- `price_date`
|
||||
- `high_price`
|
||||
- `low_price`
|
||||
- `open_price`
|
||||
- `price_value`
|
||||
|
||||
The wizard options are:
|
||||
|
||||
- `Create price index if missing`
|
||||
- `Overwrite existing price`
|
||||
|
||||
## Date and Numeric Parsing
|
||||
|
||||
Accepted `price_date` values:
|
||||
|
||||
- Excel serial date numbers
|
||||
- `YYYY-MM-DD`
|
||||
- `DD/MM/YYYY`
|
||||
- `MM/DD/YYYY`
|
||||
|
||||
Numeric price fields accept decimal commas or decimal points. Empty numeric cells are imported as empty values. Invalid numeric values are reported as row errors.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
For each non-empty data row, starting from Excel row 2:
|
||||
|
||||
1. Trim and validate `price_index`.
|
||||
2. Parse `price_date`.
|
||||
3. Search `price.price` by exact `price_index`.
|
||||
4. If the price index is missing:
|
||||
- create it when `Create price index if missing` is checked
|
||||
- otherwise skip the row with `price_index missing`
|
||||
5. Search `price.price_value` by `(price, price_date)`.
|
||||
6. If an existing price value is found:
|
||||
- update it when `Overwrite existing price` is checked
|
||||
- otherwise skip the row with `price_date already exists`
|
||||
7. If no existing price value is found, create a new `price.price_value`.
|
||||
|
||||
## Created Price Index Defaults
|
||||
|
||||
When the wizard creates a missing `price.price`, it sets:
|
||||
|
||||
- `price_index = imported price_index`
|
||||
- `price_desc = imported price_index`
|
||||
- `price_curve_type = future`
|
||||
|
||||
It also tries to default these references when matching records exist:
|
||||
|
||||
- `price_type`: `price.fixtype` where `name = Market price`
|
||||
- `price_currency`: `currency.currency` where `code = USD`
|
||||
- `price_calendar`: `price.calendar` where `name = Argus EU`
|
||||
- `price_unit`: `product.uom` where `name = Mt`
|
||||
|
||||
If the `price_index` contains a `YYYY-MM` style period, the wizard derives a `product.month`:
|
||||
|
||||
- pattern accepted in the name: `YYYY-MM`, `YYYY/MM`, `YYYY_MM`, `YYYY.MM`, or `YYYY MM`
|
||||
- month name format: `MONYY`, for example `JUL26`
|
||||
- if no matching `product.month` exists, it is created with `is_cotation = True`
|
||||
|
||||
## Result Reporting
|
||||
|
||||
The result screen always shows counts and detail sections for:
|
||||
|
||||
- created price indexes
|
||||
- imported prices
|
||||
- updated existing prices
|
||||
- skipped records
|
||||
- errors
|
||||
|
||||
Row-level errors do not stop the whole import; the wizard records the error and continues with the next row.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- Missing `price_index`: skipped.
|
||||
- Missing `price_date`: skipped.
|
||||
- Invalid `.xlsx` file: blocking `UserError`.
|
||||
- Missing required columns: blocking `UserError`.
|
||||
- Invalid date: row error.
|
||||
- Invalid numeric value: row error.
|
||||
- Existing `(price, price_date)` without overwrite option: skipped.
|
||||
- Existing `(price, price_date)` with overwrite option: updated.
|
||||
- Empty rows are ignored.
|
||||
|
||||
## Impacted Files
|
||||
|
||||
Direct `purchase_trade` files:
|
||||
|
||||
- `modules/purchase_trade/pricing.py`
|
||||
- `modules/purchase_trade/pricing.xml`
|
||||
- `modules/purchase_trade/view/import_prices_start_form.xml`
|
||||
- `modules/purchase_trade/view/import_prices_result_form.xml`
|
||||
- `modules/purchase_trade/__init__.py`
|
||||
- `modules/purchase_trade/tryton.cfg`
|
||||
- `modules/purchase_trade/tests/test_module.py`
|
||||
|
||||
External model dependencies:
|
||||
|
||||
- `modules/price/price.py`
|
||||
- `modules/price/price_value.py`
|
||||
- `modules/price/view/price_value_form.xml`
|
||||
|
||||
## Tests
|
||||
|
||||
Existing focused tests cover:
|
||||
|
||||
- missing price index skipped when creation is disabled
|
||||
- missing price index created when creation is enabled
|
||||
- default fields on newly created price indexes
|
||||
- period reuse/creation from `price_index`
|
||||
- existing `price_date` skipped when overwrite is disabled
|
||||
- existing `price_date` updated when overwrite is enabled
|
||||
- invalid row values collected as errors
|
||||
- result screen formatting
|
||||
|
||||
Recommended additional tests:
|
||||
|
||||
- `.xlsx` header normalization
|
||||
- missing required columns
|
||||
- Excel serial date parsing
|
||||
- empty row ignored
|
||||
- invalid workbook raises `UserError`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Q: Should `(price, price_date)` be enforced unique at model/database level?
|
||||
- A: Enforce uniqueness at model level for now. Do not add a database constraint yet.
|
||||
|
||||
- Q: Should created price index defaults remain hardcoded to `Market price`, `USD`, `Argus EU`, and `Mt`?
|
||||
- A: Yes. Keep these defaults hardcoded for this import.
|
||||
|
||||
- Q: Should ambiguous slash dates prefer `DD/MM/YYYY` over `MM/DD/YYYY`, as currently implemented?
|
||||
- A: Prefer the user's/default locale date format when possible. Fall back to the current order only if no locale preference is available.
|
||||
- Comment: current code tries `DD/MM/YYYY` before `MM/DD/YYYY` and does not inspect locale. Implementing this answer requires a code change.
|
||||
|
||||
- Q: Should missing `price_value` be allowed, or should it skip/error while high/low/open remain optional?
|
||||
- A: Missing `price_value` is not allowed. Report the row as an error. `high_price`, `low_price`, and `open_price` remain optional.
|
||||
- Comment: current code allows empty `price_value` and imports it as an empty value. Implementing this answer requires a code change.
|
||||
|
||||
- Q: Should duplicate rows for the same `(price_index, price_date)` inside the same Excel file be treated as an error, skipped after the first row, or resolved by the overwrite option?
|
||||
- A: Report duplicate rows as row errors and do not import or update the duplicate row.
|
||||
|
||||
- Q: When `Create price index if missing` is enabled, should missing default reference records (`Market price`, `USD`, `Argus EU`, `Mt`) block index creation or remain optional as currently implemented?
|
||||
- A: Remain optional. Create the price index with the reference records that can be found.
|
||||
|
||||
- Q: Should the import result distinguish business validation errors from technical parsing errors?
|
||||
- A: Yes. Distinguish business validation errors from file, parsing, and technical errors in the import result.
|
||||
@@ -1,10 +1,10 @@
|
||||
# Business Rules - Purchase Trade
|
||||
|
||||
Statut: `draft`
|
||||
Version: `v0.2`
|
||||
Derniere mise a jour: `2026-03-27`
|
||||
Owner metier: `a completer`
|
||||
Owner technique: `a completer`
|
||||
Version: `v0.3`
|
||||
Derniere mise a jour: `2026-05-07`
|
||||
Owner metier: `Open Squared`
|
||||
Owner technique: `Open Squared`
|
||||
|
||||
## 1) Scope
|
||||
|
||||
@@ -13,145 +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 | [business rules/lot-quantity.md](business%20rules/lot-quantity.md) |
|
||||
| BR-PT-002 | Le lot physique est le pont metier entre purchase, sale et shipment | Lot / Navigation | [business rules/lot-navigation.md](business%20rules/lot-navigation.md) |
|
||||
| BR-PT-003 | Le freight amount des templates facture vient du fee de shipment | Invoice / Freight | [business rules/invoice-freight.md](business%20rules/invoice-freight.md) |
|
||||
| BR-PT-004 | Market Price Import | Pricing | [business rules/market-price-import.md](business%20rules/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/business 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
|
||||
|
||||
## 4) Exemples concrets
|
||||
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.
|
||||
|
||||
### 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`
|
||||
|
||||
## 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
|
||||
Dans les sections `Open Questions`, prefixer chaque question avec `Q:` et chaque reponse documentee avec `A:`.
|
||||
|
||||
111
modules/purchase_trade/docs/documentation-management.md
Normal file
111
modules/purchase_trade/docs/documentation-management.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Documentation Management - Purchase Trade
|
||||
|
||||
This guide records the documentation workflow to apply for future `purchase_trade` developments.
|
||||
|
||||
## Rule Documentation Structure
|
||||
|
||||
- Keep `business-rules.md` as the entry point and rule catalog.
|
||||
- Store detailed business rules in `docs/business rules/`, one file per substantial rule.
|
||||
- Use stable business rule IDs such as `BR-PT-004`.
|
||||
- Add every new detailed rule to the catalog in `business-rules.md`.
|
||||
- Keep rule files focused on business behavior, expected outcomes, edge cases, impacted files, tests, and open questions.
|
||||
|
||||
## Rule File Template
|
||||
|
||||
Use this structure for detailed rule files when applicable:
|
||||
|
||||
- Intent
|
||||
- Scope
|
||||
- Inputs
|
||||
- Date and Numeric Parsing, when relevant
|
||||
- Expected Behavior
|
||||
- Defaults or Derived Values, when relevant
|
||||
- Result Reporting, when relevant
|
||||
- Edge Cases
|
||||
- Impacted Files
|
||||
- Tests
|
||||
- Open Questions
|
||||
|
||||
## Open Questions
|
||||
|
||||
- In `Open Questions`, prefix every question with `Q:`.
|
||||
- Prefix every documented answer with `A:`.
|
||||
- When an answer differs from current code behavior, add a short `Comment:` explaining that a code change is required.
|
||||
- Once all questions have answers, keep the section as decision history unless the rule becomes noisy.
|
||||
|
||||
Example:
|
||||
|
||||
```md
|
||||
- Q: Should missing `price_value` be allowed?
|
||||
- A: Missing `price_value` is not allowed. Report the row as an error.
|
||||
- Comment: current code allows empty `price_value`. Implementing this answer requires a code change.
|
||||
```
|
||||
|
||||
## Backlog From Documentation
|
||||
|
||||
- When answered questions imply code changes, create a dedicated backlog file for the feature or rule.
|
||||
- Store all backlog files in `docs/backlog/`.
|
||||
- Name backlog files by feature, for example `docs/backlog/market-price-import-backlog.md`.
|
||||
- Do not use a generic module backlog when the changes belong to one feature.
|
||||
- After coding a backlog item, update that item with the work done and mark its `Status` as `to be tested` until the targeted validation has run successfully in the proper environment.
|
||||
- After committing implemented backlog work, record the implementation commit hash in the relevant backlog item for future reference.
|
||||
- Each backlog item should include:
|
||||
- stable ID
|
||||
- status
|
||||
- implementation commit, once committed
|
||||
- priority
|
||||
- source rule file
|
||||
- requirement
|
||||
- work done, once implemented
|
||||
- validation status, once attempted
|
||||
- current behavior, when useful
|
||||
- expected code impact
|
||||
- expected tests
|
||||
|
||||
Backlog status values:
|
||||
|
||||
- `open`: identified, not started yet
|
||||
- `in-progress`: currently being worked on
|
||||
- `blocked`: cannot progress until a dependency or question is resolved
|
||||
- `implemented`: code or documentation change made, not yet validated
|
||||
- `to-be-tested`: implemented and waiting for targeted validation in the right environment
|
||||
- `tested`: targeted validation passed
|
||||
- `done`: tested, committed, and no further action expected
|
||||
- `rejected`: intentionally not implemented
|
||||
|
||||
Default workflow:
|
||||
|
||||
```text
|
||||
open -> in-progress -> to-be-tested -> tested -> done
|
||||
```
|
||||
|
||||
Use `implemented` only when the change is made but no validation attempt has happened yet. Use `to-be-tested` when validation must happen in another environment or could not be completed locally.
|
||||
|
||||
## Bug Management
|
||||
|
||||
- Register every new bug in `docs/bugs.md`.
|
||||
- Use stable bug IDs such as `BUG-PT-001`.
|
||||
- Every new bug must have a matching backlog entry.
|
||||
- Link the bug entry to its backlog entry.
|
||||
- If the bug belongs to an existing feature backlog, add it there.
|
||||
- If the bug does not clearly belong to an existing feature backlog, create a dedicated backlog file under `docs/backlog/`.
|
||||
- When the bug is fixed, record the implementation commit in the backlog entry and update the bug status.
|
||||
|
||||
## Review Workflow
|
||||
|
||||
For future development work:
|
||||
|
||||
1. Read the relevant rule file before changing code.
|
||||
2. Review the current code and tests related to the rule.
|
||||
3. Update the rule documentation if code review reveals missing behavior, edge cases, defaults, or test gaps.
|
||||
4. Record unresolved decisions as `Q:` entries.
|
||||
5. Record accepted decisions as `A:` entries.
|
||||
6. Convert accepted decisions that require code changes into backlog items.
|
||||
7. Implement code only after the requested documentation/specification step is complete, or after explicit approval when the user asks to wait.
|
||||
|
||||
## Scope Discipline
|
||||
|
||||
- Keep documentation changes close to the affected module and feature.
|
||||
- Do not mix unrelated features in one rule file or one backlog file.
|
||||
- Link or list impacted files so future agents can work in a narrow code scope.
|
||||
- Keep tests listed near the rule they validate.
|
||||
@@ -1,9 +1,10 @@
|
||||
# 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.model import fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval, Id
|
||||
from trytond.model import (ModelSQL, ModelView)
|
||||
from trytond.model import fields
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Bool, Eval, Id
|
||||
from trytond.model import (ModelSQL, ModelView)
|
||||
from trytond.tools import is_full_text, lstrip_wildcard
|
||||
from trytond.transaction import Transaction, inactive_records
|
||||
from decimal import getcontext, Decimal, ROUND_HALF_UP
|
||||
@@ -11,12 +12,17 @@ from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
|
||||
from sql.conditionals import Case
|
||||
from sql import Column, Literal
|
||||
from sql.functions import CurrentTimestamp, DateTrunc
|
||||
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
||||
from itertools import chain, groupby
|
||||
from operator import itemgetter
|
||||
import datetime
|
||||
import logging
|
||||
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
|
||||
from trytond.wizard import Button, StateTransition, StateView, Wizard
|
||||
from itertools import chain, groupby
|
||||
from operator import itemgetter
|
||||
import calendar
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from xml.etree import ElementTree
|
||||
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -50,13 +56,423 @@ DAYS = [
|
||||
('sunday', 'Sunday'),
|
||||
]
|
||||
|
||||
class Estimated(ModelSQL, ModelView):
|
||||
"Estimated date"
|
||||
__name__ = 'pricing.estimated'
|
||||
class Estimated(ModelSQL, ModelView):
|
||||
"Estimated date"
|
||||
__name__ = 'pricing.estimated'
|
||||
|
||||
trigger = fields.Selection(TRIGGERS,"Trigger")
|
||||
estimated_date = fields.Date("Estimated date")
|
||||
fin_int_delta = fields.Integer("Financing interests delta")
|
||||
estimated_date = fields.Date("Estimated date")
|
||||
fin_int_delta = fields.Integer("Financing interests delta")
|
||||
|
||||
|
||||
class ImportPricesStart(ModelView):
|
||||
"Import Prices"
|
||||
__name__ = 'purchase_trade.import_prices.start'
|
||||
|
||||
file_ = fields.Binary('Excel file', required=True, filename='filename')
|
||||
filename = fields.Char('Filename')
|
||||
create_missing_price_index = fields.Boolean(
|
||||
"Create price index if missing")
|
||||
overwrite_existing_price = fields.Boolean("Overwrite existing price")
|
||||
|
||||
|
||||
class ImportPricesResult(ModelView):
|
||||
"Import Prices Result"
|
||||
__name__ = 'purchase_trade.import_prices.result'
|
||||
|
||||
message = fields.Text("Results", readonly=True)
|
||||
|
||||
|
||||
class ImportPrices(Wizard):
|
||||
"Import Prices"
|
||||
__name__ = 'purchase_trade.import_prices'
|
||||
|
||||
start = StateView(
|
||||
'purchase_trade.import_prices.start',
|
||||
'purchase_trade.import_prices_start_view_form',
|
||||
[
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
Button('Import', 'import_', 'tryton-ok', default=True),
|
||||
])
|
||||
import_ = StateTransition()
|
||||
result = StateView(
|
||||
'purchase_trade.import_prices.result',
|
||||
'purchase_trade.import_prices_result_view_form',
|
||||
[
|
||||
Button('OK', 'end', 'tryton-ok', default=True),
|
||||
])
|
||||
|
||||
REQUIRED_COLUMNS = {
|
||||
'priceindex': 'price_index',
|
||||
'pricedate': 'price_date',
|
||||
'highprice': 'high_price',
|
||||
'lowprice': 'low_price',
|
||||
'openprice': 'open_price',
|
||||
'pricevalue': 'price_value',
|
||||
}
|
||||
|
||||
def transition_import_(self):
|
||||
rows = self._read_xlsx(self.start.file_)
|
||||
stats = self._import_rows(
|
||||
rows,
|
||||
create_missing_price_index=(
|
||||
self.start.create_missing_price_index),
|
||||
overwrite_existing_price=self.start.overwrite_existing_price)
|
||||
self._result_message = self._format_result(stats)
|
||||
return 'result'
|
||||
|
||||
def default_result(self, fields):
|
||||
return {
|
||||
'message': getattr(
|
||||
self, '_result_message',
|
||||
'No import result was produced.'),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _import_rows(
|
||||
cls, rows, create_missing_price_index=False,
|
||||
overwrite_existing_price=False):
|
||||
Price = Pool().get('price.price')
|
||||
PriceValue = Pool().get('price.price_value')
|
||||
stats = {
|
||||
'created_indexes': [],
|
||||
'imported': [],
|
||||
'updated': [],
|
||||
'skipped': [],
|
||||
'errors': [],
|
||||
}
|
||||
|
||||
for row_number, row in enumerate(rows, start=2):
|
||||
price_index = (row.get('price_index') or '').strip()
|
||||
try:
|
||||
price_date = cls._as_date(row.get('price_date'))
|
||||
if not price_index:
|
||||
stats['skipped'].append(
|
||||
cls._result_line(
|
||||
row_number, '', None, 'missing price_index'))
|
||||
continue
|
||||
if not price_date:
|
||||
stats['skipped'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, None,
|
||||
'missing price_date'))
|
||||
continue
|
||||
|
||||
prices = Price.search(
|
||||
[('price_index', '=', price_index)], limit=1)
|
||||
if prices:
|
||||
price = prices[0]
|
||||
elif create_missing_price_index:
|
||||
price, = Price.create([
|
||||
cls._price_index_values(price_index)])
|
||||
stats['created_indexes'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, None,
|
||||
'price index created'))
|
||||
else:
|
||||
stats['skipped'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, None,
|
||||
'price_index missing'))
|
||||
continue
|
||||
|
||||
values = cls._price_value_values(price, row, price_date)
|
||||
existing = PriceValue.search([
|
||||
('price', '=', price.id),
|
||||
('price_date', '=', price_date),
|
||||
], limit=1)
|
||||
|
||||
if existing:
|
||||
if overwrite_existing_price:
|
||||
PriceValue.write(existing, values)
|
||||
stats['updated'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, price_date,
|
||||
cls._price_summary(values)))
|
||||
else:
|
||||
stats['skipped'].append(cls._result_line(
|
||||
row_number, price_index, price_date,
|
||||
'price_date already exists'))
|
||||
continue
|
||||
|
||||
PriceValue.create([values])
|
||||
stats['imported'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, price_date,
|
||||
cls._price_summary(values)))
|
||||
except Exception as exception:
|
||||
stats['errors'].append(
|
||||
cls._result_line(
|
||||
row_number, price_index, row.get('price_date'),
|
||||
str(exception)))
|
||||
continue
|
||||
|
||||
return stats
|
||||
|
||||
@classmethod
|
||||
def _price_index_values(cls, price_index):
|
||||
values = {
|
||||
'price_index': price_index,
|
||||
'price_desc': price_index,
|
||||
'price_curve_type': 'future',
|
||||
}
|
||||
references = [
|
||||
('price_type', 'price.fixtype', [('name', '=', 'Market price')]),
|
||||
('price_currency', 'currency.currency', [('code', '=', 'USD')]),
|
||||
('price_calendar', 'price.calendar', [('name', '=', 'Argus EU')]),
|
||||
('price_unit', 'product.uom', [('name', '=', 'Mt')]),
|
||||
]
|
||||
for field, model_name, domain in references:
|
||||
record = cls._first_record(model_name, domain)
|
||||
if record:
|
||||
values[field] = record.id
|
||||
|
||||
period = cls._period_from_price_index(price_index)
|
||||
if period:
|
||||
values['price_period'] = period.id
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def _period_from_price_index(cls, price_index):
|
||||
match = re.search(r'(?<!\d)(20\d{2})[-_/\. ](0[1-9]|1[0-2])(?!\d)',
|
||||
price_index)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
year = int(match.group(1))
|
||||
month = int(match.group(2))
|
||||
month_name = cls._period_month_name(year, month)
|
||||
periods = cls._period_model().search(
|
||||
[('month_name', '=', month_name)], limit=1)
|
||||
if periods:
|
||||
return periods[0]
|
||||
|
||||
beg_date = datetime.date(year, month, 1)
|
||||
end_date = datetime.date(
|
||||
year, month, calendar.monthrange(year, month)[1])
|
||||
period, = cls._period_model().create([{
|
||||
'month_name': month_name,
|
||||
'description': month_name,
|
||||
'beg_date': beg_date,
|
||||
'end_date': end_date,
|
||||
'is_cotation': True,
|
||||
}])
|
||||
return period
|
||||
|
||||
@staticmethod
|
||||
def _period_month_name(year, month):
|
||||
return '%s%s' % (
|
||||
calendar.month_abbr[month].upper(), str(year)[-2:])
|
||||
|
||||
@staticmethod
|
||||
def _period_model():
|
||||
return Pool().get('product.month')
|
||||
|
||||
@staticmethod
|
||||
def _first_record(model_name, domain):
|
||||
records = Pool().get(model_name).search(domain, limit=1)
|
||||
return records[0] if records else None
|
||||
|
||||
@classmethod
|
||||
def _price_value_values(cls, price, row, price_date):
|
||||
return {
|
||||
'price': price.id,
|
||||
'price_date': price_date,
|
||||
'high_price': cls._as_float(row.get('high_price')),
|
||||
'low_price': cls._as_float(row.get('low_price')),
|
||||
'open_price': cls._as_float(row.get('open_price')),
|
||||
'price_value': cls._as_float(row.get('price_value')),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _result_line(row_number, price_index, price_date, detail):
|
||||
return {
|
||||
'row': row_number,
|
||||
'price_index': price_index,
|
||||
'price_date': price_date,
|
||||
'detail': detail,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _format_result(cls, stats):
|
||||
lines = [
|
||||
'Import completed.',
|
||||
f"Created price indexes: {len(stats['created_indexes'])}",
|
||||
f"Imported prices: {len(stats['imported'])}",
|
||||
f"Updated prices: {len(stats['updated'])}",
|
||||
f"Skipped rows: {len(stats['skipped'])}",
|
||||
f"Errors: {len(stats['errors'])}",
|
||||
]
|
||||
|
||||
sections = [
|
||||
('Created price indexes', stats['created_indexes']),
|
||||
('Successfully imported prices', stats['imported']),
|
||||
('Updated existing prices', stats['updated']),
|
||||
('Skipped records', stats['skipped']),
|
||||
('Errors', stats['errors']),
|
||||
]
|
||||
for title, items in sections:
|
||||
lines.append('')
|
||||
lines.append('%s:' % title)
|
||||
if not items:
|
||||
lines.append('- None')
|
||||
continue
|
||||
for item in items:
|
||||
lines.append('- %s' % cls._format_result_line(item))
|
||||
return '\n'.join(lines)
|
||||
|
||||
@staticmethod
|
||||
def _format_result_line(item):
|
||||
label = item['price_index'] or '<empty price_index>'
|
||||
price_date = item['price_date']
|
||||
if isinstance(price_date, datetime.date):
|
||||
price_date = price_date.isoformat()
|
||||
if price_date:
|
||||
label = '%s / %s' % (label, price_date)
|
||||
return 'Row %(row)s - ' % item + '%s: %s' % (label, item['detail'])
|
||||
|
||||
@staticmethod
|
||||
def _price_summary(values):
|
||||
parts = []
|
||||
for name in ('price_value', 'open_price', 'low_price', 'high_price'):
|
||||
value = values.get(name)
|
||||
if value is not None:
|
||||
parts.append('%s=%s' % (name, value))
|
||||
return ', '.join(parts) or 'price imported'
|
||||
|
||||
@classmethod
|
||||
def _read_xlsx(cls, data):
|
||||
try:
|
||||
with zipfile.ZipFile(BytesIO(data)) as workbook:
|
||||
shared_strings = cls._read_shared_strings(workbook)
|
||||
sheet_name = cls._first_sheet_name(workbook)
|
||||
sheet = ElementTree.fromstring(workbook.read(sheet_name))
|
||||
except (KeyError, zipfile.BadZipFile, ElementTree.ParseError):
|
||||
raise UserError("The selected file is not a valid Excel .xlsx file.")
|
||||
|
||||
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||||
rows = sheet.findall('.//s:sheetData/s:row', ns)
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
headers = {}
|
||||
for cell in rows[0].findall('s:c', ns):
|
||||
index = cls._cell_column_index(cell.get('r'))
|
||||
header = cls._cell_value(cell, shared_strings)
|
||||
normalized = cls._normalize_header(header)
|
||||
if normalized in cls.REQUIRED_COLUMNS:
|
||||
headers[index] = cls.REQUIRED_COLUMNS[normalized]
|
||||
|
||||
missing = set(cls.REQUIRED_COLUMNS.values()) - set(headers.values())
|
||||
if missing:
|
||||
raise UserError(
|
||||
"Missing columns in Excel file: %s"
|
||||
% ', '.join(sorted(missing)))
|
||||
|
||||
result = []
|
||||
for sheet_row in rows[1:]:
|
||||
values = {}
|
||||
for cell in sheet_row.findall('s:c', ns):
|
||||
index = cls._cell_column_index(cell.get('r'))
|
||||
field = headers.get(index)
|
||||
if field:
|
||||
values[field] = cls._cell_value(cell, shared_strings)
|
||||
if any(v not in (None, '') for v in values.values()):
|
||||
result.append(values)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _read_shared_strings(workbook):
|
||||
try:
|
||||
content = workbook.read('xl/sharedStrings.xml')
|
||||
except KeyError:
|
||||
return []
|
||||
root = ElementTree.fromstring(content)
|
||||
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||||
strings = []
|
||||
for item in root.findall('s:si', ns):
|
||||
strings.append(''.join(
|
||||
text.text or '' for text in item.findall('.//s:t', ns)))
|
||||
return strings
|
||||
|
||||
@staticmethod
|
||||
def _first_sheet_name(workbook):
|
||||
workbook_xml = ElementTree.fromstring(workbook.read('xl/workbook.xml'))
|
||||
rels_xml = ElementTree.fromstring(
|
||||
workbook.read('xl/_rels/workbook.xml.rels'))
|
||||
wb_ns = {
|
||||
's': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
|
||||
'r': (
|
||||
'http://schemas.openxmlformats.org/officeDocument/2006/'
|
||||
'relationships'),
|
||||
}
|
||||
rel_ns = {
|
||||
'r': (
|
||||
'http://schemas.openxmlformats.org/package/2006/'
|
||||
'relationships'),
|
||||
}
|
||||
sheet = workbook_xml.find('.//s:sheet', wb_ns)
|
||||
rel_id = sheet.get(
|
||||
'{http://schemas.openxmlformats.org/officeDocument/2006/'
|
||||
'relationships}id')
|
||||
for rel in rels_xml.findall('r:Relationship', rel_ns):
|
||||
if rel.get('Id') == rel_id:
|
||||
target = rel.get('Target')
|
||||
if not target.startswith('/'):
|
||||
target = 'xl/' + target
|
||||
return target.lstrip('/')
|
||||
raise KeyError('No worksheet found')
|
||||
|
||||
@staticmethod
|
||||
def _cell_column_index(reference):
|
||||
match = re.match(r'([A-Z]+)', reference or '')
|
||||
if not match:
|
||||
return None
|
||||
index = 0
|
||||
for char in match.group(1):
|
||||
index = index * 26 + ord(char) - ord('A') + 1
|
||||
return index
|
||||
|
||||
@classmethod
|
||||
def _cell_value(cls, cell, shared_strings):
|
||||
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||||
if cell.get('t') == 'inlineStr':
|
||||
text = cell.find('.//s:t', ns)
|
||||
return text.text if text is not None else ''
|
||||
value = cell.find('s:v', ns)
|
||||
if value is None:
|
||||
return ''
|
||||
if cell.get('t') == 's':
|
||||
return shared_strings[int(value.text)]
|
||||
return value.text
|
||||
|
||||
@staticmethod
|
||||
def _normalize_header(value):
|
||||
return re.sub(r'[^a-z0-9]', '', (value or '').strip().lower())
|
||||
|
||||
@staticmethod
|
||||
def _as_float(value):
|
||||
if value in (None, ''):
|
||||
return None
|
||||
return float(str(value).replace(',', '.'))
|
||||
|
||||
@staticmethod
|
||||
def _as_date(value):
|
||||
if isinstance(value, datetime.date):
|
||||
return value
|
||||
if value in (None, ''):
|
||||
return None
|
||||
text = str(value).strip()
|
||||
if re.match(r'^\d+(\.\d+)?$', text):
|
||||
return (
|
||||
datetime.date(1899, 12, 30)
|
||||
+ datetime.timedelta(days=int(float(text))))
|
||||
for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y'):
|
||||
try:
|
||||
return datetime.datetime.strptime(text, fmt).date()
|
||||
except ValueError:
|
||||
pass
|
||||
raise UserError("Invalid price_date: %s" % text)
|
||||
|
||||
class MtmScenario(ModelSQL, ModelView):
|
||||
"MtM Scenario"
|
||||
|
||||
@@ -20,12 +20,29 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="name">summary_tree_sequence</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="estimated_view_tree">
|
||||
<field name="model">pricing.estimated</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">estimated_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="estimated_view_tree">
|
||||
<field name="model">pricing.estimated</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">estimated_tree</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="import_prices_start_view_form">
|
||||
<field name="model">purchase_trade.import_prices.start</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">import_prices_start_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="import_prices_result_view_form">
|
||||
<field name="model">purchase_trade.import_prices.result</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">import_prices_result_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="act_import_prices">
|
||||
<field name="name">Import Prices</field>
|
||||
<field name="wiz_name">purchase_trade.import_prices</field>
|
||||
<field name="window" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="component_view_tree">
|
||||
<field name="model">pricing.component</field>
|
||||
@@ -181,12 +198,17 @@ this repository contains the full copyright notices and license terms. -->
|
||||
sequence="99"
|
||||
id="menu_mtm"
|
||||
icon="tradon-mtm" />
|
||||
<menuitem
|
||||
name="Strategy"
|
||||
action="act_strategy_form"
|
||||
parent="menu_mtm"
|
||||
sequence="10"
|
||||
id="menu_strategy" />
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
<menuitem
|
||||
name="Strategy"
|
||||
action="act_strategy_form"
|
||||
parent="menu_mtm"
|
||||
sequence="10"
|
||||
id="menu_strategy" />
|
||||
<menuitem
|
||||
parent="price.menu_price"
|
||||
sequence="85"
|
||||
action="act_import_prices"
|
||||
id="menu_import_prices"/>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from decimal import Decimal
|
||||
from datetime import date
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from trytond.model.exceptions import ValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.modules.price.price_value import PriceValue
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.modules.purchase_trade.pricing import ImportPrices
|
||||
|
||||
|
||||
class PurchaseTradeTestCase(ModuleTestCase):
|
||||
@@ -70,5 +75,345 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
strategy.get_mtm(line, Decimal('10')),
|
||||
Decimal('250.00'))
|
||||
|
||||
def test_import_prices_skips_missing_index_without_create_option(self):
|
||||
'import prices skips rows when the price index is missing'
|
||||
price_model = _FakePriceModel()
|
||||
price_value_model = _FakePriceValueModel()
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
}.__getitem__
|
||||
|
||||
stats = ImportPrices._import_rows([{
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'high_price': '100',
|
||||
'low_price': '90',
|
||||
'open_price': '95',
|
||||
'price_value': '98',
|
||||
}])
|
||||
|
||||
self.assertEqual(len(stats['imported']), 0)
|
||||
self.assertEqual(len(stats['skipped']), 1)
|
||||
self.assertEqual(stats['skipped'][0]['detail'], 'price_index missing')
|
||||
|
||||
def test_import_prices_creates_index_and_imports_new_price(self):
|
||||
'import prices creates missing index when requested'
|
||||
price_model = _FakePriceModel()
|
||||
price_value_model = _FakePriceValueModel()
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
'price.fixtype': _FakeRecordModel([
|
||||
SimpleNamespace(id=10, name='Market price')]),
|
||||
'currency.currency': _FakeRecordModel([
|
||||
SimpleNamespace(id=20, code='USD')]),
|
||||
'price.calendar': _FakeRecordModel([
|
||||
SimpleNamespace(id=30, name='Argus EU')]),
|
||||
'product.uom': _FakeRecordModel([
|
||||
SimpleNamespace(id=40, name='Mt')]),
|
||||
'product.month': _FakeRecordModel(),
|
||||
}.__getitem__
|
||||
|
||||
stats = ImportPrices._import_rows([{
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'high_price': '100',
|
||||
'low_price': '90',
|
||||
'open_price': '95',
|
||||
'price_value': '98',
|
||||
}], create_missing_price_index=True)
|
||||
|
||||
self.assertEqual(len(stats['created_indexes']), 1)
|
||||
self.assertEqual(len(stats['imported']), 1)
|
||||
self.assertEqual(price_model.records[0].price_index, 'LME Copper')
|
||||
self.assertEqual(price_value_model.records[0].price_value, 98.0)
|
||||
|
||||
def test_import_prices_defaults_created_price_index(self):
|
||||
'import prices defaults newly created price index fields'
|
||||
price_model = _FakePriceModel()
|
||||
price_value_model = _FakePriceValueModel()
|
||||
period_model = _FakeRecordModel()
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
'price.fixtype': _FakeRecordModel([
|
||||
SimpleNamespace(id=10, name='Market price')]),
|
||||
'currency.currency': _FakeRecordModel([
|
||||
SimpleNamespace(id=20, code='USD')]),
|
||||
'price.calendar': _FakeRecordModel([
|
||||
SimpleNamespace(id=30, name='Argus EU')]),
|
||||
'product.uom': _FakeRecordModel([
|
||||
SimpleNamespace(id=40, name='Mt')]),
|
||||
'product.month': period_model,
|
||||
}.__getitem__
|
||||
|
||||
ImportPrices._import_rows([{
|
||||
'price_index': 'Argus Copper 2026-07',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'price_value': '98',
|
||||
}], create_missing_price_index=True)
|
||||
|
||||
price = price_model.records[0]
|
||||
period = period_model.records[0]
|
||||
self.assertEqual(price.price_type, 10)
|
||||
self.assertEqual(price.price_currency, 20)
|
||||
self.assertEqual(price.price_calendar, 30)
|
||||
self.assertEqual(price.price_curve_type, 'future')
|
||||
self.assertEqual(price.price_unit, 40)
|
||||
self.assertEqual(price.price_period, period.id)
|
||||
self.assertEqual(period.month_name, 'JUL26')
|
||||
self.assertEqual(period.beg_date, date(2026, 7, 1))
|
||||
self.assertEqual(period.end_date, date(2026, 7, 31))
|
||||
self.assertTrue(period.is_cotation)
|
||||
|
||||
def test_import_prices_uses_existing_period_from_price_index(self):
|
||||
'import prices reuses existing period parsed from the price index'
|
||||
price_model = _FakePriceModel()
|
||||
price_value_model = _FakePriceValueModel()
|
||||
existing_period = SimpleNamespace(
|
||||
id=55, month_name='APR25', description='APR25')
|
||||
period_model = _FakeRecordModel([existing_period])
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
'price.fixtype': _FakeRecordModel(),
|
||||
'currency.currency': _FakeRecordModel(),
|
||||
'price.calendar': _FakeRecordModel(),
|
||||
'product.uom': _FakeRecordModel(),
|
||||
'product.month': period_model,
|
||||
}.__getitem__
|
||||
|
||||
ImportPrices._import_rows([{
|
||||
'price_index': 'Argus Copper 2025-04',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'price_value': '98',
|
||||
}], create_missing_price_index=True)
|
||||
|
||||
self.assertEqual(price_model.records[0].price_period, 55)
|
||||
self.assertEqual(period_model.records, [existing_period])
|
||||
|
||||
def test_import_prices_skips_existing_date_without_overwrite(self):
|
||||
'import prices skips existing price dates unless overwrite is enabled'
|
||||
price = SimpleNamespace(id=1, price_index='LME Copper')
|
||||
price_model = _FakePriceModel([price])
|
||||
price_value_model = _FakePriceValueModel([
|
||||
SimpleNamespace(
|
||||
id=1, price=1, price_date=date(2026, 3, 27),
|
||||
price_value=97.0)])
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
}.__getitem__
|
||||
|
||||
stats = ImportPrices._import_rows([{
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'price_value': '98',
|
||||
}])
|
||||
|
||||
self.assertEqual(len(stats['updated']), 0)
|
||||
self.assertEqual(price_value_model.records[0].price_value, 97.0)
|
||||
self.assertEqual(
|
||||
stats['skipped'][0]['detail'], 'price_date already exists')
|
||||
|
||||
def test_import_prices_overwrites_existing_date_when_requested(self):
|
||||
'import prices updates existing price dates when requested'
|
||||
price = SimpleNamespace(id=1, price_index='LME Copper')
|
||||
price_model = _FakePriceModel([price])
|
||||
price_value_model = _FakePriceValueModel([
|
||||
SimpleNamespace(
|
||||
id=1, price=1, price_date=date(2026, 3, 27),
|
||||
price_value=97.0)])
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
}.__getitem__
|
||||
|
||||
stats = ImportPrices._import_rows([{
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'price_value': '98',
|
||||
}], overwrite_existing_price=True)
|
||||
|
||||
self.assertEqual(len(stats['updated']), 1)
|
||||
self.assertEqual(price_value_model.records[0].price_value, 98.0)
|
||||
|
||||
def test_import_prices_collects_row_errors(self):
|
||||
'import prices reports invalid row values without hiding the result'
|
||||
price = SimpleNamespace(id=1, price_index='LME Copper')
|
||||
price_model = _FakePriceModel([price])
|
||||
price_value_model = _FakePriceValueModel()
|
||||
|
||||
with patch('trytond.modules.purchase_trade.pricing.Pool') as pool:
|
||||
pool.return_value.get.side_effect = {
|
||||
'price.price': price_model,
|
||||
'price.price_value': price_value_model,
|
||||
}.__getitem__
|
||||
|
||||
stats = ImportPrices._import_rows([{
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'price_value': 'not-a-number',
|
||||
}])
|
||||
|
||||
self.assertEqual(len(stats['errors']), 1)
|
||||
self.assertIn('not-a-number', stats['errors'][0]['detail'])
|
||||
|
||||
def test_import_prices_result_lists_all_outcome_sections(self):
|
||||
'import prices formats detailed result sections'
|
||||
message = ImportPrices._format_result({
|
||||
'created_indexes': [{
|
||||
'row': 2,
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': None,
|
||||
'detail': 'price index created',
|
||||
}],
|
||||
'imported': [{
|
||||
'row': 2,
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'detail': 'price_value=98.0',
|
||||
}],
|
||||
'updated': [],
|
||||
'skipped': [{
|
||||
'row': 3,
|
||||
'price_index': 'LME Copper',
|
||||
'price_date': date(2026, 3, 27),
|
||||
'detail': 'price_date already exists',
|
||||
}],
|
||||
'errors': [{
|
||||
'row': 4,
|
||||
'price_index': 'LME Zinc',
|
||||
'price_date': 'bad-date',
|
||||
'detail': 'Invalid price_date: bad-date',
|
||||
}],
|
||||
})
|
||||
|
||||
self.assertIn('Created price indexes:', message)
|
||||
self.assertIn('Successfully imported prices:', message)
|
||||
self.assertIn('Skipped records:', message)
|
||||
self.assertIn('Errors:', message)
|
||||
self.assertIn('Row 2 - LME Copper / 2026-03-27', message)
|
||||
|
||||
def test_price_value_unique_rejects_duplicate_create(self):
|
||||
'price value uniqueness rejects duplicate model creation'
|
||||
price_value = SimpleNamespace(
|
||||
id=None,
|
||||
price=SimpleNamespace(id=1),
|
||||
price_date=date(2026, 3, 27),
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
PriceValue, 'search',
|
||||
return_value=[SimpleNamespace(id=2)]):
|
||||
with self.assertRaises(ValidationError):
|
||||
PriceValue.check_unique_price_date([price_value])
|
||||
|
||||
def test_price_value_unique_rejects_duplicate_write(self):
|
||||
'price value uniqueness excludes current record on write'
|
||||
price_value = SimpleNamespace(
|
||||
id=2,
|
||||
price=SimpleNamespace(id=1),
|
||||
price_date=date(2026, 3, 27),
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
PriceValue, 'search',
|
||||
return_value=[SimpleNamespace(id=3)]) as search:
|
||||
with self.assertRaises(ValidationError):
|
||||
PriceValue.check_unique_price_date([price_value])
|
||||
|
||||
domain = search.call_args[0][0]
|
||||
self.assertIn(('id', '!=', 2), domain[1])
|
||||
|
||||
|
||||
class _FakePriceModel:
|
||||
|
||||
def __init__(self, records=None):
|
||||
self.records = records or []
|
||||
|
||||
def search(self, domain, limit=None):
|
||||
price_index = domain[0][2]
|
||||
records = [
|
||||
record for record in self.records
|
||||
if record.price_index == price_index]
|
||||
return records[:limit] if limit else records
|
||||
|
||||
def create(self, values):
|
||||
records = []
|
||||
for value in values:
|
||||
record = SimpleNamespace(id=len(self.records) + 1, **value)
|
||||
self.records.append(record)
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
|
||||
class _FakePriceValueModel:
|
||||
|
||||
def __init__(self, records=None):
|
||||
self.records = records or []
|
||||
|
||||
def search(self, domain, limit=None):
|
||||
price = domain[0][2]
|
||||
price_date = domain[1][2]
|
||||
records = [
|
||||
record for record in self.records
|
||||
if record.price == price and record.price_date == price_date]
|
||||
return records[:limit] if limit else records
|
||||
|
||||
def create(self, values):
|
||||
records = []
|
||||
for value in values:
|
||||
record = SimpleNamespace(id=len(self.records) + 1, **value)
|
||||
self.records.append(record)
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
def write(self, records, values):
|
||||
for record in records:
|
||||
for name, value in values.items():
|
||||
setattr(record, name, value)
|
||||
|
||||
|
||||
class _FakeRecordModel:
|
||||
|
||||
def __init__(self, records=None):
|
||||
self.records = records or []
|
||||
|
||||
def search(self, domain, limit=None):
|
||||
records = self.records
|
||||
for name, operator, value in domain:
|
||||
self.assert_operator(operator)
|
||||
records = [
|
||||
record for record in records
|
||||
if getattr(record, name, None) == value]
|
||||
return records[:limit] if limit else records
|
||||
|
||||
def create(self, values):
|
||||
records = []
|
||||
for value in values:
|
||||
record = SimpleNamespace(id=len(self.records) + 1, **value)
|
||||
self.records.append(record)
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
@staticmethod
|
||||
def assert_operator(operator):
|
||||
if operator != '=':
|
||||
raise AssertionError('Unsupported operator: %s' % operator)
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<form col="4">
|
||||
<field name="message" colspan="4" height="400"/>
|
||||
</form>
|
||||
10
modules/purchase_trade/view/import_prices_start_form.xml
Normal file
10
modules/purchase_trade/view/import_prices_start_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="file_"/>
|
||||
<field name="file_"/>
|
||||
<newline/>
|
||||
<label name="create_missing_price_index"/>
|
||||
<field name="create_missing_price_index"/>
|
||||
<newline/>
|
||||
<label name="overwrite_existing_price"/>
|
||||
<field name="overwrite_existing_price"/>
|
||||
</form>
|
||||
7
modules/trade_finance/AGENTS.md
Normal file
7
modules/trade_finance/AGENTS.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# AGENTS.md
|
||||
|
||||
Instructions locales pour les agents qui interviennent dans `modules/trade_finance/`.
|
||||
|
||||
- `trade_finance` est un module additionnel de Tryton standard.
|
||||
- Le traiter comme un module standard/upstream Tryton: privilegier les patterns Tryton existants, garder les changements generiques, et eviter d'y introduire de la logique metier specifique a un client sauf demande explicite.
|
||||
- Limiter les modifications a ce module et a ses tests proches quand la demande concerne `trade_finance`.
|
||||
58
modules/trade_finance/__init__.py
Normal file
58
modules/trade_finance/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# This file is part of Tradon. 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 (
|
||||
financing,
|
||||
market,
|
||||
valuation,
|
||||
document,
|
||||
template_execution,
|
||||
counterparty,
|
||||
fx,
|
||||
operational,
|
||||
facility,
|
||||
constraint_type,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
financing.FinancingType,
|
||||
financing.OperationalStatus,
|
||||
market.MarketIndex,
|
||||
market.MarketIndexRate,
|
||||
market.IndexTerm,
|
||||
market.InterestFormula,
|
||||
valuation.ValuationMethod,
|
||||
valuation.HaircutFormula,
|
||||
valuation.CollateralType,
|
||||
document.EvidenceType,
|
||||
template_execution.TemplateSegment,
|
||||
template_execution.ExecutionTemplate,
|
||||
template_execution.ExecutionTemplateLine,
|
||||
counterparty.ReceivableCategory,
|
||||
counterparty.PaymentConditionType,
|
||||
fx.FxFeeder,
|
||||
operational.BlockingReason,
|
||||
operational.ChargeType,
|
||||
facility.FacilityStatus,
|
||||
facility.Facility,
|
||||
facility.FacilityCurrency,
|
||||
facility.FacilityCovenant,
|
||||
facility.FacilityLimit,
|
||||
facility.FacilityLimitHaircut,
|
||||
facility.FacilityLimitCurrency,
|
||||
facility.FacilityLimitCost,
|
||||
facility.FacilityLimitCostVariation,
|
||||
facility.FacilityLimitOpStatus,
|
||||
facility.FacilityLimitBankAccount,
|
||||
facility.FacilityCap,
|
||||
facility.FacilityCapHaircut,
|
||||
facility.FacilityConstraint,
|
||||
constraint_type.ConstraintType,
|
||||
module='trade_finance', type_='model')
|
||||
Pool.register(
|
||||
fx.PriceCalendar,
|
||||
module='price', type_='model')
|
||||
54
modules/trade_finance/constraint_type.py
Normal file
54
modules/trade_finance/constraint_type.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
__all__ = ['ConstraintType']
|
||||
|
||||
|
||||
class ConstraintType(ModelSQL, ModelView):
|
||||
'Constraint Type'
|
||||
__name__ = 'trade_finance.constraint_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char('Type', required=True, size=50)
|
||||
view_name = fields.Selection([], 'View')
|
||||
value_field = fields.Selection(
|
||||
'get_column_selection', 'Value Field',
|
||||
selection_change_with=['view_name'])
|
||||
label_field = fields.Selection(
|
||||
'get_column_selection', 'Label Field',
|
||||
selection_change_with=['view_name'])
|
||||
|
||||
@fields.depends('view_name')
|
||||
def get_column_selection(self):
|
||||
if not self.view_name:
|
||||
return [('', '')]
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute(
|
||||
"SELECT column_name FROM information_schema.columns "
|
||||
"WHERE table_schema = 'public' AND table_name = %s "
|
||||
"ORDER BY ordinal_position",
|
||||
(self.view_name,)
|
||||
)
|
||||
return [('', '')] + [(r[0], r[0]) for r in cursor.fetchall()]
|
||||
|
||||
@fields.depends('view_name')
|
||||
def on_change_view_name(self):
|
||||
self.value_field = None
|
||||
self.label_field = None
|
||||
|
||||
@classmethod
|
||||
def fields_get(cls, fields_names=None, level=0):
|
||||
res = super().fields_get(fields_names, level)
|
||||
if 'view_name' in res:
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute(
|
||||
"SELECT table_name FROM information_schema.views "
|
||||
"WHERE table_schema = 'public' AND table_name LIKE 'vw_tf_const_%' "
|
||||
"ORDER BY table_name"
|
||||
)
|
||||
res['view_name']['selection'] = (
|
||||
[('', '')] + [(r[0], r[0]) for r in cursor.fetchall()])
|
||||
return res
|
||||
66
modules/trade_finance/constraint_type.xml
Normal file
66
modules/trade_finance/constraint_type.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- CONSTRAINT TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="constraint_type_view_tree">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">constraint_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="constraint_type_view_form">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">constraint_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_constraint_type">
|
||||
<field name="name">Constraint Types</field>
|
||||
<field name="res_model">trade_finance.constraint_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_constraint_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="constraint_type_view_tree"/>
|
||||
<field name="act_window" ref="act_constraint_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_constraint_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="constraint_type_view_form"/>
|
||||
<field name="act_window" ref="act_constraint_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Constraint Types"
|
||||
sequence="10"
|
||||
id="menu_constraint_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_constraint_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_constraint_type">
|
||||
<field name="model">trade_finance.constraint_type</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_constraint_type_user">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.model.access" id="access_constraint_type_admin">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
39
modules/trade_finance/counterparty.py
Normal file
39
modules/trade_finance/counterparty.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['ReceivableCategory', 'PaymentConditionType']
|
||||
|
||||
|
||||
class ReceivableCategory(ModelSQL, ModelView):
|
||||
'Receivable Category'
|
||||
__name__ = 'trade_finance.receivable_category'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class PaymentConditionType(ModelSQL, ModelView):
|
||||
'Payment Condition Type'
|
||||
__name__ = 'trade_finance.payment_condition_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
remaining_risk_pct = fields.Numeric(
|
||||
'Remaining Risk (%)', digits=(16, 2),
|
||||
help='Percentage of residual credit risk retained by the company '
|
||||
'under this payment condition')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
21
modules/trade_finance/document.py
Normal file
21
modules/trade_finance/document.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['EvidenceType']
|
||||
|
||||
|
||||
class EvidenceType(ModelSQL, ModelView):
|
||||
'Evidence Type'
|
||||
__name__ = 'trade_finance.evidence_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
497
modules/trade_finance/facility.py
Normal file
497
modules/trade_finance/facility.py
Normal file
@@ -0,0 +1,497 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.model import sequence_ordered
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.exceptions import UserError
|
||||
|
||||
__all__ = [
|
||||
'FacilityStatus',
|
||||
'Facility',
|
||||
'FacilityCurrency',
|
||||
'FacilityCovenant',
|
||||
'FacilityLimit',
|
||||
'FacilityLimitHaircut',
|
||||
'FacilityLimitCurrency',
|
||||
'FacilityLimitCost',
|
||||
'FacilityLimitCostVariation',
|
||||
'FacilityLimitOpStatus',
|
||||
'FacilityLimitBankAccount',
|
||||
'FacilityCap',
|
||||
'FacilityCapHaircut',
|
||||
'FacilityConstraint',
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared Selection lists
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ATTRIBUTE_TYPES = [
|
||||
('commodity', 'Commodity'),
|
||||
('article', 'Article'),
|
||||
('origin', 'Origin'),
|
||||
('destination', 'Destination'),
|
||||
('loading_place', 'Loading Place'),
|
||||
('supplier', 'Supplier'),
|
||||
('customer', 'Customer'),
|
||||
('payment_condition_purchase', 'Payment Condition (Purchase)'),
|
||||
('payment_condition_sale', 'Payment Condition (Sale)'),
|
||||
('purchase_currency', 'Purchase Currency'),
|
||||
('warehouse', 'Warehouse'),
|
||||
('incoterm', 'Incoterm'),
|
||||
('counterparty_rating', 'Counterparty Rating'),
|
||||
('receivable_category', 'Receivable Category'),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Facility Status (configurable reference)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityStatus(ModelSQL, ModelView):
|
||||
'Facility Status'
|
||||
__name__ = 'trade_finance.facility_status'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Facility Header
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class Facility(ModelSQL, ModelView):
|
||||
'TF Facility'
|
||||
__name__ = 'trade_finance.facility'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
tfe = fields.Many2One('bank', 'Trade Finance Entity', required=True,
|
||||
ondelete='RESTRICT',
|
||||
help='Bank or fund providing this facility')
|
||||
description = fields.Text('Description')
|
||||
|
||||
status = fields.Many2One('trade_finance.facility_status', 'Status',
|
||||
ondelete='RESTRICT')
|
||||
commitment_status = fields.Selection([
|
||||
('uncommitted', 'Uncommitted'),
|
||||
('committed', 'Committed'),
|
||||
], 'Commitment Status', required=True)
|
||||
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
currency = fields.Many2One('currency.currency', 'Facility Currency',
|
||||
required=True, ondelete='RESTRICT')
|
||||
fx_feeder = fields.Many2One('trade_finance.fx_feeder', 'FX Rate Feeder',
|
||||
ondelete='RESTRICT')
|
||||
fx_haircut_pct = fields.Numeric('FX Haircut (%)', digits=(16, 2))
|
||||
|
||||
is_tpa = fields.Boolean('TPA',
|
||||
help='Third Party Account — broker managed structure')
|
||||
broker = fields.Many2One('party.party', 'Broker',
|
||||
states={'invisible': ~Bool(Eval('is_tpa'))},
|
||||
depends=['is_tpa'], ondelete='RESTRICT')
|
||||
broker_account = fields.Char('Broker Account',
|
||||
states={'invisible': ~Bool(Eval('is_tpa'))},
|
||||
depends=['is_tpa'])
|
||||
|
||||
limits = fields.One2Many('trade_finance.facility_limit', 'facility',
|
||||
'Limits')
|
||||
currencies = fields.One2Many('trade_finance.facility_currency', 'facility',
|
||||
'Accepted Currencies')
|
||||
caps = fields.One2Many('trade_finance.facility_cap', 'facility', 'Caps')
|
||||
covenants = fields.One2Many('trade_finance.facility_covenant', 'facility',
|
||||
'Covenants')
|
||||
constraints = fields.One2Many('trade_finance.facility_constraint',
|
||||
'facility', 'Facility Constraints',
|
||||
domain=[('limit', '=', None)])
|
||||
|
||||
@classmethod
|
||||
def default_status(cls):
|
||||
pool = Pool()
|
||||
FacilityStatus = pool.get('trade_finance.facility_status')
|
||||
statuses = FacilityStatus.search([('code', '=', 'DRAFT')], limit=1)
|
||||
if statuses:
|
||||
return statuses[0].id
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def default_commitment_status():
|
||||
return 'uncommitted'
|
||||
|
||||
@staticmethod
|
||||
def default_is_tpa():
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Facility Accepted Currency
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityCurrency(ModelSQL, ModelView):
|
||||
'Facility Currency'
|
||||
__name__ = 'trade_finance.facility_currency'
|
||||
|
||||
facility = fields.Many2One('trade_finance.facility', 'Facility',
|
||||
required=True, ondelete='CASCADE')
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
required=True, ondelete='RESTRICT')
|
||||
fx_haircut_formula = fields.Many2One('trade_finance.haircut_formula',
|
||||
'FX Haircut Formula', ondelete='RESTRICT')
|
||||
fx_feeder = fields.Many2One('trade_finance.fx_feeder', 'FX Rate Feeder',
|
||||
ondelete='RESTRICT')
|
||||
valuation_method = fields.Many2One('trade_finance.valuation_method',
|
||||
'Valuation Method', ondelete='RESTRICT')
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Covenant
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityCovenant(ModelSQL, ModelView):
|
||||
'Facility Covenant'
|
||||
__name__ = 'trade_finance.facility_covenant'
|
||||
_rec_name = 'name'
|
||||
|
||||
facility = fields.Many2One('trade_finance.facility', 'Facility',
|
||||
required=True, ondelete='CASCADE')
|
||||
name = fields.Char('Covenant', required=True)
|
||||
ratio_type = fields.Selection([
|
||||
('leverage_ratio', 'Leverage Ratio'),
|
||||
('dso', 'DSO'),
|
||||
('interest_coverage', 'Interest Coverage Ratio'),
|
||||
('current_ratio', 'Current Ratio'),
|
||||
('other', 'Other'),
|
||||
], 'Ratio Type', required=True)
|
||||
threshold = fields.Numeric('Threshold', digits=(16, 4))
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
ondelete='RESTRICT')
|
||||
notes = fields.Text('Notes')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Facility Limit (Global Limit + Sub-Limits in one model)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimit(ModelSQL, ModelView):
|
||||
'Facility Limit'
|
||||
__name__ = 'trade_finance.facility_limit'
|
||||
_rec_name = 'name'
|
||||
|
||||
facility = fields.Many2One('trade_finance.facility', 'Facility',
|
||||
required=True, ondelete='CASCADE',
|
||||
states={'readonly': Bool(Eval('parent'))},
|
||||
depends=['parent'])
|
||||
parent = fields.Many2One('trade_finance.facility_limit', 'Parent Limit',
|
||||
ondelete='RESTRICT',
|
||||
domain=[('facility', '=', Eval('facility'))],
|
||||
depends=['facility'],
|
||||
help='Leave empty for Global Limit (root node)')
|
||||
children = fields.One2Many('trade_finance.facility_limit', 'parent',
|
||||
'Sub-Limits')
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
alternative_name = fields.Char('Limit Alternative Name')
|
||||
financing_type = fields.Many2One('trade_finance.financing_type',
|
||||
'Financing Type', ondelete='RESTRICT')
|
||||
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
|
||||
tenor = fields.Integer('Tenor (days)',
|
||||
help='Maximum duration of financing from drawdown to repayment')
|
||||
sequence = fields.Integer('Sequence')
|
||||
|
||||
_order = [('sequence', 'ASC'), ('id', 'ASC')]
|
||||
|
||||
haircuts = fields.One2Many('trade_finance.facility_limit_haircut', 'limit',
|
||||
'Haircuts')
|
||||
currencies = fields.One2Many('trade_finance.facility_limit_currency',
|
||||
'limit', 'Accepted Currencies')
|
||||
costs = fields.One2Many('trade_finance.facility_limit_cost', 'limit',
|
||||
'Costs')
|
||||
op_statuses = fields.One2Many('trade_finance.facility_limit_op_status',
|
||||
'limit', 'Expected Operational Statuses')
|
||||
bank_accounts = fields.One2Many('trade_finance.facility_limit_bank_account',
|
||||
'limit', 'Bank Accounts')
|
||||
constraints = fields.One2Many('trade_finance.facility_constraint', 'limit',
|
||||
'Limit Constraints',
|
||||
domain=[('facility', '=', None)])
|
||||
|
||||
@staticmethod
|
||||
def default_sequence():
|
||||
return 10
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
vlist = [v.copy() for v in vlist]
|
||||
for values in vlist:
|
||||
if values.get('parent') and not values.get('facility'):
|
||||
parent = cls(values['parent'])
|
||||
values['facility'] = parent.facility.id
|
||||
return super().create(vlist)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, limits):
|
||||
super().validate(limits)
|
||||
for limit in limits:
|
||||
limit.check_single_root()
|
||||
limit.check_amount_vs_parent()
|
||||
limit.check_children_amounts()
|
||||
|
||||
def check_single_root(self):
|
||||
if self.parent is None:
|
||||
roots = self.__class__.search([
|
||||
('facility', '=', self.facility.id),
|
||||
('parent', '=', None),
|
||||
('id', '!=', self.id),
|
||||
])
|
||||
if roots:
|
||||
raise UserError(
|
||||
f"Facility '{self.facility.name}' already has a Global "
|
||||
f"Limit ('{roots[0].name}'). Only one root limit is "
|
||||
f"allowed per facility.")
|
||||
|
||||
def check_amount_vs_parent(self):
|
||||
if self.parent and self.amount > self.parent.amount:
|
||||
raise UserError(
|
||||
f"Sub-limit '{self.name}' amount ({self.amount}) "
|
||||
f"cannot exceed parent limit '{self.parent.name}' "
|
||||
f"amount ({self.parent.amount}).")
|
||||
|
||||
def check_children_amounts(self):
|
||||
for child in self.children:
|
||||
if child.amount > self.amount:
|
||||
raise UserError(
|
||||
f"Sub-limit '{child.name}' amount ({child.amount}) "
|
||||
f"cannot exceed parent limit '{self.name}' "
|
||||
f"amount ({self.amount}).")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Haircut
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitHaircut(ModelSQL, ModelView):
|
||||
'Facility Limit Haircut'
|
||||
__name__ = 'trade_finance.facility_limit_haircut'
|
||||
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
required=True, ondelete='CASCADE')
|
||||
attribute = fields.Selection(ATTRIBUTE_TYPES, 'Attribute Type',
|
||||
required=True)
|
||||
value = fields.Char('Value',
|
||||
help='The specific attribute value this haircut applies to '
|
||||
'(e.g. "Brazil", "Coffee", "USD")')
|
||||
haircut_pct = fields.Numeric('Haircut (%)', digits=(16, 2), required=True)
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Accepted Currency
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitCurrency(ModelSQL, ModelView):
|
||||
'Facility Limit Currency'
|
||||
__name__ = 'trade_finance.facility_limit_currency'
|
||||
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
required=True, ondelete='CASCADE')
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
required=True, ondelete='RESTRICT')
|
||||
haircut_pct = fields.Numeric('FX Haircut (%)', digits=(16, 2))
|
||||
fx_feeder = fields.Many2One('trade_finance.fx_feeder', 'FX Rate Feeder',
|
||||
ondelete='RESTRICT')
|
||||
valuation_method = fields.Many2One('trade_finance.valuation_method',
|
||||
'Valuation Method', ondelete='RESTRICT')
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Cost
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitCost(ModelSQL, ModelView):
|
||||
'Facility Limit Cost'
|
||||
__name__ = 'trade_finance.facility_limit_cost'
|
||||
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
required=True, ondelete='CASCADE')
|
||||
cost_type = fields.Selection([
|
||||
('interest', 'Interest-Based'),
|
||||
('flat', 'Flat / Minimal Amount'),
|
||||
], 'Cost Type', required=True)
|
||||
|
||||
# Interest-based fields
|
||||
spread = fields.Numeric('Spread (%)', digits=(16, 4),
|
||||
states={'invisible': Eval('cost_type') != 'interest'},
|
||||
depends=['cost_type'])
|
||||
index = fields.Many2One('trade_finance.market_index', 'Market Index',
|
||||
ondelete='RESTRICT',
|
||||
states={'invisible': Eval('cost_type') != 'interest'},
|
||||
depends=['cost_type'])
|
||||
index_term = fields.Many2One('trade_finance.index_term', 'Index Term',
|
||||
ondelete='RESTRICT',
|
||||
states={'invisible': Eval('cost_type') != 'interest'},
|
||||
depends=['cost_type'])
|
||||
interest_formula = fields.Many2One('trade_finance.interest_formula',
|
||||
'Interest Formula', ondelete='RESTRICT',
|
||||
states={'invisible': Eval('cost_type') != 'interest'},
|
||||
depends=['cost_type'])
|
||||
|
||||
# Flat fields
|
||||
flat_amount = fields.Numeric('Flat Amount', digits=(16, 2),
|
||||
states={'invisible': Eval('cost_type') != 'flat'},
|
||||
depends=['cost_type'])
|
||||
flat_currency = fields.Many2One('currency.currency', 'Currency',
|
||||
ondelete='RESTRICT',
|
||||
states={'invisible': Eval('cost_type') != 'flat'},
|
||||
depends=['cost_type'])
|
||||
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
variations = fields.One2Many('trade_finance.facility_limit_cost_variation',
|
||||
'cost', 'Cost Variations')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Cost Variation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitCostVariation(ModelSQL, ModelView):
|
||||
'Facility Limit Cost Variation'
|
||||
__name__ = 'trade_finance.facility_limit_cost_variation'
|
||||
|
||||
cost = fields.Many2One('trade_finance.facility_limit_cost', 'Cost',
|
||||
required=True, ondelete='CASCADE')
|
||||
attribute = fields.Selection(ATTRIBUTE_TYPES, 'Attribute Type',
|
||||
required=True)
|
||||
value = fields.Char('Value')
|
||||
variation_type = fields.Selection([
|
||||
('pct', 'Percentage (+/-)'),
|
||||
('flat', 'Flat Amount (+/-)'),
|
||||
], 'Variation Type', required=True)
|
||||
variation_value = fields.Numeric('Variation Value', digits=(16, 4),
|
||||
required=True)
|
||||
variation_currency = fields.Many2One('currency.currency', 'Currency',
|
||||
ondelete='RESTRICT',
|
||||
states={'invisible': Eval('variation_type') != 'flat'},
|
||||
depends=['variation_type'])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Expected Operational Status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitOpStatus(ModelSQL, ModelView):
|
||||
'Facility Limit Operational Status'
|
||||
__name__ = 'trade_finance.facility_limit_op_status'
|
||||
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
required=True, ondelete='CASCADE')
|
||||
operational_status = fields.Many2One('trade_finance.operational_status',
|
||||
'Operational Status', required=True, ondelete='RESTRICT')
|
||||
evidence_type = fields.Many2One('trade_finance.evidence_type',
|
||||
'Required Evidence', ondelete='RESTRICT',
|
||||
help='Evidence required when this operational status is reached')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Limit Bank Account
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityLimitBankAccount(ModelSQL, ModelView):
|
||||
'Facility Limit Bank Account'
|
||||
__name__ = 'trade_finance.facility_limit_bank_account'
|
||||
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
required=True, ondelete='CASCADE')
|
||||
bank_account = fields.Many2One('bank.account', 'Bank Account',
|
||||
required=True, ondelete='RESTRICT')
|
||||
currency = fields.Many2One('currency.currency', 'Currency',
|
||||
required=True, ondelete='RESTRICT')
|
||||
is_default = fields.Boolean('Default')
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
@staticmethod
|
||||
def default_is_default():
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cap
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityCap(ModelSQL, ModelView):
|
||||
'Facility Cap'
|
||||
__name__ = 'trade_finance.facility_cap'
|
||||
_rec_name = 'name'
|
||||
|
||||
facility = fields.Many2One('trade_finance.facility', 'Facility',
|
||||
required=True, ondelete='CASCADE')
|
||||
name = fields.Char('Name', required=True)
|
||||
amount = fields.Numeric('Cap Amount', digits=(16, 2), required=True)
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
haircuts = fields.One2Many('trade_finance.facility_cap_haircut', 'cap',
|
||||
'Haircuts')
|
||||
constraints = fields.One2Many('trade_finance.facility_constraint', 'cap',
|
||||
'Cap Constraints')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cap Haircut
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityCapHaircut(ModelSQL, ModelView):
|
||||
'Facility Cap Haircut'
|
||||
__name__ = 'trade_finance.facility_cap_haircut'
|
||||
|
||||
cap = fields.Many2One('trade_finance.facility_cap', 'Cap',
|
||||
required=True, ondelete='CASCADE')
|
||||
attribute = fields.Selection(ATTRIBUTE_TYPES, 'Attribute Type',
|
||||
required=True)
|
||||
value = fields.Char('Value')
|
||||
haircut_pct = fields.Numeric('Haircut (%)', digits=(16, 2), required=True)
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constraint (shared by Facility, Limit, Cap)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FacilityConstraint(ModelSQL, ModelView):
|
||||
'Facility Constraint'
|
||||
__name__ = 'trade_finance.facility_constraint'
|
||||
|
||||
facility = fields.Many2One('trade_finance.facility', 'Facility',
|
||||
ondelete='CASCADE')
|
||||
limit = fields.Many2One('trade_finance.facility_limit', 'Limit',
|
||||
ondelete='CASCADE')
|
||||
cap = fields.Many2One('trade_finance.facility_cap', 'Cap',
|
||||
ondelete='CASCADE')
|
||||
|
||||
constraint_type = fields.Many2One('trade_finance.constraint_type',
|
||||
'Constraint Type', required=True, ondelete='RESTRICT')
|
||||
is_exclusion = fields.Boolean('Exclusion',
|
||||
help='Checked = Exclusion constraint, unchecked = Inclusion constraint')
|
||||
date_from = fields.Date('Valid From')
|
||||
date_to = fields.Date('Valid To')
|
||||
|
||||
@staticmethod
|
||||
def default_is_exclusion():
|
||||
return False
|
||||
447
modules/trade_finance/facility.xml
Normal file
447
modules/trade_finance/facility.xml
Normal file
@@ -0,0 +1,447 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- ACCESS GROUP -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="res.group" id="group_trade_finance_user">
|
||||
<field name="name">Trade Finance</field>
|
||||
</record>
|
||||
<record model="res.user-res.group"
|
||||
id="user_admin_group_trade_finance_user">
|
||||
<field name="user" ref="res.user_admin"/>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY STATUS — Reference Data -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="trade_finance.facility_status" id="facility_status_draft">
|
||||
<field name="code">DRAFT</field>
|
||||
<field name="name">Draft</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY STATUS -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_status_view_tree">
|
||||
<field name="model">trade_finance.facility_status</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_status_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="facility_status_view_form">
|
||||
<field name="model">trade_finance.facility_status</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">facility_status_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_facility_status">
|
||||
<field name="name">Facility Statuses</field>
|
||||
<field name="res_model">trade_finance.facility_status</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_facility_status_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="facility_status_view_tree"/>
|
||||
<field name="act_window" ref="act_facility_status"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_facility_status_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="facility_status_view_form"/>
|
||||
<field name="act_window" ref="act_facility_status"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Facility Statuses"
|
||||
sequence="5"
|
||||
id="menu_facility_status"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_facility_status"/>
|
||||
|
||||
<record model="ir.model.access" id="access_facility_status">
|
||||
<field name="model">trade_finance.facility_status</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_facility_status_user">
|
||||
<field name="model">trade_finance.facility_status</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.model.access" id="access_facility_status_admin">
|
||||
<field name="model">trade_finance.facility_status</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_view_tree">
|
||||
<field name="model">trade_finance.facility</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="facility_view_form">
|
||||
<field name="model">trade_finance.facility</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">facility_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_facility">
|
||||
<field name="name">TF Facilities</field>
|
||||
<field name="res_model">trade_finance.facility</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_facility_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="facility_view_tree"/>
|
||||
<field name="act_window" ref="act_facility"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_facility_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="facility_view_form"/>
|
||||
<field name="act_window" ref="act_facility"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Facilities"
|
||||
sequence="10"
|
||||
id="menu_facility"
|
||||
parent="menu_trade_finance"
|
||||
action="act_facility"/>
|
||||
|
||||
<record model="ir.model.access" id="access_facility">
|
||||
<field name="model">trade_finance.facility</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_facility_user">
|
||||
<field name="model">trade_finance.facility</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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_facility_admin">
|
||||
<field name="model">trade_finance.facility</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY CURRENCY -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_currency_view_tree">
|
||||
<field name="model">trade_finance.facility_currency</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_currency_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_currency">
|
||||
<field name="model">trade_finance.facility_currency</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_facility_currency_user">
|
||||
<field name="model">trade_finance.facility_currency</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY COVENANT -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_covenant_view_tree">
|
||||
<field name="model">trade_finance.facility_covenant</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_covenant_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_covenant">
|
||||
<field name="model">trade_finance.facility_covenant</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_facility_covenant_user">
|
||||
<field name="model">trade_finance.facility_covenant</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY LIMIT -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_limit_view_tree">
|
||||
<field name="model">trade_finance.facility_limit</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="facility_limit_view_form">
|
||||
<field name="model">trade_finance.facility_limit</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">facility_limit_form</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit">
|
||||
<field name="model">trade_finance.facility_limit</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_facility_limit_user">
|
||||
<field name="model">trade_finance.facility_limit</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY LIMIT CHILD TABLES -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_limit_haircut_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_haircut</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_haircut_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_haircut">
|
||||
<field name="model">trade_finance.facility_limit_haircut</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_facility_limit_haircut_user">
|
||||
<field name="model">trade_finance.facility_limit_haircut</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_limit_currency_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_currency</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_currency_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_currency">
|
||||
<field name="model">trade_finance.facility_limit_currency</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_facility_limit_currency_user">
|
||||
<field name="model">trade_finance.facility_limit_currency</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_limit_cost_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_cost</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_cost_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_cost">
|
||||
<field name="model">trade_finance.facility_limit_cost</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_facility_limit_cost_user">
|
||||
<field name="model">trade_finance.facility_limit_cost</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_limit_cost_variation_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_cost_variation</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_cost_variation_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_cost_variation">
|
||||
<field name="model">trade_finance.facility_limit_cost_variation</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_facility_limit_cost_variation_user">
|
||||
<field name="model">trade_finance.facility_limit_cost_variation</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_limit_op_status_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_op_status</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_op_status_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_op_status">
|
||||
<field name="model">trade_finance.facility_limit_op_status</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_facility_limit_op_status_user">
|
||||
<field name="model">trade_finance.facility_limit_op_status</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_limit_bank_account_view_tree">
|
||||
<field name="model">trade_finance.facility_limit_bank_account</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_limit_bank_account_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_limit_bank_account">
|
||||
<field name="model">trade_finance.facility_limit_bank_account</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_facility_limit_bank_account_user">
|
||||
<field name="model">trade_finance.facility_limit_bank_account</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY CAP -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_cap_view_tree">
|
||||
<field name="model">trade_finance.facility_cap</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_cap_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_cap">
|
||||
<field name="model">trade_finance.facility_cap</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_facility_cap_user">
|
||||
<field name="model">trade_finance.facility_cap</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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.ui.view" id="facility_cap_haircut_view_tree">
|
||||
<field name="model">trade_finance.facility_cap_haircut</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_cap_haircut_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_cap_haircut">
|
||||
<field name="model">trade_finance.facility_cap_haircut</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_facility_cap_haircut_user">
|
||||
<field name="model">trade_finance.facility_cap_haircut</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FACILITY CONSTRAINT -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="facility_constraint_view_tree">
|
||||
<field name="model">trade_finance.facility_constraint</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">facility_constraint_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="facility_constraint_view_form">
|
||||
<field name="model">trade_finance.facility_constraint</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">facility_constraint_form</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_facility_constraint">
|
||||
<field name="model">trade_finance.facility_constraint</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_facility_constraint_user">
|
||||
<field name="model">trade_finance.facility_constraint</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<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>
|
||||
47
modules/trade_finance/financing.py
Normal file
47
modules/trade_finance/financing.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['FinancingType', 'OperationalStatus']
|
||||
|
||||
|
||||
class FinancingType(ModelSQL, ModelView):
|
||||
'Financing Type'
|
||||
__name__ = 'trade_finance.financing_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def default_sequence():
|
||||
return 10
|
||||
|
||||
|
||||
class OperationalStatus(ModelSQL, ModelView):
|
||||
'Operational Status'
|
||||
__name__ = 'trade_finance.operational_status'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
financing_type = fields.Many2One(
|
||||
'trade_finance.financing_type', 'Default Financing Type',
|
||||
ondelete='RESTRICT')
|
||||
sequence = fields.Integer('Sequence')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def default_sequence():
|
||||
return 10
|
||||
37
modules/trade_finance/fx.py
Normal file
37
modules/trade_finance/fx.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
__all__ = ['FxFeeder', 'PriceCalendar']
|
||||
|
||||
|
||||
class FxFeeder(ModelSQL, ModelView):
|
||||
'FX Rate Feeder'
|
||||
__name__ = 'trade_finance.fx_feeder'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
source_description = fields.Text(
|
||||
'Source Description',
|
||||
help='Description of the FX rate source / provider')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class PriceCalendar(metaclass=PoolMeta):
|
||||
__name__ = 'price.calendar'
|
||||
|
||||
purpose = fields.Selection([
|
||||
(None, ''),
|
||||
('banking', 'Banking'),
|
||||
('market', 'Market'),
|
||||
('factoring', 'Factoring'),
|
||||
], 'Purpose',
|
||||
help='Use of this calendar: Banking days, Market trading days, '
|
||||
'or Factoring program calendar')
|
||||
64
modules/trade_finance/market.py
Normal file
64
modules/trade_finance/market.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['MarketIndex', 'MarketIndexRate', 'IndexTerm', 'InterestFormula']
|
||||
|
||||
|
||||
class MarketIndex(ModelSQL, ModelView):
|
||||
'Market Index'
|
||||
__name__ = 'trade_finance.market_index'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
rates = fields.One2Many(
|
||||
'trade_finance.market_index_rate', 'index', 'Rates')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class MarketIndexRate(ModelSQL, ModelView):
|
||||
'Market Index Rate'
|
||||
__name__ = 'trade_finance.market_index_rate'
|
||||
|
||||
index = fields.Many2One(
|
||||
'trade_finance.market_index', 'Index',
|
||||
required=True, ondelete='CASCADE')
|
||||
rate_date = fields.Date('Date', required=True)
|
||||
rate = fields.Numeric('Rate (%)', digits=(16, 6), required=True)
|
||||
|
||||
|
||||
class IndexTerm(ModelSQL, ModelView):
|
||||
'Index Term'
|
||||
__name__ = 'trade_finance.index_term'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
days = fields.Integer('Days', required=True,
|
||||
help='Approximate number of days for this term (O/N=1, 1W=7, 1M=30, '
|
||||
'3M=90, 6M=180, 1Y=365)')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class InterestFormula(ModelSQL, ModelView):
|
||||
'Interest Calculation Formula'
|
||||
__name__ = 'trade_finance.interest_formula'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
35
modules/trade_finance/operational.py
Normal file
35
modules/trade_finance/operational.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['BlockingReason', 'ChargeType']
|
||||
|
||||
|
||||
class BlockingReason(ModelSQL, ModelView):
|
||||
'Blocking Reason'
|
||||
__name__ = 'trade_finance.blocking_reason'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ChargeType(ModelSQL, ModelView):
|
||||
'Charge Type'
|
||||
__name__ = 'trade_finance.charge_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
947
modules/trade_finance/reference.xml
Normal file
947
modules/trade_finance/reference.xml
Normal file
@@ -0,0 +1,947 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- ACCESS GROUPS -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="res.group" id="group_trade_finance_admin">
|
||||
<field name="name">Trade Finance Administration</field>
|
||||
</record>
|
||||
<record model="res.user-res.group"
|
||||
id="user_admin_group_trade_finance_admin">
|
||||
<field name="user" ref="res.user_admin"/>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- TOP-LEVEL MENU -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<menuitem
|
||||
name="Trade Finance"
|
||||
sequence="70"
|
||||
id="menu_trade_finance"/>
|
||||
|
||||
<menuitem
|
||||
name="Configuration"
|
||||
sequence="99"
|
||||
id="menu_trade_finance_configuration"
|
||||
parent="menu_trade_finance"/>
|
||||
|
||||
<menuitem
|
||||
name="Market Data"
|
||||
sequence="20"
|
||||
id="menu_trade_finance_market_data"
|
||||
parent="menu_trade_finance"/>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FINANCING TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="financing_type_view_tree">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">financing_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="financing_type_view_form">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">financing_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_financing_type">
|
||||
<field name="name">Financing Types</field>
|
||||
<field name="res_model">trade_finance.financing_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_financing_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="financing_type_view_tree"/>
|
||||
<field name="act_window" ref="act_financing_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_financing_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="financing_type_view_form"/>
|
||||
<field name="act_window" ref="act_financing_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Financing Types"
|
||||
sequence="10"
|
||||
id="menu_financing_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_financing_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_financing_type">
|
||||
<field name="model">trade_finance.financing_type</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_financing_type_admin">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- OPERATIONAL STATUS -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="operational_status_view_tree">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">operational_status_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="operational_status_view_form">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">operational_status_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_operational_status">
|
||||
<field name="name">Operational Statuses</field>
|
||||
<field name="res_model">trade_finance.operational_status</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_operational_status_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="operational_status_view_tree"/>
|
||||
<field name="act_window" ref="act_operational_status"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_operational_status_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="operational_status_view_form"/>
|
||||
<field name="act_window" ref="act_operational_status"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Operational Statuses"
|
||||
sequence="20"
|
||||
id="menu_operational_status"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_operational_status"/>
|
||||
|
||||
<record model="ir.model.access" id="access_operational_status">
|
||||
<field name="model">trade_finance.operational_status</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_operational_status_admin">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- MARKET INDEX -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="market_index_view_tree">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">market_index_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="market_index_view_form">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">market_index_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_market_index">
|
||||
<field name="name">Market Indexes</field>
|
||||
<field name="res_model">trade_finance.market_index</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="market_index_view_tree"/>
|
||||
<field name="act_window" ref="act_market_index"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="market_index_view_form"/>
|
||||
<field name="act_window" ref="act_market_index"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Market Indexes"
|
||||
sequence="10"
|
||||
id="menu_market_index"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_market_index"/>
|
||||
|
||||
<record model="ir.model.access" id="access_market_index">
|
||||
<field name="model">trade_finance.market_index</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_market_index_admin">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- MARKET INDEX RATE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="market_index_rate_view_tree">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">market_index_rate_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="market_index_rate_view_form">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">market_index_rate_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_market_index_rate">
|
||||
<field name="name">Market Index Rates</field>
|
||||
<field name="res_model">trade_finance.market_index_rate</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_rate_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="market_index_rate_view_tree"/>
|
||||
<field name="act_window" ref="act_market_index_rate"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_rate_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="market_index_rate_view_form"/>
|
||||
<field name="act_window" ref="act_market_index_rate"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Market Index Rates"
|
||||
sequence="10"
|
||||
id="menu_market_index_rate"
|
||||
parent="menu_trade_finance_market_data"
|
||||
action="act_market_index_rate"/>
|
||||
|
||||
<record model="ir.model.access" id="access_market_index_rate">
|
||||
<field name="model">trade_finance.market_index_rate</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_market_index_rate_admin">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- INDEX TERM -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="index_term_view_tree">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">index_term_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="index_term_view_form">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">index_term_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_index_term">
|
||||
<field name="name">Index Terms</field>
|
||||
<field name="res_model">trade_finance.index_term</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_index_term_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="index_term_view_tree"/>
|
||||
<field name="act_window" ref="act_index_term"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_index_term_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="index_term_view_form"/>
|
||||
<field name="act_window" ref="act_index_term"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Index Terms"
|
||||
sequence="30"
|
||||
id="menu_index_term"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_index_term"/>
|
||||
|
||||
<record model="ir.model.access" id="access_index_term">
|
||||
<field name="model">trade_finance.index_term</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_index_term_admin">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- INTEREST FORMULA -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="interest_formula_view_tree">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">interest_formula_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="interest_formula_view_form">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">interest_formula_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_interest_formula">
|
||||
<field name="name">Interest Formulas</field>
|
||||
<field name="res_model">trade_finance.interest_formula</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_interest_formula_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="interest_formula_view_tree"/>
|
||||
<field name="act_window" ref="act_interest_formula"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_interest_formula_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="interest_formula_view_form"/>
|
||||
<field name="act_window" ref="act_interest_formula"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Interest Formulas"
|
||||
sequence="40"
|
||||
id="menu_interest_formula"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_interest_formula"/>
|
||||
|
||||
<record model="ir.model.access" id="access_interest_formula">
|
||||
<field name="model">trade_finance.interest_formula</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_interest_formula_admin">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- HAIRCUT FORMULA -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="haircut_formula_view_tree">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">haircut_formula_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="haircut_formula_view_form">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">haircut_formula_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_haircut_formula">
|
||||
<field name="name">Haircut Formulas</field>
|
||||
<field name="res_model">trade_finance.haircut_formula</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_haircut_formula_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="haircut_formula_view_tree"/>
|
||||
<field name="act_window" ref="act_haircut_formula"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_haircut_formula_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="haircut_formula_view_form"/>
|
||||
<field name="act_window" ref="act_haircut_formula"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Haircut Formulas"
|
||||
sequence="50"
|
||||
id="menu_haircut_formula"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_haircut_formula"/>
|
||||
|
||||
<record model="ir.model.access" id="access_haircut_formula">
|
||||
<field name="model">trade_finance.haircut_formula</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_haircut_formula_admin">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- VALUATION METHOD -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="valuation_method_view_tree">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">valuation_method_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_method_view_form">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">valuation_method_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_valuation_method">
|
||||
<field name="name">Valuation Methods</field>
|
||||
<field name="res_model">trade_finance.valuation_method</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_valuation_method_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="valuation_method_view_tree"/>
|
||||
<field name="act_window" ref="act_valuation_method"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_valuation_method_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="valuation_method_view_form"/>
|
||||
<field name="act_window" ref="act_valuation_method"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Valuation Methods"
|
||||
sequence="60"
|
||||
id="menu_valuation_method"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_valuation_method"/>
|
||||
|
||||
<record model="ir.model.access" id="access_valuation_method">
|
||||
<field name="model">trade_finance.valuation_method</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_valuation_method_admin">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- COLLATERAL TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="collateral_type_view_tree">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">collateral_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="collateral_type_view_form">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">collateral_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_collateral_type">
|
||||
<field name="name">Collateral Types</field>
|
||||
<field name="res_model">trade_finance.collateral_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_collateral_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="collateral_type_view_tree"/>
|
||||
<field name="act_window" ref="act_collateral_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_collateral_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="collateral_type_view_form"/>
|
||||
<field name="act_window" ref="act_collateral_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Collateral Types"
|
||||
sequence="70"
|
||||
id="menu_collateral_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_collateral_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_collateral_type">
|
||||
<field name="model">trade_finance.collateral_type</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_collateral_type_admin">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- EVIDENCE TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="evidence_type_view_tree">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">evidence_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="evidence_type_view_form">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">evidence_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_evidence_type">
|
||||
<field name="name">Evidence Types</field>
|
||||
<field name="res_model">trade_finance.evidence_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_evidence_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="evidence_type_view_tree"/>
|
||||
<field name="act_window" ref="act_evidence_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_evidence_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="evidence_type_view_form"/>
|
||||
<field name="act_window" ref="act_evidence_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Evidence Types"
|
||||
sequence="80"
|
||||
id="menu_evidence_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_evidence_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_evidence_type">
|
||||
<field name="model">trade_finance.evidence_type</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_evidence_type_admin">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- TEMPLATE SEGMENT -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="template_segment_view_tree">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">template_segment_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_segment_view_form">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_segment_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_template_segment">
|
||||
<field name="name">Template Segments</field>
|
||||
<field name="res_model">trade_finance.template_segment</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_template_segment_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="template_segment_view_tree"/>
|
||||
<field name="act_window" ref="act_template_segment"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_template_segment_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="template_segment_view_form"/>
|
||||
<field name="act_window" ref="act_template_segment"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Template Segments"
|
||||
sequence="90"
|
||||
id="menu_template_segment"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_template_segment"/>
|
||||
|
||||
<record model="ir.model.access" id="access_template_segment">
|
||||
<field name="model">trade_finance.template_segment</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_template_segment_admin">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- EXECUTION TEMPLATE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="execution_template_view_tree">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">execution_template_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="execution_template_view_form">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">execution_template_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_execution_template">
|
||||
<field name="name">Execution Templates</field>
|
||||
<field name="res_model">trade_finance.execution_template</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_execution_template_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="execution_template_view_tree"/>
|
||||
<field name="act_window" ref="act_execution_template"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_execution_template_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="execution_template_view_form"/>
|
||||
<field name="act_window" ref="act_execution_template"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Execution Templates"
|
||||
sequence="100"
|
||||
id="menu_execution_template"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_execution_template"/>
|
||||
|
||||
<record model="ir.model.access" id="access_execution_template">
|
||||
<field name="model">trade_finance.execution_template</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_execution_template_admin">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ExecutionTemplateLine — no separate menu (child of ExecutionTemplate) -->
|
||||
<record model="ir.ui.view" id="execution_template_line_view_tree">
|
||||
<field name="model">trade_finance.execution_template_line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">execution_template_line_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_execution_template_line">
|
||||
<field name="model">trade_finance.execution_template_line</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_execution_template_line_admin">
|
||||
<field name="model">trade_finance.execution_template_line</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- RECEIVABLE CATEGORY -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="receivable_category_view_tree">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">receivable_category_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="receivable_category_view_form">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">receivable_category_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_receivable_category">
|
||||
<field name="name">Receivable Categories</field>
|
||||
<field name="res_model">trade_finance.receivable_category</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_receivable_category_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="receivable_category_view_tree"/>
|
||||
<field name="act_window" ref="act_receivable_category"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_receivable_category_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="receivable_category_view_form"/>
|
||||
<field name="act_window" ref="act_receivable_category"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Receivable Categories"
|
||||
sequence="110"
|
||||
id="menu_receivable_category"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_receivable_category"/>
|
||||
|
||||
<record model="ir.model.access" id="access_receivable_category">
|
||||
<field name="model">trade_finance.receivable_category</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_receivable_category_admin">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- PAYMENT CONDITION TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="payment_condition_type_view_tree">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">payment_condition_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="payment_condition_type_view_form">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">payment_condition_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_payment_condition_type">
|
||||
<field name="name">Payment Condition Types</field>
|
||||
<field name="res_model">trade_finance.payment_condition_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_payment_condition_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="payment_condition_type_view_tree"/>
|
||||
<field name="act_window" ref="act_payment_condition_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_payment_condition_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="payment_condition_type_view_form"/>
|
||||
<field name="act_window" ref="act_payment_condition_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Payment Condition Types"
|
||||
sequence="120"
|
||||
id="menu_payment_condition_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_payment_condition_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_payment_condition_type">
|
||||
<field name="model">trade_finance.payment_condition_type</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_payment_condition_type_admin">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FX FEEDER -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="fx_feeder_view_tree">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">fx_feeder_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="fx_feeder_view_form">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">fx_feeder_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_fx_feeder">
|
||||
<field name="name">FX Rate Feeders</field>
|
||||
<field name="res_model">trade_finance.fx_feeder</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_fx_feeder_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="fx_feeder_view_tree"/>
|
||||
<field name="act_window" ref="act_fx_feeder"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_fx_feeder_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="fx_feeder_view_form"/>
|
||||
<field name="act_window" ref="act_fx_feeder"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="FX Rate Feeders"
|
||||
sequence="130"
|
||||
id="menu_fx_feeder"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_fx_feeder"/>
|
||||
|
||||
<record model="ir.model.access" id="access_fx_feeder">
|
||||
<field name="model">trade_finance.fx_feeder</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_fx_feeder_admin">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- BLOCKING REASON -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="blocking_reason_view_tree">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">blocking_reason_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="blocking_reason_view_form">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">blocking_reason_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_blocking_reason">
|
||||
<field name="name">Blocking Reasons</field>
|
||||
<field name="res_model">trade_finance.blocking_reason</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_blocking_reason_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="blocking_reason_view_tree"/>
|
||||
<field name="act_window" ref="act_blocking_reason"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_blocking_reason_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="blocking_reason_view_form"/>
|
||||
<field name="act_window" ref="act_blocking_reason"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Blocking Reasons"
|
||||
sequence="140"
|
||||
id="menu_blocking_reason"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_blocking_reason"/>
|
||||
|
||||
<record model="ir.model.access" id="access_blocking_reason">
|
||||
<field name="model">trade_finance.blocking_reason</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_blocking_reason_admin">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- CHARGE TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="charge_type_view_tree">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">charge_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="charge_type_view_form">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">charge_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_charge_type">
|
||||
<field name="name">Charge Types</field>
|
||||
<field name="res_model">trade_finance.charge_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_charge_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="charge_type_view_tree"/>
|
||||
<field name="act_window" ref="act_charge_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_charge_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="charge_type_view_form"/>
|
||||
<field name="act_window" ref="act_charge_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Charge Types"
|
||||
sequence="150"
|
||||
id="menu_charge_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_charge_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_charge_type">
|
||||
<field name="model">trade_finance.charge_type</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_charge_type_admin">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<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>
|
||||
61
modules/trade_finance/template_execution.py
Normal file
61
modules/trade_finance/template_execution.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields, sequence_ordered
|
||||
|
||||
__all__ = ['TemplateSegment', 'ExecutionTemplate', 'ExecutionTemplateLine']
|
||||
|
||||
|
||||
class TemplateSegment(ModelSQL, ModelView):
|
||||
'Template Segment'
|
||||
__name__ = 'trade_finance.template_segment'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
from_place = fields.Many2One(
|
||||
'stock.location', 'From Place',
|
||||
help='Origin location / port of loading')
|
||||
to_place = fields.Many2One(
|
||||
'stock.location', 'To Place',
|
||||
help='Destination location / port of discharge')
|
||||
default_duration_days = fields.Integer(
|
||||
'Default Duration (days)',
|
||||
help='Default number of days for this segment')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ExecutionTemplate(ModelSQL, ModelView):
|
||||
'Execution Template'
|
||||
__name__ = 'trade_finance.execution_template'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
lines = fields.One2Many(
|
||||
'trade_finance.execution_template_line', 'template', 'Segments')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ExecutionTemplateLine(sequence_ordered(), ModelSQL, ModelView):
|
||||
'Execution Template Line'
|
||||
__name__ = 'trade_finance.execution_template_line'
|
||||
|
||||
template = fields.Many2One(
|
||||
'trade_finance.execution_template', 'Template',
|
||||
required=True, ondelete='CASCADE')
|
||||
segment = fields.Many2One(
|
||||
'trade_finance.template_segment', 'Segment',
|
||||
required=True, ondelete='RESTRICT')
|
||||
duration_days = fields.Integer(
|
||||
'Duration (days)',
|
||||
help='Overrides the segment default duration for this template')
|
||||
12
modules/trade_finance/tryton.cfg
Normal file
12
modules/trade_finance/tryton.cfg
Normal file
@@ -0,0 +1,12 @@
|
||||
[tryton]
|
||||
version=7.2.7
|
||||
depends:
|
||||
ir
|
||||
res
|
||||
stock
|
||||
price
|
||||
bank
|
||||
xml:
|
||||
reference.xml
|
||||
facility.xml
|
||||
constraint_type.xml
|
||||
50
modules/trade_finance/valuation.py
Normal file
50
modules/trade_finance/valuation.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['ValuationMethod', 'HaircutFormula', 'CollateralType']
|
||||
|
||||
|
||||
class ValuationMethod(ModelSQL, ModelView):
|
||||
'Valuation Method'
|
||||
__name__ = 'trade_finance.valuation_method'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class HaircutFormula(ModelSQL, ModelView):
|
||||
'Haircut Formula'
|
||||
__name__ = 'trade_finance.haircut_formula'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class CollateralType(ModelSQL, ModelView):
|
||||
'Collateral Type'
|
||||
__name__ = 'trade_finance.collateral_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
8
modules/trade_finance/view/blocking_reason_form.xml
Normal file
8
modules/trade_finance/view/blocking_reason_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/blocking_reason_tree.xml
Normal file
5
modules/trade_finance/view/blocking_reason_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/charge_type_form.xml
Normal file
10
modules/trade_finance/view/charge_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/charge_type_tree.xml
Normal file
5
modules/trade_finance/view/charge_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/collateral_type_form.xml
Normal file
8
modules/trade_finance/view/collateral_type_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/collateral_type_tree.xml
Normal file
5
modules/trade_finance/view/collateral_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/constraint_type_form.xml
Normal file
12
modules/trade_finance/view/constraint_type_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<group id="main" col="4" colspan="4">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<label name="view_name"/>
|
||||
<field name="view_name" colspan="3"/>
|
||||
<label name="value_field"/>
|
||||
<field name="value_field" colspan="3"/>
|
||||
<label name="label_field"/>
|
||||
<field name="label_field" colspan="3"/>
|
||||
</group>
|
||||
</form>
|
||||
3
modules/trade_finance/view/constraint_type_tree.xml
Normal file
3
modules/trade_finance/view/constraint_type_tree.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/evidence_type_form.xml
Normal file
10
modules/trade_finance/view/evidence_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/evidence_type_tree.xml
Normal file
5
modules/trade_finance/view/evidence_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/execution_template_form.xml
Normal file
12
modules/trade_finance/view/execution_template_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
<newline/>
|
||||
<field name="lines" colspan="4"/>
|
||||
</form>
|
||||
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="segment"/>
|
||||
<field name="duration_days"/>
|
||||
</tree>
|
||||
5
modules/trade_finance/view/execution_template_tree.xml
Normal file
5
modules/trade_finance/view/execution_template_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
7
modules/trade_finance/view/facility_cap_haircut_tree.xml
Normal file
7
modules/trade_finance/view/facility_cap_haircut_tree.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
6
modules/trade_finance/view/facility_cap_tree.xml
Normal file
6
modules/trade_finance/view/facility_cap_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/facility_constraint_form.xml
Normal file
10
modules/trade_finance/view/facility_constraint_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="constraint_type"/>
|
||||
<field name="constraint_type" colspan="3"/>
|
||||
<label name="date_from"/>
|
||||
<field name="date_from"/>
|
||||
<label name="date_to"/>
|
||||
<field name="date_to"/>
|
||||
<label name="is_exclusion"/>
|
||||
<field name="is_exclusion"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/facility_constraint_tree.xml
Normal file
6
modules/trade_finance/view/facility_constraint_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="constraint_type"/>
|
||||
<field name="is_exclusion"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
6
modules/trade_finance/view/facility_covenant_tree.xml
Normal file
6
modules/trade_finance/view/facility_covenant_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="ratio_type"/>
|
||||
<field name="threshold"/>
|
||||
<field name="currency"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/facility_currency_tree.xml
Normal file
8
modules/trade_finance/view/facility_currency_tree.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="currency"/>
|
||||
<field name="valuation_method"/>
|
||||
<field name="fx_haircut_formula"/>
|
||||
<field name="fx_feeder"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
50
modules/trade_finance/view/facility_form.xml
Normal file
50
modules/trade_finance/view/facility_form.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<form col="6">
|
||||
<group id="header" col="6" colspan="6">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<label name="status"/>
|
||||
<field name="status" widget="selection"/>
|
||||
<label name="tfe"/>
|
||||
<field name="tfe" colspan="3"/>
|
||||
<label name="commitment_status"/>
|
||||
<field name="commitment_status"/>
|
||||
<label name="currency"/>
|
||||
<field name="currency"/>
|
||||
<label name="fx_feeder"/>
|
||||
<field name="fx_feeder"/>
|
||||
<label name="fx_haircut_pct"/>
|
||||
<field name="fx_haircut_pct"/>
|
||||
<newline/>
|
||||
<label name="date_from"/>
|
||||
<field name="date_from"/>
|
||||
<label name="date_to"/>
|
||||
<field name="date_to"/>
|
||||
<label name="is_tpa"/>
|
||||
<field name="is_tpa"/>
|
||||
<label name="broker"/>
|
||||
<field name="broker"/>
|
||||
<label name="broker_account"/>
|
||||
<field name="broker_account" colspan="3"/>
|
||||
<newline/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="5"/>
|
||||
</group>
|
||||
<notebook colspan="6">
|
||||
<page string="Limits" id="limits">
|
||||
<field name="limits" colspan="6"
|
||||
domain="[('parent', '=', None)]"/>
|
||||
</page>
|
||||
<page string="Currencies" id="currencies">
|
||||
<field name="currencies" colspan="6"/>
|
||||
</page>
|
||||
<page string="Constraints" id="constraints">
|
||||
<field name="constraints" colspan="6"/>
|
||||
</page>
|
||||
<page string="Caps" id="caps">
|
||||
<field name="caps" colspan="6"/>
|
||||
</page>
|
||||
<page string="Covenants" id="covenants">
|
||||
<field name="covenants" colspan="6"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="bank_account"/>
|
||||
<field name="currency"/>
|
||||
<field name="is_default"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
11
modules/trade_finance/view/facility_limit_cost_tree.xml
Normal file
11
modules/trade_finance/view/facility_limit_cost_tree.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<tree>
|
||||
<field name="cost_type"/>
|
||||
<field name="spread"/>
|
||||
<field name="index"/>
|
||||
<field name="index_term"/>
|
||||
<field name="interest_formula"/>
|
||||
<field name="flat_amount"/>
|
||||
<field name="flat_currency"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="variation_type"/>
|
||||
<field name="variation_value"/>
|
||||
<field name="variation_currency"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="currency"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="fx_feeder"/>
|
||||
<field name="valuation_method"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
44
modules/trade_finance/view/facility_limit_form.xml
Normal file
44
modules/trade_finance/view/facility_limit_form.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<form col="4">
|
||||
<group id="header" col="4" colspan="4">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<label name="alternative_name"/>
|
||||
<field name="alternative_name" colspan="3"/>
|
||||
<label name="financing_type"/>
|
||||
<field name="financing_type" colspan="3"/>
|
||||
<label name="amount"/>
|
||||
<field name="amount"/>
|
||||
<label name="tenor"/>
|
||||
<field name="tenor"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<newline/>
|
||||
<label name="parent"/>
|
||||
<field name="parent" colspan="3"/>
|
||||
<label name="facility"/>
|
||||
<field name="facility" colspan="3"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Sub-Limits" id="children">
|
||||
<field name="children" colspan="4"/>
|
||||
</page>
|
||||
<page string="Haircuts" id="haircuts">
|
||||
<field name="haircuts" colspan="4"/>
|
||||
</page>
|
||||
<page string="Currencies" id="currencies">
|
||||
<field name="currencies" colspan="4"/>
|
||||
</page>
|
||||
<page string="Costs" id="costs">
|
||||
<field name="costs" colspan="4"/>
|
||||
</page>
|
||||
<page string="Operational Statuses" id="op_statuses">
|
||||
<field name="op_statuses" colspan="4"/>
|
||||
</page>
|
||||
<page string="Bank Accounts" id="bank_accounts">
|
||||
<field name="bank_accounts" colspan="4"/>
|
||||
</page>
|
||||
<page string="Constraints" id="limit_constraints">
|
||||
<field name="constraints" colspan="4"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,4 @@
|
||||
<tree>
|
||||
<field name="operational_status"/>
|
||||
<field name="evidence_type"/>
|
||||
</tree>
|
||||
7
modules/trade_finance/view/facility_limit_tree.xml
Normal file
7
modules/trade_finance/view/facility_limit_tree.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<tree expand="1">
|
||||
<field name="name"/>
|
||||
<field name="parent"/>
|
||||
<field name="financing_type"/>
|
||||
<field name="amount"/>
|
||||
<field name="tenor"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/facility_status_form.xml
Normal file
10
modules/trade_finance/view/facility_status_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<group id="main" col="4" colspan="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
</group>
|
||||
</form>
|
||||
5
modules/trade_finance/view/facility_status_tree.xml
Normal file
5
modules/trade_finance/view/facility_status_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
9
modules/trade_finance/view/facility_tree.xml
Normal file
9
modules/trade_finance/view/facility_tree.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="tfe"/>
|
||||
<field name="currency"/>
|
||||
<field name="status"/>
|
||||
<field name="commitment_status"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/financing_type_form.xml
Normal file
10
modules/trade_finance/view/financing_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/financing_type_tree.xml
Normal file
6
modules/trade_finance/view/financing_type_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/fx_feeder_form.xml
Normal file
10
modules/trade_finance/view/fx_feeder_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="source_description"/>
|
||||
<field name="source_description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/fx_feeder_tree.xml
Normal file
5
modules/trade_finance/view/fx_feeder_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/haircut_formula_form.xml
Normal file
10
modules/trade_finance/view/haircut_formula_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/haircut_formula_tree.xml
Normal file
5
modules/trade_finance/view/haircut_formula_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/index_term_form.xml
Normal file
10
modules/trade_finance/view/index_term_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="days"/>
|
||||
<field name="days"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/index_term_tree.xml
Normal file
6
modules/trade_finance/view/index_term_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="days"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/interest_formula_form.xml
Normal file
8
modules/trade_finance/view/interest_formula_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/interest_formula_tree.xml
Normal file
5
modules/trade_finance/view/interest_formula_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/market_index_form.xml
Normal file
10
modules/trade_finance/view/market_index_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<newline/>
|
||||
<field name="rates" colspan="4"/>
|
||||
</form>
|
||||
8
modules/trade_finance/view/market_index_rate_form.xml
Normal file
8
modules/trade_finance/view/market_index_rate_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="index"/>
|
||||
<field name="index"/>
|
||||
<label name="rate_date"/>
|
||||
<field name="rate_date"/>
|
||||
<label name="rate"/>
|
||||
<field name="rate"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/market_index_rate_tree.xml
Normal file
5
modules/trade_finance/view/market_index_rate_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="index"/>
|
||||
<field name="rate_date"/>
|
||||
<field name="rate"/>
|
||||
</tree>
|
||||
5
modules/trade_finance/view/market_index_tree.xml
Normal file
5
modules/trade_finance/view/market_index_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/operational_status_form.xml
Normal file
12
modules/trade_finance/view/operational_status_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="financing_type"/>
|
||||
<field name="financing_type"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
7
modules/trade_finance/view/operational_status_tree.xml
Normal file
7
modules/trade_finance/view/operational_status_tree.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="financing_type"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/payment_condition_type_form.xml
Normal file
10
modules/trade_finance/view/payment_condition_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="remaining_risk_pct"/>
|
||||
<field name="remaining_risk_pct"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="remaining_risk_pct"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/receivable_category_form.xml
Normal file
10
modules/trade_finance/view/receivable_category_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/receivable_category_tree.xml
Normal file
5
modules/trade_finance/view/receivable_category_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
14
modules/trade_finance/view/template_segment_form.xml
Normal file
14
modules/trade_finance/view/template_segment_form.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="from_place"/>
|
||||
<field name="from_place"/>
|
||||
<label name="to_place"/>
|
||||
<field name="to_place"/>
|
||||
<label name="default_duration_days"/>
|
||||
<field name="default_duration_days"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
8
modules/trade_finance/view/template_segment_tree.xml
Normal file
8
modules/trade_finance/view/template_segment_tree.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="from_place"/>
|
||||
<field name="to_place"/>
|
||||
<field name="default_duration_days"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/valuation_method_form.xml
Normal file
10
modules/trade_finance/view/valuation_method_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/valuation_method_tree.xml
Normal file
5
modules/trade_finance/view/valuation_method_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user