Merge dev

This commit is contained in:
2026-06-02 14:53:07 +02:00
parent 2fecf60295
commit f39675543a
88 changed files with 7066 additions and 380 deletions

View File

@@ -7,10 +7,10 @@ from . import (
account, account,
configuration, configuration,
purchase, purchase,
sale, sale,
global_reporting, global_reporting,
ctrm_reporting, ctrm_reporting,
stock, stock,
derivative, derivative,
lot, lot,
pricing, pricing,
@@ -36,6 +36,8 @@ from . import (
backtoback, backtoback,
service, service,
invoice, invoice,
process_documentation,
routes,
) )
def register(): def register():
@@ -57,43 +59,44 @@ def register():
lc.LCMT700, lc.LCMT700,
lc.LCMessage, lc.LCMessage,
lc.CreateLCStart, lc.CreateLCStart,
global_reporting.GRConfiguration, global_reporting.GRConfiguration,
ctrm_reporting.CTRMPhysicalPosition, ctrm_reporting.CTRMPhysicalPosition,
ctrm_reporting.CTRMPhysicalPositionContext, ctrm_reporting.CTRMPhysicalPositionContext,
ctrm_reporting.CTRMFinancialPosition, ctrm_reporting.CTRMFinancialPosition,
ctrm_reporting.CTRMFinancialPositionContext, ctrm_reporting.CTRMFinancialPositionContext,
ctrm_reporting.CTRMNetPosition, ctrm_reporting.CTRMNetPosition,
ctrm_reporting.CTRMNetPositionContext, ctrm_reporting.CTRMNetPositionContext,
ctrm_reporting.CTRMRealizedPnl, ctrm_reporting.CTRMRealizedPnl,
ctrm_reporting.CTRMRealizedPnlContext, ctrm_reporting.CTRMRealizedPnlContext,
ctrm_reporting.CTRMMtmPnl, ctrm_reporting.CTRMMtmPnl,
ctrm_reporting.CTRMMtmPnlContext, ctrm_reporting.CTRMMtmPnlContext,
ctrm_reporting.CTRMPnlExplain, ctrm_reporting.CTRMPnlExplain,
ctrm_reporting.CTRMPnlExplainContext, ctrm_reporting.CTRMPnlExplainContext,
ctrm_reporting.CTRMPnlDimension, ctrm_reporting.CTRMPnlDimension,
ctrm_reporting.CTRMPnlDimensionContext, ctrm_reporting.CTRMPnlDimensionContext,
ctrm_reporting.CTRMCreditRisk, ctrm_reporting.CTRMCreditRisk,
ctrm_reporting.CTRMCreditRiskContext, ctrm_reporting.CTRMCreditRiskContext,
ctrm_reporting.CTRMShippingLogistics, ctrm_reporting.CTRMShippingLogistics,
ctrm_reporting.CTRMShippingLogisticsContext, ctrm_reporting.CTRMShippingLogisticsContext,
ctrm_reporting.CTRMInventory, ctrm_reporting.CTRMInventory,
ctrm_reporting.CTRMInventoryContext, ctrm_reporting.CTRMInventoryContext,
ctrm_reporting.CTRMContractPerformance, ctrm_reporting.CTRMContractPerformance,
ctrm_reporting.CTRMContractPerformanceContext, ctrm_reporting.CTRMContractPerformanceContext,
ctrm_reporting.CTRMScheduling, ctrm_reporting.CTRMScheduling,
ctrm_reporting.CTRMSchedulingContext, ctrm_reporting.CTRMSchedulingContext,
ctrm_reporting.CTRMAccruals, ctrm_reporting.CTRMAccruals,
ctrm_reporting.CTRMAccrualsContext, ctrm_reporting.CTRMAccrualsContext,
ctrm_reporting.CTRMSettlements, ctrm_reporting.CTRMSettlements,
ctrm_reporting.CTRMSettlementsContext, ctrm_reporting.CTRMSettlementsContext,
ctrm_reporting.CTRMCashFlow, ctrm_reporting.CTRMCashFlow,
ctrm_reporting.CTRMCashFlowContext, ctrm_reporting.CTRMCashFlowContext,
ctrm_reporting.CTRMGLReconciliation, ctrm_reporting.CTRMGLReconciliation,
ctrm_reporting.CTRMGLReconciliationContext, ctrm_reporting.CTRMGLReconciliationContext,
configuration.Configuration, configuration.Configuration,
pricing.ImportPricesStart, process_documentation.ProcessDocumentation,
pricing.ImportPricesResult, pricing.ImportPricesStart,
module='purchase_trade', type_='model') pricing.ImportPricesResult,
module='purchase_trade', type_='model')
Pool.register( Pool.register(
incoming.ImportSwift, incoming.ImportSwift,
incoming.PrepareDocuments, incoming.PrepareDocuments,
@@ -117,20 +120,20 @@ def register():
dashboard.Demos, dashboard.Demos,
party.Party, party.Party,
party.PartyExecution, party.PartyExecution,
party.PartyExecutionSla, party.PartyExecutionSla,
party.PartyExecutionPlace, party.PartyExecutionPlace,
payment_term.PaymentTerm, payment_term.PaymentTerm,
payment_term.PaymentTermLine, payment_term.PaymentTermLine,
stock.CharterRateType, stock.CharterRateType,
stock.CharterConditionRate, stock.CharterConditionRate,
stock.CharterCondition, stock.CharterCondition,
stock.CharterParty, stock.CharterParty,
purchase.ContractClause, purchase.ContractClause,
purchase.ContractTemplate, purchase.ContractTemplate,
purchase.ContractTemplateLine, purchase.ContractTemplateLine,
purchase.ContractClauseSelection, purchase.ContractClauseSelection,
purchase.Purchase, purchase.Purchase,
purchase.Line, purchase.Line,
purchase.Estimated, purchase.Estimated,
purchase.Component, purchase.Component,
purchase.Pricing, purchase.Pricing,
@@ -145,10 +148,10 @@ def register():
open_position.OpenPosition, open_position.OpenPosition,
open_position.OpenPositionReport, open_position.OpenPositionReport,
open_position.OpenPositionContext, open_position.OpenPositionContext,
optional.OptionalScenario, optional.OptionalScenario,
fee.Fee, fee.Fee,
fee.FeeRule, fee.FeeRule,
fee.FeeLots, fee.FeeLots,
purchase.FeeLots, purchase.FeeLots,
valuation.Valuation, valuation.Valuation,
valuation.ValuationLine, valuation.ValuationLine,
@@ -218,10 +221,10 @@ def register():
purchase.ContractDocumentType, purchase.ContractDocumentType,
purchase.DocTemplate, purchase.DocTemplate,
purchase.DocTypeTemplate, purchase.DocTypeTemplate,
purchase.PurchaseStrategy, purchase.PurchaseStrategy,
purchase.PriceComposition, purchase.PriceComposition,
purchase.PremiumComposition, purchase.PremiumComposition,
purchase.QualityAnalysis, purchase.QualityAnalysis,
purchase.Assay, purchase.Assay,
purchase.AssayLine, purchase.AssayLine,
purchase.AssayElement, purchase.AssayElement,
@@ -239,13 +242,13 @@ def register():
Pool.register( Pool.register(
account.PhysicalTradeIFRS, account.PhysicalTradeIFRS,
module='purchase_trade', type_='model') module='purchase_trade', type_='model')
Pool.register( Pool.register(
configuration.AccountConfiguration, configuration.AccountConfiguration,
configuration.AccountConfigurationDefaultAccount, configuration.AccountConfigurationDefaultAccount,
invoice.InvoiceLineLotWeight, invoice.InvoiceLineLotWeight,
invoice.InvoicePaddingReport, invoice.InvoicePaddingReport,
invoice.InvoicePaddingContext, invoice.InvoicePaddingContext,
module='purchase_trade', type_='model') module='purchase_trade', type_='model')
Pool.register( Pool.register(
invoice.Invoice, invoice.Invoice,
invoice.InvoiceLine, invoice.InvoiceLine,
@@ -270,12 +273,12 @@ def register():
purchase.PurchaseAssociation, purchase.PurchaseAssociation,
purchase.PurchaseCrop, purchase.PurchaseCrop,
module='lot', type_='model') module='lot', type_='model')
Pool.register( Pool.register(
sale.ContractClauseSelection, sale.ContractClauseSelection,
sale.CharterCondition, sale.CharterCondition,
sale.Sale, sale.Sale,
sale.SaleLine, sale.SaleLine,
sale.SaleCreatePurchaseInput, sale.SaleCreatePurchaseInput,
sale.Derivative, sale.Derivative,
sale.Valuation, sale.Valuation,
sale.ValuationLine, sale.ValuationLine,
@@ -294,10 +297,10 @@ def register():
sale.SaleStrategy, sale.SaleStrategy,
sale.OpenPosition, sale.OpenPosition,
sale.Backtoback, sale.Backtoback,
sale.AnalyticDimensionAssignment, sale.AnalyticDimensionAssignment,
sale.PriceComposition, sale.PriceComposition,
sale.PremiumComposition, sale.PremiumComposition,
module='sale', type_='model') module='sale', type_='model')
Pool.register( Pool.register(
lot.LotShipping, lot.LotShipping,
lot.LotMatching, lot.LotMatching,

View File

@@ -2,6 +2,7 @@
# this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
from trytond.model import fields from trytond.model import fields
from trytond.pool import Pool, PoolMeta from trytond.pool import Pool, PoolMeta
from trytond.exceptions import UserError
from trytond.pyson import Bool, Eval, Id from trytond.pyson import Bool, Eval, Id
from trytond.model import (ModelSQL, ModelView) from trytond.model import (ModelSQL, ModelView)
from trytond.exceptions import UserError from trytond.exceptions import UserError
@@ -68,12 +69,19 @@ class ImportPricesStart(ModelView):
"Import Prices" "Import Prices"
__name__ = 'purchase_trade.import_prices.start' __name__ = 'purchase_trade.import_prices.start'
file_structure = fields.Selection([
('historical', "Historical Prices"),
('forward', "Forward Prices"),
], "Excel file structure", required=True)
file_ = fields.Binary('Excel file', required=True, filename='filename') file_ = fields.Binary('Excel file', required=True, filename='filename')
filename = fields.Char('Filename') filename = fields.Char('Filename')
create_missing_price_index = fields.Boolean( create_missing_price_index = fields.Boolean(
"Create price index if missing") "Create price index if missing")
overwrite_existing_price = fields.Boolean("Overwrite existing price") overwrite_existing_price = fields.Boolean("Overwrite existing price")
@staticmethod
def default_file_structure():
return 'historical'
class ImportPricesResult(ModelView): class ImportPricesResult(ModelView):
"Import Prices Result" "Import Prices Result"
@@ -111,7 +119,8 @@ class ImportPrices(Wizard):
} }
def transition_import_(self): def transition_import_(self):
rows = self._read_xlsx(self.start.file_) rows = self._read_xlsx(
self.start.file_, file_structure=self.start.file_structure)
stats = self._import_rows( stats = self._import_rows(
rows, rows,
create_missing_price_index=( create_missing_price_index=(
@@ -142,6 +151,7 @@ class ImportPrices(Wizard):
} }
for row_number, row in enumerate(rows, start=2): for row_number, row in enumerate(rows, start=2):
row_number = row.get('_row_number', index)
price_index = (row.get('price_index') or '').strip() price_index = (row.get('price_index') or '').strip()
try: try:
price_date = cls._as_date(row.get('price_date')) price_date = cls._as_date(row.get('price_date'))
@@ -217,7 +227,7 @@ class ImportPrices(Wizard):
} }
references = [ references = [
('price_type', 'price.fixtype', [('name', '=', 'Market price')]), ('price_type', 'price.fixtype', [('name', '=', 'Market price')]),
('price_currency', 'currency.currency', [('code', '=', 'USD')]), ('price_currency', 'currency.currency', [('name', '=', 'USD')]),
('price_calendar', 'price.calendar', [('name', '=', 'Argus EU')]), ('price_calendar', 'price.calendar', [('name', '=', 'Argus EU')]),
('price_unit', 'product.uom', [('name', '=', 'Mt')]), ('price_unit', 'product.uom', [('name', '=', 'Mt')]),
] ]
@@ -340,7 +350,7 @@ class ImportPrices(Wizard):
return ', '.join(parts) or 'price imported' return ', '.join(parts) or 'price imported'
@classmethod @classmethod
def _read_xlsx(cls, data): def _read_xlsx(cls, data, file_structure='historical'):
try: try:
with zipfile.ZipFile(BytesIO(data)) as workbook: with zipfile.ZipFile(BytesIO(data)) as workbook:
shared_strings = cls._read_shared_strings(workbook) shared_strings = cls._read_shared_strings(workbook)
@@ -354,6 +364,12 @@ class ImportPrices(Wizard):
if not rows: if not rows:
return [] return []
if file_structure == 'forward':
return cls._read_forward_price_rows(rows, shared_strings, ns)
return cls._read_historical_price_rows(rows, shared_strings, ns)
@classmethod
def _read_historical_price_rows(cls, rows, shared_strings, ns):
headers = {} headers = {}
for cell in rows[0].findall('s:c', ns): for cell in rows[0].findall('s:c', ns):
index = cls._cell_column_index(cell.get('r')) index = cls._cell_column_index(cell.get('r'))
@@ -380,6 +396,59 @@ class ImportPrices(Wizard):
result.append(values) result.append(values)
return result return result
@classmethod
def _read_forward_price_rows(cls, rows, shared_strings, ns):
headers = {}
month_terms = {}
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 index == 1 and normalized == 'priceindex':
headers[index] = 'price_index'
elif index == 2 and normalized == 'pricedate':
headers[index] = 'price_date'
elif index and index >= 3 and header not in (None, ''):
month_terms[index] = cls._as_month_term(header)
missing = []
if headers.get(1) != 'price_index':
missing.append('price_index')
if headers.get(2) != 'price_date':
missing.append('price_date')
if missing:
raise UserError(
"Missing columns in Excel file: %s" % ', '.join(missing))
if not month_terms:
raise UserError(
"Missing month term columns in Excel file.")
result = []
for excel_row_number, sheet_row in enumerate(rows[1:], start=2):
row_values = {}
for cell in sheet_row.findall('s:c', ns):
index = cls._cell_column_index(cell.get('r'))
row_values[index] = cls._cell_value(cell, shared_strings)
price_index = (row_values.get(1) or '').strip()
price_date = row_values.get(2)
if not price_index and price_date in (None, ''):
continue
for index, month_term in sorted(month_terms.items()):
price_value = row_values.get(index)
if price_value in (None, ''):
continue
result.append({
'_row_number': excel_row_number,
'price_index': (
'%s %s' % (price_index, month_term)
if price_index else ''),
'price_date': price_date,
'price_value': price_value,
})
return result
@staticmethod @staticmethod
def _read_shared_strings(workbook): def _read_shared_strings(workbook):
try: try:
@@ -472,6 +541,18 @@ class ImportPrices(Wizard):
except ValueError: except ValueError:
pass pass
raise UserError("Invalid price_date: %s" % text) raise UserError("Invalid price_date: %s" % text)
@classmethod
def _as_month_term(cls, value):
if isinstance(value, datetime.date):
return value.strftime('%Y-%m')
text = str(value).strip()
match = re.match(r'^(20\d{2})[-_/\. ](0[1-9]|1[0-2])$', text)
if match:
return '%s-%s' % (match.group(1), match.group(2))
if re.match(r'^\d+(\.\d+)?$', text):
return cls._as_date(text).strftime('%Y-%m')
raise UserError("Invalid month term: %s" % text)
class MtmScenario(ModelSQL, ModelView): class MtmScenario(ModelSQL, ModelView):
"MtM Scenario" "MtM Scenario"
__name__ = 'mtm.scenario' __name__ = 'mtm.scenario'
@@ -511,13 +592,13 @@ class MtmStrategy(ModelSQL, ModelView):
Currency = pool.get('currency.currency') Currency = pool.get('currency.currency')
total = Decimal(0) total = Decimal(0)
scenario = self.scenario scenario = self.scenario
dt = scenario.valuation_date dt = scenario.valuation_date
for comp in self.components: for comp in self.components:
value = Decimal(0) value = Decimal(0)
if comp.price_source_type == 'curve' and comp.price_index: if comp.price_source_type == 'curve' and comp.price_index:
value = Decimal( value = Decimal(
comp.price_index.get_price( comp.price_index.get_price(
dt, dt,
@@ -526,22 +607,22 @@ class MtmStrategy(ModelSQL, ModelView):
last=scenario.use_last_price last=scenario.use_last_price
) )
) )
elif comp.price_source_type == 'matrix' and comp.price_matrix: elif comp.price_source_type == 'matrix' and comp.price_matrix:
value = self._get_matrix_price(comp, line, dt) value = self._get_matrix_price(comp, line, dt)
elif comp.price_source_type == 'fixed': elif comp.price_source_type == 'fixed':
value = Decimal(comp.get_price( value = Decimal(comp.get_price(
dt, dt,
line.unit, line.unit,
self.currency, self.currency,
last=scenario.use_last_price last=scenario.use_last_price
)) ))
if comp.ratio: if comp.ratio:
value *= Decimal(comp.ratio) / Decimal(100) value *= Decimal(comp.ratio) / Decimal(100)
total += value * qty total += value * qty
return Decimal(str(total)).quantize(Decimal("0.01")) return Decimal(str(total)).quantize(Decimal("0.01"))
@@ -699,198 +780,198 @@ class Component(ModelSQL, ModelView):
required=False, ondelete='CASCADE' required=False, ondelete='CASCADE'
) )
price_source_type = fields.Selection([ price_source_type = fields.Selection([
('curve', 'Curve'), ('curve', 'Curve'),
('matrix', 'Matrix'), ('matrix', 'Matrix'),
('fixed', 'Fixed'), ('fixed', 'Fixed'),
], "Price Source", required=True) ], "Price Source", required=True)
fix_type = fields.Many2One('price.fixtype',"Fixation type") fix_type = fields.Many2One('price.fixtype',"Fixation type")
ratio = fields.Numeric("%",digits=(16,7)) ratio = fields.Numeric("%",digits=(16,7))
price_index = fields.Many2One( price_index = fields.Many2One(
'price.price', "Curve", 'price.price', "Curve",
states={ states={
'readonly': Eval('price_source_type') != 'curve', 'readonly': Eval('price_source_type') != 'curve',
'required': Eval('price_source_type') == 'curve', 'required': Eval('price_source_type') == 'curve',
}, },
depends=['price_source_type']) depends=['price_source_type'])
price_matrix = fields.Many2One( price_matrix = fields.Many2One(
'price.matrix', "Price Matrix", 'price.matrix', "Price Matrix",
states={ states={
'readonly': Eval('price_source_type') != 'matrix', 'readonly': Eval('price_source_type') != 'matrix',
'required': Eval('price_source_type') == 'matrix', 'required': Eval('price_source_type') == 'matrix',
}, },
depends=['price_source_type']) depends=['price_source_type'])
fixed_price = fields.Numeric( fixed_price = fields.Numeric(
"Fixed Price", digits=(16, 6), "Fixed Price", digits=(16, 6),
states={ states={
'readonly': Eval('price_source_type') != 'fixed', 'readonly': Eval('price_source_type') != 'fixed',
'required': Eval('price_source_type') == 'fixed', 'required': Eval('price_source_type') == 'fixed',
}, },
depends=['price_source_type']) depends=['price_source_type'])
fixed_currency = fields.Many2One('currency.currency', "Fixed Curr.") fixed_currency = fields.Many2One('currency.currency', "Fixed Curr.")
currency = fields.Function( currency = fields.Function(
fields.Many2One( fields.Many2One(
'currency.currency', "Curr.", 'currency.currency', "Curr.",
states={ states={
'required': Eval('price_source_type') == 'fixed', 'required': Eval('price_source_type') == 'fixed',
}, },
depends=['price_source_type']), depends=['price_source_type']),
'get_cur', setter='set_cur') 'get_cur', setter='set_cur')
auto = fields.Boolean("Auto") auto = fields.Boolean("Auto")
fallback = fields.Boolean("Fallback") fallback = fields.Boolean("Fallback")
calendar = fields.Many2One('price.calendar',"Calendar") calendar = fields.Many2One('price.calendar',"Calendar")
nbdays = fields.Function(fields.Integer("Nb days"),'get_nbdays') nbdays = fields.Function(fields.Integer("Nb days"),'get_nbdays')
triggers = fields.One2Many('pricing.trigger','component',"Period rules") triggers = fields.One2Many('pricing.trigger','component',"Period rules")
pricing_date = fields.Date("Pricing date max") pricing_date = fields.Date("Pricing date max")
def get_rec_name(self, name=None): def get_rec_name(self, name=None):
if self.price_index: if self.price_index:
return '[' + self.fix_type.name + '] ' + self.price_index.price_index return '[' + self.fix_type.name + '] ' + self.price_index.price_index
if self.price_matrix: if self.price_matrix:
return '[' + self.fix_type.name + '] ' + self.price_matrix.name return '[' + self.fix_type.name + '] ' + self.price_matrix.name
if self.price_source_type == 'fixed': if self.price_source_type == 'fixed':
return '[' + self.fix_type.name + '] Fixed' return '[' + self.fix_type.name + '] Fixed'
else: else:
return '[' + self.fix_type.name + '] ' return '[' + self.fix_type.name + '] '
def get_cur(self, name=None): def get_cur(self, name=None):
if self.price_index: if self.price_index:
PI = Pool().get('price.price') PI = Pool().get('price.price')
pi = PI(self.price_index) pi = PI(self.price_index)
return pi.price_currency return pi.price_currency
if self.price_matrix: if self.price_matrix:
return self.price_matrix.currency return self.price_matrix.currency
if self.price_source_type == 'fixed': if self.price_source_type == 'fixed':
return self.fixed_currency return self.fixed_currency
@fields.depends( @fields.depends(
'price_source_type', 'price_index', 'price_matrix', 'price_source_type', 'price_index', 'price_matrix',
'fixed_currency') 'fixed_currency')
def on_change_with_currency(self): def on_change_with_currency(self):
if self.price_source_type == 'curve' and self.price_index: if self.price_source_type == 'curve' and self.price_index:
return self.get_cur() return self.get_cur()
if self.price_source_type == 'matrix' and self.price_matrix: if self.price_source_type == 'matrix' and self.price_matrix:
return self.get_cur() return self.get_cur()
if self.price_source_type == 'fixed': if self.price_source_type == 'fixed':
return self.fixed_currency return self.fixed_currency
@classmethod @classmethod
def set_cur(cls, components, name, value): def set_cur(cls, components, name, value):
cls.write(components, {'fixed_currency': value}) cls.write(components, {'fixed_currency': value})
def get_calendar(self): def get_calendar(self):
if self.calendar: if self.calendar:
return self.calendar return self.calendar
if self.price_matrix: if self.price_matrix:
return self.price_matrix.calendar return self.price_matrix.calendar
@staticmethod @staticmethod
def _record_id(record): def _record_id(record):
return getattr(record, 'id', record) return getattr(record, 'id', record)
@classmethod @classmethod
def _matches_optional_field(cls, matrix_line, field_name, target): def _matches_optional_field(cls, matrix_line, field_name, target):
value = getattr(matrix_line, field_name, None) value = getattr(matrix_line, field_name, None)
if not value: if not value:
return True, 0 return True, 0
if target and cls._record_id(value) == cls._record_id(target): if target and cls._record_id(value) == cls._record_id(target):
return True, 1 return True, 1
return False, 0 return False, 0
def _matrix_valid_on(self, price_date): def _matrix_valid_on(self, price_date):
if not self.price_matrix: if not self.price_matrix:
return False return False
if hasattr(price_date, 'date'): if hasattr(price_date, 'date'):
price_date = price_date.date() price_date = price_date.date()
valid_from = getattr(self.price_matrix, 'valid_from', None) valid_from = getattr(self.price_matrix, 'valid_from', None)
valid_to = getattr(self.price_matrix, 'valid_to', None) valid_to = getattr(self.price_matrix, 'valid_to', None)
if valid_from and price_date < valid_from: if valid_from and price_date < valid_from:
return False return False
if valid_to and price_date > valid_to: if valid_to and price_date > valid_to:
return False return False
return True return True
def _get_matrix_owner_values(self): def _get_matrix_owner_values(self):
line = getattr(self, 'line', None) or getattr(self, 'sale_line', None) line = getattr(self, 'line', None) or getattr(self, 'sale_line', None)
document = getattr(line, 'purchase', None) or getattr(line, 'sale', None) document = getattr(line, 'purchase', None) or getattr(line, 'sale', None)
return { return {
'origin': getattr(document, 'from_location', None), 'origin': getattr(document, 'from_location', None),
'destination': getattr(document, 'to_location', None), 'destination': getattr(document, 'to_location', None),
'product': getattr(line, 'product', None), 'product': getattr(line, 'product', None),
'quality': getattr(line, 'quality', None), 'quality': getattr(line, 'quality', None),
} }
def _get_matrix_line(self): def _get_matrix_line(self):
MatrixLine = Pool().get('price.matrix.line') MatrixLine = Pool().get('price.matrix.line')
lines = MatrixLine.search([('matrix', '=', self.price_matrix.id)]) lines = MatrixLine.search([('matrix', '=', self.price_matrix.id)])
owner_values = self._get_matrix_owner_values() owner_values = self._get_matrix_owner_values()
best_line = None best_line = None
best_score = -1 best_score = -1
for line in lines: for line in lines:
score = 0 score = 0
for field_name, target in owner_values.items(): for field_name, target in owner_values.items():
matches, field_score = self._matches_optional_field( matches, field_score = self._matches_optional_field(
line, field_name, target) line, field_name, target)
if not matches: if not matches:
break break
score += field_score score += field_score
else: else:
if score > best_score: if score > best_score:
best_line = line best_line = line
best_score = score best_score = score
return best_line return best_line
def _convert_matrix_price(self, price, unit, currency): def _convert_matrix_price(self, price, unit, currency):
price_qt = Decimal(str(price or 0)) price_qt = Decimal(str(price or 0))
matrix_unit = getattr(self.price_matrix, 'unit', None) matrix_unit = getattr(self.price_matrix, 'unit', None)
matrix_currency = getattr(self.price_matrix, 'currency', None) matrix_currency = getattr(self.price_matrix, 'currency', None)
if matrix_unit and unit: if matrix_unit and unit:
Uom = Pool().get('product.uom') Uom = Pool().get('product.uom')
price_qt *= Decimal(str( price_qt *= Decimal(str(
Uom.compute_qty(unit, float(1), matrix_unit))) Uom.compute_qty(unit, float(1), matrix_unit)))
if matrix_currency and currency and currency != matrix_currency: if matrix_currency and currency and currency != matrix_currency:
Currency = Pool().get('currency.currency') Currency = Pool().get('currency.currency')
rates = Currency._get_rate([matrix_currency]) rates = Currency._get_rate([matrix_currency])
rate = rates.get(matrix_currency.id) rate = rates.get(matrix_currency.id)
if rate: if rate:
price_qt *= Decimal(str(rate)) price_qt *= Decimal(str(rate))
return round(price_qt, 4) return round(price_qt, 4)
def _convert_fixed_price(self, price, currency): def _convert_fixed_price(self, price, currency):
price_qt = Decimal(str(price or 0)) price_qt = Decimal(str(price or 0))
price_currency = getattr(self, 'fixed_currency', None) price_currency = getattr(self, 'fixed_currency', None)
if price_currency and currency and currency != price_currency: if price_currency and currency and currency != price_currency:
Currency = Pool().get('currency.currency') Currency = Pool().get('currency.currency')
rates = Currency._get_rate([price_currency]) rates = Currency._get_rate([price_currency])
rate = rates.get(price_currency.id) rate = rates.get(price_currency.id)
if rate: if rate:
price_qt *= Decimal(str(rate)) price_qt *= Decimal(str(rate))
return round(price_qt, 4) return round(price_qt, 4)
def get_price(self, price_date, unit, currency, last=False): def get_price(self, price_date, unit, currency, last=False):
if self.price_source_type == 'curve' and self.price_index: if self.price_source_type == 'curve' and self.price_index:
PI = Pool().get('price.price') PI = Pool().get('price.price')
pi = PI(self.price_index) pi = PI(self.price_index)
return pi.get_price(price_date, unit, currency, last) return pi.get_price(price_date, unit, currency, last)
if self.price_source_type == 'matrix' and self.price_matrix: if self.price_source_type == 'matrix' and self.price_matrix:
if not self._matrix_valid_on(price_date): if not self._matrix_valid_on(price_date):
return Decimal(0) return Decimal(0)
line = self._get_matrix_line() line = self._get_matrix_line()
if line: if line:
return self._convert_matrix_price( return self._convert_matrix_price(
line.price_value, unit, currency) line.price_value, unit, currency)
if self.price_source_type == 'fixed': if self.price_source_type == 'fixed':
return self._convert_fixed_price(self.fixed_price, currency) return self._convert_fixed_price(self.fixed_price, currency)
return Decimal(0) return Decimal(0)
def get_nbdays(self, name): def get_nbdays(self, name):
days = 0 days = 0
if self.triggers: if self.triggers:
for t in self.triggers: for t in self.triggers:
l,l2 = t.getApplicationListDates(self.get_calendar()) l,l2 = t.getApplicationListDates(self.get_calendar())
days += len(l) days += len(l)
return days return days
@classmethod @classmethod
def delete(cls, components): def delete(cls, components):
@@ -1239,9 +1320,9 @@ class Trigger(ModelSQL,ModelView):
states={ states={
'readonly': Eval('pricing_period') != None, 'readonly': Eval('pricing_period') != None,
}) })
average = fields.Boolean("Avg") average = fields.Boolean("Avg")
last = fields.Boolean("Latest") last = fields.Boolean("Latest")
application_period = fields.Many2One('pricing.period',"Application period") application_period = fields.Many2One('pricing.period',"Application period")
from_a = fields.Date("From", from_a = fields.Date("From",
states={ states={
'readonly': Eval('application_period') != None, 'readonly': Eval('application_period') != None,
@@ -1249,26 +1330,26 @@ class Trigger(ModelSQL,ModelView):
to_a = fields.Date("To", to_a = fields.Date("To",
states={ states={
'readonly': Eval('application_period') != None, 'readonly': Eval('application_period') != None,
}) })
@classmethod @classmethod
def default_average(cls): def default_average(cls):
return True return True
@fields.depends('pricing_period') @fields.depends('pricing_period')
def on_change_with_application_period(self): def on_change_with_application_period(self):
if not self.application_period and self.pricing_period: if not self.application_period and self.pricing_period:
return self.pricing_period return self.pricing_period
@fields.depends('from_p', 'from_a', 'application_period') @fields.depends('from_p', 'from_a', 'application_period')
def on_change_from_p(self): def on_change_from_p(self):
if not self.application_period and not self.from_a: if not self.application_period and not self.from_a:
self.from_a = self.from_p self.from_a = self.from_p
@fields.depends('to_p', 'to_a', 'application_period') @fields.depends('to_p', 'to_a', 'application_period')
def on_change_to_p(self): def on_change_to_p(self):
if not self.application_period and not self.to_a: if not self.application_period and not self.to_a:
self.to_a = self.to_p self.to_a = self.to_p
def getDateWithEstTrigger(self, period): def getDateWithEstTrigger(self, period):
PP = Pool().get('pricing.period') PP = Pool().get('pricing.period')
@@ -1285,73 +1366,73 @@ class Trigger(ModelSQL,ModelView):
date_from,date_to,dates = pp.getDates(d) date_from,date_to,dates = pp.getDates(d)
return date_from,date_to,d,pp.include,dates return date_from,date_to,d,pp.include,dates
def getApplicationListDates(self, cal): def getApplicationListDates(self, cal):
ld = [] ld = []
dates = [] dates = []
if self.application_period: if self.application_period:
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(2) date_from, date_to, d, include,dates = self.getDateWithEstTrigger(2)
else: else:
date_from = self.from_a or self.from_p date_from = self.from_a or self.from_p
date_to = self.to_a or self.to_p date_to = self.to_a or self.to_p
d = None d = None
include = False include = False
ld, lprice = self.getListDates(date_from,date_to,d,include,cal,2,dates) ld, lprice = self.getListDates(date_from,date_to,d,include,cal,2,dates)
return ld, lprice return ld, lprice
def getPricingListDates(self,cal): def getPricingListDates(self,cal):
ld = [] ld = []
dates = [] dates = []
if self.pricing_period: if self.pricing_period:
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(1) date_from, date_to, d, include,dates = self.getDateWithEstTrigger(1)
else: else:
date_from = self.from_p#datetime.datetime(self.from_p.year, self.from_p.month, self.from_p.day) date_from = self.from_p#datetime.datetime(self.from_p.year, self.from_p.month, self.from_p.day)
date_to = self.to_p#datetime.datetime(self.to_p.year, self.to_p.month, self.to_p.day) date_to = self.to_p#datetime.datetime(self.to_p.year, self.to_p.month, self.to_p.day)
d = None d = None
include = False include = False
ld, lprice = self.getListDates(date_from,date_to,d,include,cal,1,dates) ld, lprice = self.getListDates(date_from,date_to,d,include,cal,1,dates)
return ld, lprice return ld, lprice
def getListDates(self,df,dt,t,i,cal,pricing,dates): def getListDates(self,df,dt,t,i,cal,pricing,dates):
l = [] l = []
lprice = [] lprice = []
def append_date(date): def append_date(date):
if cal: if cal:
if not cal.IsQuote(date): if not cal.IsQuote(date):
return return
l.append(date) l.append(date)
if pricing == 1: if pricing == 1:
lprice.append(self.getprice(date)) lprice.append(self.getprice(date))
if cal:
CAL = Pool().get('price.calendar')
cal = CAL(cal)
if dates:
for d in dates:
append_date(d)
return l, lprice
if df and dt:
current_date = datetime.datetime(df.year,df.month,df.day)
dt = datetime.datetime(dt.year,dt.month,dt.day)
while current_date <= dt:
if i or (not i and current_date != t):
append_date(current_date)
current_date += datetime.timedelta(days=1)
return l, lprice
def getprice(self,current_date): if cal:
PC = Pool().get('pricing.component') CAL = Pool().get('price.calendar')
pc = PC(self.component) cal = CAL(cal)
val = {} if dates:
val['date'] = current_date for d in dates:
line = pc.line if pc.line else pc.sale_line append_date(d)
val['price'] = pc.get_price( return l, lprice
current_date, if df and dt:
line.unit, current_date = datetime.datetime(df.year,df.month,df.day)
line.currency, dt = datetime.datetime(dt.year,dt.month,dt.day)
self.last) while current_date <= dt:
val['avg'] = val['price'] if i or (not i and current_date != t):
val['avg_minus_1'] = val['price'] append_date(current_date)
val['isAvg'] = self.average current_date += datetime.timedelta(days=1)
return l, lprice
def getprice(self,current_date):
PC = Pool().get('pricing.component')
pc = PC(self.component)
val = {}
val['date'] = current_date
line = pc.line if pc.line else pc.sale_line
val['price'] = pc.get_price(
current_date,
line.unit,
line.currency,
self.last)
val['avg'] = val['price']
val['avg_minus_1'] = val['price']
val['isAvg'] = self.average
return val return val
class Period(ModelSQL,ModelView): class Period(ModelSQL,ModelView):

View File

@@ -1,4 +1,7 @@
<form col="4"> <form col="4">
<label name="file_structure"/>
<field name="file_structure"/>
<newline/>
<label name="file_"/> <label name="file_"/>
<field name="file_"/> <field name="file_"/>
<newline/> <newline/>

View 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`.

View File

@@ -0,0 +1,414 @@
# Trade Finance Backlog
This backlog tracks the next development phases for the `trade_finance`
module.
Status values:
```text
todo | in-progress | blocked | done
```
Priority values:
```text
low | medium | high
```
## Phase 1 - Core Ledger Foundation
Goal: stabilize the Trade Finance file, presentation, and movement ledger.
### TF-001 - Validate core model installation
Status: todo
Area: test
Priority: high
Goal:
- Install/update the module in a real Tryton database with dependencies.
- Confirm the new models, views, sequence, menus, and access rules load.
Implementation notes:
- Run the targeted module test suite in an environment with `python-sql` and
the full Tryton dependency stack.
- Fix any XML/view/model registration issue found during module activation.
Validation:
- `python -m unittest modules.trade_finance.tests.test_module`
- Manual module activation/update in a development database.
### TF-002 - Harden posted movement immutability
Status: todo
Area: workflow
Priority: high
Goal:
- Ensure posted presentations and posted movement lines cannot be edited or
deleted through direct writes, nested One2Many writes, or UI actions.
Implementation notes:
- Review `write`, `delete`, and nested child update behavior.
- Keep reversal movements as the only correction path for posted exposure.
Validation:
- Add tests for direct line write/delete after posting.
- Add tests for nested line modification through presentation write.
### TF-003 - Add robust exposure helper methods
Status: todo
Area: model
Priority: high
Goal:
- Provide reusable helpers for exposure by facility limit and by Trade Finance
file.
Implementation notes:
- Keep ledger lines as the source of truth.
- Start with Python helpers; SQL/materialized reporting can come later.
- Include `as_of_date` support for historical exposure.
Validation:
- Tests for current exposure by limit.
- Tests for historical exposure before/after transfer movements.
### TF-004 - Improve limit eligibility validation
Status: todo
Area: model
Priority: high
Goal:
- Validate selected `trade_finance.facility_limit` against bank, currency,
validity dates, financing type, and expected operational status.
Implementation notes:
- Reuse `trade_finance.facility_limit_op_status`.
- Do not create a separate `bank.limit` model.
- Consider whether root global limits should be selectable or whether only
leaf sub-limits should receive movements.
Validation:
- Tests for rejected bank mismatch.
- Tests for rejected currency mismatch.
- Tests for rejected financing type mismatch.
- Tests for rejected operational status mismatch once connected.
## Phase 2 - Basic UX and Operational Entry Points
Goal: make the core usable from operational documents.
### TF-005 - Add contextual Start Financing wizard
Status: todo
Area: workflow
Priority: high
Goal:
- Let users start a Trade Finance presentation from an operational document.
Implementation notes:
- Start with stock shipment and stock move because `stock` is already a module
dependency.
- Use `fields.Reference` on presentation origin.
- Pre-fill date, origin, stage, company, currency, and suggested quantity where
available.
Validation:
- Wizard test from `stock.shipment.in`.
- Manual UI check from shipment form.
### TF-006 - Add purchase contract entry points
Status: todo
Area: workflow
Priority: medium
Goal:
- Start financing from purchase contract and purchase contract line when the
relevant purchase contract module is available.
Implementation notes:
- First identify the actual local model names and module dependency impact.
- Avoid adding a hard dependency until confirmed necessary.
- Prefer optional extension module logic if purchase contract is not part of
the base dependency graph.
Validation:
- Tests only if the dependency is activated in the module test environment.
### TF-007 - Add invoice financing entry point
Status: todo
Area: workflow
Priority: medium
Goal:
- Start or continue financing from receivable/invoice stage.
Implementation notes:
- Confirm whether dependency on `account_invoice` is acceptable.
- Origin should point to `account.invoice`.
- Presentation type should default to invoice financing.
Validation:
- Test default origin and stage from invoice when dependency is enabled.
### TF-008 - Add central exposure summary in the UI
Status: todo
Area: view
Priority: medium
Goal:
- Show current exposure by facility limit on the Trade Finance file.
Implementation notes:
- Use a read-only derived view/model or function fields first.
- Do not store manual exposure balances.
Validation:
- Manual UI check with multiple posted movements.
- Tests for summary values if implemented as model logic.
## Phase 3 - Transfer and Lifecycle Workflow
Goal: support movement between financing stages and limits.
### TF-009 - Add transfer proposal helper
Status: todo
Area: workflow
Priority: high
Goal:
- Generate two movement lines for transfers between facility limits.
Implementation notes:
- Negative line on the source facility limit.
- Positive line on the target facility limit.
- Preserve different haircuts/rates on each line.
Validation:
- Test PREFI to IN_TRANSIT transfer with liquidity gain.
### TF-010 - Track liquidity gain/loss
Status: todo
Area: model
Priority: medium
Goal:
- Calculate liquidity gain/loss when moving collateral between limits with
different haircuts.
Implementation notes:
- Keep this derived from movement lines.
- Store applied haircut on each line for audit.
Validation:
- Test gain when target haircut is lower.
- Test loss when target haircut is higher.
### TF-011 - Add repayment/release presentation support
Status: todo
Area: workflow
Priority: medium
Goal:
- Allow users to release financed exposure partially or fully.
Implementation notes:
- Repayment/release should create negative movement lines.
- Avoid editing original posted movements.
Validation:
- Test partial repayment.
- Test full repayment reduces exposure to zero.
## Phase 4 - Funding Cost Engine
Goal: calculate funding costs from chronological exposure movements.
### TF-012 - Build exposure period calculation
Status: todo
Area: model
Priority: high
Goal:
- Convert dated movement lines into running exposure periods per facility
limit.
Implementation notes:
- Sort by movement date.
- Calculate running balance.
- Build intervals until the next movement date or an explicit end date.
Validation:
- Test one movement.
- Test multiple movements on different dates.
- Test same-day movements.
### TF-013 - Calculate interest/funding cost
Status: todo
Area: model
Priority: high
Goal:
- Calculate funding cost using exposure, applied rate, days, and day-count
basis.
Implementation notes:
- Use values copied onto movement lines at posting time.
- Support `360`, `365`, `ACT/360`, and `ACT/365`.
Validation:
- Tests for 360 and 365 bases.
- Test changing facility cost after posting does not alter historical cost.
### TF-014 - Add funding cost report screen
Status: todo
Area: report
Priority: medium
Goal:
- Provide a user-facing report of funding cost by Trade Finance file and by
facility limit.
Implementation notes:
- Start with a Tryton model/report view.
- Add export later only if needed.
Validation:
- Manual report check.
- Tests for report backing model if applicable.
## Phase 5 - Reporting and Controls
Goal: make exposure auditable and operationally controllable.
### TF-015 - Add current exposure by facility limit report
Status: todo
Area: report
Priority: high
Goal:
- Show exposure grouped by `trade_finance.facility_limit`.
Implementation notes:
- Source data must be posted movement lines.
- Include quantity and signed financed amount.
Validation:
- Test aggregation across multiple Trade Finance files.
### TF-016 - Add historical exposure as-of report
Status: todo
Area: report
Priority: medium
Goal:
- Show exposure at a selected historical date.
Implementation notes:
- Filter movement lines where `movement_date <= as_of_date`.
- Group by facility limit and optionally by Trade Finance file.
Validation:
- Tests around before/after transfer dates.
### TF-017 - Add over-limit checks and alerts
Status: todo
Area: workflow
Priority: medium
Goal:
- Detect when posted exposure exceeds facility limit amount.
Implementation notes:
- Decide whether to block posting or warn first.
- Consider parent/global limit and child sub-limit behavior.
Validation:
- Test over child limit.
- Test over global limit if hierarchical aggregation is implemented.
### TF-018 - Add expired limit controls
Status: todo
Area: workflow
Priority: medium
Goal:
- Highlight or block new exposure on expired limits.
Implementation notes:
- Posting already checks movement date validity.
- Add UI filtering and operational alerts.
Validation:
- Manual UI domain check.
- Tests for posting behavior already covered by core validation.
## Phase 6 - Advanced Operational Integration
Goal: connect financing to the broader commodity workflow.
### TF-019 - Add sale matching support
Status: todo
Area: workflow
Priority: medium
Goal:
- Continue financing lifecycle when goods are matched with sales.
Implementation notes:
- Identify the actual local matching model.
- Add it to origin selection only after confirming dependency/model names.
Validation:
- Scenario test once model dependency is clear.
### TF-020 - Add automatic stage suggestions
Status: todo
Area: workflow
Priority: medium
Goal:
- Suggest finance stage from origin document status.
Implementation notes:
- Keep suggestions editable.
- Implement stage inference per origin model.
Validation:
- Tests per supported origin type.
### TF-021 - Add derived snapshot table if performance requires it
Status: todo
Area: report
Priority: low
Goal:
- Improve reporting performance for large ledgers.
Implementation notes:
- Snapshot must be derived from ledger lines.
- Ledger remains the source of truth.
- Do not implement until performance data justifies it.
Validation:
- Reconciliation test comparing snapshot totals with ledger totals.

View File

@@ -0,0 +1,175 @@
# Trade Finance Development Guidance
This file captures implementation guidance for the next development steps of
the `trade_finance` module.
It complements `tryton_trade_finance_architecture.md` and should be used as the
practical working note before creating or changing code.
## 1. Important Existing Model Mapping
The architecture document uses the conceptual name `bank.limit`.
In this codebase, do not create a new `bank.limit` model for the Trade Finance
ledger unless there is a later explicit design decision to do so.
Use the existing facility structure instead:
```text
Concept in architecture doc Existing model in this module
--------------------------- -----------------------------
bank.limit trade_finance.facility_limit
bank / banking facility trade_finance.facility
bank reference / provider trade_finance.facility.tfe
limit amount trade_finance.facility_limit.amount
limit currency trade_finance.facility_limit.currency
limit validity trade_finance.facility_limit.date_from/date_to
limit financing type trade_finance.facility_limit.financing_type
limit haircut rules trade_finance.facility_limit_haircut
limit accepted currencies trade_finance.facility_limit_currency
limit cost/rate rules trade_finance.facility_limit_cost
limit operational eligibility trade_finance.facility_limit_op_status
```
Any future presentation ledger line should therefore reference
`trade_finance.facility_limit`, not a hypothetical `bank.limit`.
## 2. Development Principles
- Keep the existing facility/limit model as the contractual source of truth.
- Treat presentation lines as an exposure movement ledger.
- Store signed amounts and signed quantities on movement lines.
- Do not store or manually update current exposure balances as source data.
- Copy applied haircut/rate/cost values onto posted movement lines for audit.
- Use reversal movements for cancellations instead of editing posted movements.
- Keep contextual origin links polymorphic with `fields.Reference`.
- Add new operational start points by extending the reference selection and
action logic, not by adding many foreign keys to a header model.
## 3. Suggested Documentation Structure
Use a small set of focused markdown files under `modules/trade_finance/documentation/`.
Recommended structure:
```text
documentation/
tryton_trade_finance_architecture.md
development_guidance.md
roadmap.md
backlog.md
bugs.md
decisions.md
test_plan.md
```
Purpose of each file:
```text
tryton_trade_finance_architecture.md
Long-form target architecture and conceptual model.
development_guidance.md
Practical implementation rules, mappings to existing models, and local
conventions for developers.
roadmap.md
Ordered development phases. Stable enough to show where the module is going.
backlog.md
Concrete features/tasks not yet scheduled. One item per small deliverable.
bugs.md
Known defects, reproduction notes, expected behavior, and current status.
decisions.md
Architecture decision records. Use for choices that should not be rediscovered
every time, such as using facility_limit instead of bank.limit.
test_plan.md
Targeted validation strategy, scenarios to cover, and commands to run.
```
Do not create every file upfront if it would remain empty. Create them when
there is useful content to record.
## 4. Suggested Item Formats
### Backlog Item
```text
## TF-001 - Short title
Status: todo | in-progress | blocked | done
Area: model | workflow | view | report | test | data
Priority: low | medium | high
Goal:
- What business behavior should exist?
Implementation notes:
- Smallest expected code change.
- Existing model(s) to reuse.
Validation:
- Target test file or scenario.
- Manual check if needed.
```
### Bug Item
```text
## BUG-001 - Short title
Status: open | reproduced | fixed | closed
Severity: low | medium | high | critical
Area: model | workflow | view | report | test | data
Observed:
- What happens now?
Expected:
- What should happen?
Reproduction:
- Minimal steps or test data.
Fix notes:
- Linked commit/test once fixed.
```
### Decision Record
```text
## ADR-001 - Short title
Date: YYYY-MM-DD
Status: proposed | accepted | superseded
Context:
- Why a decision is needed.
Decision:
- What we decided.
Consequences:
- What this enables.
- What tradeoffs remain.
```
## 5. Immediate Next Development Focus
Recommended next implementation sequence:
1. Create the Trade Finance file/header model.
2. Create the presentation/event model.
3. Create the presentation line ledger model linked to
`trade_finance.facility_limit`.
4. Add sequence/configuration for internal Trade Finance references.
5. Add minimal list/form views and access rules.
6. Add targeted tests around creation, references, signed movements, and limit
validity checks.
The first implementation should stay narrow: no funding-cost engine, no
snapshot table, and no automatic exposure summary as source of truth until the
ledger model is stable.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
# 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,
trade,
)
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,
trade.TradeFinance,
trade.TradeFinancePresentation,
trade.TradeFinancePresentationLine,
trade.TradeFinanceCreateStart,
module='trade_finance', type_='model')
Pool.register(
fx.PriceCalendar,
module='price', type_='model')
Pool.register(
trade.TradeFinanceCreate,
module='trade_finance', type_='wizard')

View 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

View 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>

View 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

View 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

View File

@@ -0,0 +1,646 @@
# 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, tree
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')
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', domain=[('parent', '=', None)],
filter=[('parent', '=', None)])
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(tree(), 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',
left='left', right='right',
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')
left = fields.Integer('Left', required=True)
right = fields.Integer('Right', required=True)
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')
currency = fields.Many2One('currency.currency', 'Currency',
ondelete='RESTRICT',
states={'readonly': Bool(Eval('parent'))},
depends=['parent'],
help='Currency of the global limit. Sub-limits inherit it from '
'their parent.')
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
tenor = fields.Integer('Tenor (days)',
help='Maximum duration of financing from drawdown to repayment')
date_from = fields.Date('Valid From')
date_to = fields.Date('Valid To')
sequence = fields.Integer('Sequence')
notes = fields.Text('Notes')
_order = [('sequence', 'ASC'), ('id', 'ASC')]
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
cls._rebuild_tree('parent', None, 0)
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
@staticmethod
def default_left():
return 0
@staticmethod
def default_right():
return 0
@fields.depends('facility', 'currency', 'date_from', 'date_to', 'parent',
'_parent_parent.facility', '_parent_parent.currency',
'_parent_parent.date_from', '_parent_parent.date_to')
def on_change_parent(self):
if self.parent:
self.facility = self.parent.facility
self.currency = self.parent.currency
if not self.date_from:
self.date_from = self.parent.date_from
if not self.date_to:
self.date_to = self.parent.date_to
@fields.depends('facility', 'currency', 'date_from', 'date_to', 'parent',
'_parent_facility.currency', '_parent_facility.date_from',
'_parent_facility.date_to')
def on_change_facility(self):
if self.facility and not self.parent:
if not self.currency:
self.currency = self.facility.currency
if not self.date_from:
self.date_from = self.facility.date_from
if not self.date_to:
self.date_to = self.facility.date_to
@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
if values.get('parent'):
parent = cls(values['parent'])
values['currency'] = (
parent.currency.id if parent.currency else None)
values.setdefault('date_from', parent.date_from)
values.setdefault('date_to', parent.date_to)
elif values.get('facility'):
facility = Pool().get('trade_finance.facility')(
values['facility'])
values.setdefault('currency',
facility.currency.id if facility.currency else None)
values.setdefault('date_from', facility.date_from)
values.setdefault('date_to', facility.date_to)
return super().create(vlist)
@classmethod
def write(cls, *args):
args = list(args)
actions = iter(args)
new_args = []
check_descendants = []
sync_currency = []
for limits, values in zip(actions, actions):
values = values.copy()
if values.get('parent'):
parent = cls(values['parent'])
values['facility'] = parent.facility.id
values['currency'] = (
parent.currency.id if parent.currency else None)
if {'parent', 'currency'} & set(values):
sync_currency.extend(limits)
if {'parent', 'currency', 'date_from', 'date_to'} & set(values):
check_descendants.extend(limits)
new_args.extend((limits, values))
result = super().write(*new_args)
cls._sync_descendant_currencies(sync_currency)
cls._check_descendants(check_descendants)
return result
@classmethod
def _sync_descendant_currencies(cls, limits):
for limit in limits:
limit = cls(limit.id)
if not limit.currency:
continue
descendants = cls.search([('parent', 'child_of', [limit.id])])
if descendants:
super(FacilityLimit, cls).write(
descendants, {'currency': limit.currency.id})
@classmethod
def _check_descendants(cls, limits):
descendants = []
for limit in limits:
descendants.extend(
cls.search([('parent', 'child_of', [limit.id])]))
if descendants:
cls.validate(descendants)
@classmethod
def validate(cls, limits):
super().validate(limits)
for limit in limits:
limit.check_single_root()
limit.check_required_values()
limit.check_parent_consistency()
limit.check_dates_within_parent()
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_required_values(self):
if not self.currency:
raise UserError(
f"Limit '{self.name}' must define or inherit a currency.")
if not self.date_from or not self.date_to:
raise UserError(
f"Limit '{self.name}' must define valid from and valid to "
f"dates.")
if self.date_from > self.date_to:
raise UserError(
f"Limit '{self.name}' valid from date cannot be after "
f"its valid to date.")
def check_parent_consistency(self):
if not self.parent:
return
if self.facility != self.parent.facility:
raise UserError(
f"Sub-limit '{self.name}' must belong to the same facility "
f"as parent limit '{self.parent.name}'.")
if self.currency != self.parent.currency:
raise UserError(
f"Sub-limit '{self.name}' must use the same currency as "
f"parent limit '{self.parent.name}'.")
def check_dates_within_parent(self):
if self.date_from and self.date_to and self.date_from > self.date_to:
raise UserError(
f"Limit '{self.name}' valid from date cannot be after its "
f"valid to date.")
if not self.parent:
return
if (self.date_from and self.parent.date_from
and self.date_from < self.parent.date_from):
raise UserError(
f"Sub-limit '{self.name}' valid from date cannot be before "
f"parent limit '{self.parent.name}' valid from date.")
if (self.date_to and self.parent.date_to
and self.date_to > self.parent.date_to):
raise UserError(
f"Sub-limit '{self.name}' valid to date cannot be after "
f"parent limit '{self.parent.name}' valid to date.")
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

View File

@@ -0,0 +1,448 @@
<?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>
<field name="field_childs">children</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>

View 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

View 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')

View 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

View 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

View 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>

View 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')

View File

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

View File

@@ -0,0 +1,328 @@
# 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 datetime import date
from decimal import Decimal
from trytond.exceptions import UserError
from trytond.modules.company.tests import create_company, set_company
from trytond.modules.currency.tests import create_currency
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class TradeFinanceTestCase(ModuleTestCase):
'Test Trade Finance module'
module = 'trade_finance'
def create_facility(self):
pool = Pool()
Bank = pool.get('bank')
Facility = pool.get('trade_finance.facility')
Party = pool.get('party.party')
currency = create_currency('USD')
party = Party(name='Test Bank')
party.save()
bank = Bank(party=party)
bank.save()
facility = Facility(
name='Test Facility',
tfe=bank,
currency=currency,
commitment_status='committed',
date_from=date(2026, 1, 1),
date_to=date(2026, 12, 31))
facility.save()
return facility, currency
def create_trade_finance_case(self):
pool = Pool()
Cost = pool.get('trade_finance.facility_limit_cost')
Haircut = pool.get('trade_finance.facility_limit_haircut')
Limit = pool.get('trade_finance.facility_limit')
TradeFinance = pool.get('trade.finance')
facility, currency = self.create_facility()
company = create_company(currency=currency)
limit, = Limit.create([{
'facility': facility.id,
'name': 'Prefi limit',
'currency': currency.id,
'amount': Decimal('1000000.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
Haircut.create([{
'limit': limit.id,
'attribute': 'commodity',
'haircut_pct': Decimal('20.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
Cost.create([{
'limit': limit.id,
'cost_type': 'interest',
'spread': Decimal('10.0000'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
with set_company(company):
trade_finance, = TradeFinance.create([{
'bank': facility.tfe.id,
'company': company.id,
'currency': currency.id,
'start_date': date(2026, 1, 1),
}])
return trade_finance, limit, currency
@with_transaction()
def test_sublimit_inherits_currency_and_dates(self):
'Test sublimit inherits currency and dates from parent'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
child, = Limit.create([{
'parent': root.id,
'name': 'Trading limit',
'amount': Decimal('50.00'),
}])
self.assertEqual(child.facility, facility)
self.assertEqual(child.currency, currency)
self.assertEqual(child.date_from, root.date_from)
self.assertEqual(child.date_to, root.date_to)
@with_transaction()
def test_facility_limits_only_returns_root_limits(self):
'Test facility limits exclude nested sublimits'
pool = Pool()
Facility = pool.get('trade_finance.facility')
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
child, = Limit.create([{
'parent': root.id,
'name': 'Trading limit',
'amount': Decimal('50.00'),
}])
facility = Facility(facility.id)
root = Limit(root.id)
self.assertEqual(list(facility.limits), [root])
self.assertEqual(list(root.children), [child])
@with_transaction()
def test_sublimit_dates_must_stay_within_parent(self):
'Test sublimit dates stay within parent dates'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
with self.assertRaises(UserError):
Limit.create([{
'parent': root.id,
'name': 'Late limit',
'amount': Decimal('50.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2027, 1, 1),
}])
@with_transaction()
def test_currency_change_cascades_to_sublimits(self):
'Test root currency changes cascade to sublimits'
pool = Pool()
Limit = pool.get('trade_finance.facility_limit')
facility, currency = self.create_facility()
other_currency = create_currency('EUR')
root, = Limit.create([{
'facility': facility.id,
'name': 'Global limit',
'currency': currency.id,
'amount': Decimal('100.00'),
'date_from': date(2026, 1, 1),
'date_to': date(2026, 12, 31),
}])
child, = Limit.create([{
'parent': root.id,
'name': 'Trading limit',
'amount': Decimal('50.00'),
}])
Limit.write([root], {'currency': other_currency.id})
child = Limit(child.id)
self.assertEqual(child.currency, other_currency)
@with_transaction()
def test_trade_finance_number_sequence(self):
'Test trade finance number sequence'
trade_finance, _, _ = self.create_trade_finance_case()
self.assertTrue(trade_finance.number.startswith('TFR-'))
@with_transaction()
def test_post_presentation_uses_facility_limit_ledger(self):
'Test posting creates an audited facility limit movement'
pool = Pool()
Presentation = pool.get('trade.finance.presentation')
Line = pool.get('trade.finance.presentation.line')
trade_finance, limit, currency = self.create_trade_finance_case()
presentation, = Presentation.create([{
'trade_finance': trade_finance.id,
'presentation_date': date(2026, 1, 10),
'presentation_type': 'prefi',
'finance_stage': 'prefi',
}])
line, = Line.create([{
'presentation': presentation.id,
'facility_limit': limit.id,
'signed_quantity': Decimal('1000.0000'),
'commodity_price': Decimal('100.000000'),
}])
self.assertEqual(line.currency, currency)
self.assertEqual(line.gross_collateral_value, Decimal('100000.0000000000'))
self.assertEqual(line.financed_amount, Decimal('100000.0000000000'))
Presentation.post([presentation])
presentation = Presentation(presentation.id)
line, = presentation.lines
self.assertEqual(presentation.state, 'posted')
self.assertEqual(line.haircut_percent_applied, Decimal('20.00'))
self.assertEqual(line.interest_rate_applied, Decimal('10.0000'))
self.assertEqual(line.day_count_basis_applied, '360')
self.assertEqual(line.financed_amount, Decimal('80000.000000000000'))
self.assertEqual(
line.signed_financed_amount, Decimal('80000.000000000000'))
@with_transaction()
def test_post_presentation_rejects_limit_outside_validity(self):
'Test posting validates facility limit validity dates'
pool = Pool()
Presentation = pool.get('trade.finance.presentation')
Line = pool.get('trade.finance.presentation.line')
trade_finance, limit, _ = self.create_trade_finance_case()
presentation, = Presentation.create([{
'trade_finance': trade_finance.id,
'presentation_date': date(2025, 12, 31),
'presentation_type': 'prefi',
'finance_stage': 'prefi',
}])
Line.create([{
'presentation': presentation.id,
'facility_limit': limit.id,
'movement_date': date(2025, 12, 31),
'finance_stage': 'prefi',
'currency': trade_finance.currency.id,
'signed_financed_amount': Decimal('100.00'),
}])
with self.assertRaises(UserError):
Presentation.post([presentation])
@with_transaction()
def test_cancel_presentation_creates_reversal(self):
'Test cancellation creates a posted reversal movement'
pool = Pool()
Presentation = pool.get('trade.finance.presentation')
Line = pool.get('trade.finance.presentation.line')
trade_finance, limit, _ = self.create_trade_finance_case()
presentation, = Presentation.create([{
'trade_finance': trade_finance.id,
'presentation_date': date(2026, 1, 10),
'presentation_type': 'prefi',
'finance_stage': 'prefi',
}])
Line.create([{
'presentation': presentation.id,
'facility_limit': limit.id,
'movement_date': date(2026, 1, 10),
'finance_stage': 'prefi',
'currency': trade_finance.currency.id,
'signed_financed_amount': Decimal('80000.00'),
}])
Presentation.post([presentation])
Presentation.cancel([presentation])
presentations = Presentation.search([
('trade_finance', '=', trade_finance.id),
], order=[('sequence', 'ASC')])
self.assertEqual(len(presentations), 2)
self.assertEqual(presentations[0].state, 'cancelled')
self.assertEqual(presentations[1].state, 'posted')
reversal_line, = presentations[1].lines
self.assertEqual(reversal_line.signed_financed_amount, Decimal('-80000.00'))
trade_finance = pool.get('trade.finance')(trade_finance.id)
self.assertEqual(trade_finance.exposure_amount, Decimal('0.00'))
@with_transaction()
def test_create_financing_wizard(self):
'Test single input financing creation wizard'
pool = Pool()
TradeFinance = pool.get('trade.finance')
Wizard = pool.get('trade.finance.create', type='wizard')
_, limit, _ = self.create_trade_finance_case()
company = create_company(currency=limit.currency)
session_id, start_state, _ = Wizard.create()
Wizard.execute(session_id, {
start_state: {
'facility_limit': limit.id,
'company': company.id,
'bank_reference': 'BANK/TEST/001',
'start_date': date(2026, 1, 10),
'presentation_date': date(2026, 1, 10),
'presentation_type': 'prefi',
'finance_stage': 'prefi',
'signed_quantity': Decimal('1000.0000'),
'commodity_price': Decimal('100.000000'),
'signed_financed_amount': Decimal('80000.00'),
'post': True,
},
}, 'create_')
trade_finance, = TradeFinance.search([
('bank_reference', '=', 'BANK/TEST/001'),
])
presentation, = trade_finance.presentations
line, = presentation.lines
self.assertEqual(trade_finance.bank, limit.facility.tfe)
self.assertEqual(trade_finance.currency, limit.currency)
self.assertEqual(presentation.state, 'posted')
self.assertEqual(line.facility_limit, limit)
del ModuleTestCase

View File

@@ -0,0 +1,854 @@
# 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 decimal import Decimal
from trytond.exceptions import UserError
from trytond.model import ModelSQL, ModelView, Workflow, fields
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Button, StateAction, StateView, Wizard
__all__ = [
'TradeFinance',
'TradeFinancePresentation',
'TradeFinancePresentationLine',
'TradeFinanceCreateStart',
'TradeFinanceCreate',
]
_DRAFT_STATES = {
'readonly': Eval('state') != 'draft',
}
_DRAFT_DEPENDS = ['state']
class TradeFinance(Workflow, ModelSQL, ModelView):
'Trade Finance'
__name__ = 'trade.finance'
_rec_name = 'number'
number = fields.Char(
'Trade Finance Reference', required=True, readonly=True)
bank_reference = fields.Char('Bank Reference')
bank = fields.Many2One(
'bank', 'Trade Finance Entity', required=True, ondelete='RESTRICT',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
party = fields.Many2One(
'party.party', 'Counterparty', ondelete='RESTRICT',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
company = fields.Many2One(
'company.company', 'Company', required=True, ondelete='RESTRICT',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
currency = fields.Many2One(
'currency.currency', 'Currency', required=True, ondelete='RESTRICT',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
start_date = fields.Date(
'Start Date', required=True,
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
state = fields.Selection([
('draft', 'Draft'),
('active', 'Active'),
('closed', 'Closed'),
('cancelled', 'Cancelled'),
], 'State', required=True, readonly=True)
description = fields.Text(
'Description', states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
presentations = fields.One2Many(
'trade.finance.presentation', 'trade_finance', 'Presentations',
states={'readonly': Eval('state').in_(['closed', 'cancelled'])},
depends=['state'])
exposure_amount = fields.Function(fields.Numeric(
'Exposure Amount', digits=(16, 2)), 'get_exposure_amount')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('number', 'DESC'))
cls._transitions |= set((
('draft', 'active'),
('active', 'closed'),
('draft', 'cancelled'),
('active', 'cancelled'),
('cancelled', 'draft'),
))
cls._buttons.update({
'activate': {
'invisible': Eval('state') != 'draft',
'depends': ['state'],
},
'close': {
'invisible': Eval('state') != 'active',
'depends': ['state'],
},
'cancel': {
'invisible': ~Eval('state').in_(['draft', 'active']),
'depends': ['state'],
},
'draft': {
'invisible': Eval('state') != 'cancelled',
'depends': ['state'],
},
})
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_start_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_company():
return Transaction().context.get('company')
@classmethod
def _new_number(cls):
pool = Pool()
ModelData = pool.get('ir.model.data')
Sequence = pool.get('ir.sequence')
try:
sequence_id = ModelData.get_id(
'trade_finance', 'sequence_trade_finance')
except KeyError:
return '/'
return Sequence(sequence_id).get()
@classmethod
def create(cls, vlist):
vlist = [v.copy() for v in vlist]
for values in vlist:
values.setdefault('number', cls._new_number())
return super().create(vlist)
@classmethod
def write(cls, *args):
actions = iter(args)
for records, values in zip(actions, actions):
if set(values) - {'state'}:
for record in records:
if record.state in {'closed', 'cancelled'}:
raise UserError(
'Closed or cancelled trade finance files cannot '
'be modified.')
return super().write(*args)
def get_rec_name(self, name):
parts = [self.number]
if self.bank_reference:
parts.append('[%s]' % self.bank_reference)
return ' '.join(parts)
@classmethod
def search_rec_name(cls, name, clause):
_, operator, value = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('number', operator, value),
('bank_reference', operator, value),
]
@classmethod
def get_exposure_amount(cls, records, name):
amounts = {r.id: Decimal('0.00') for r in records}
for record in records:
for presentation in record.presentations:
if presentation.state == 'draft':
continue
for line in presentation.lines:
amounts[record.id] += line.signed_financed_amount or 0
return amounts
@classmethod
@ModelView.button
@Workflow.transition('active')
def activate(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('closed')
def close(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
class TradeFinancePresentation(Workflow, ModelSQL, ModelView):
'Trade Finance Presentation'
__name__ = 'trade.finance.presentation'
trade_finance = fields.Many2One(
'trade.finance', 'Trade Finance', required=True, ondelete='CASCADE',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
sequence = fields.Integer(
'Sequence', states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
presentation_date = fields.Date(
'Presentation Date', required=True,
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
presentation_type = fields.Selection([
('prefi', 'Prefinancing'),
('transfer', 'Transfer'),
('shipment', 'Shipment Presentation'),
('matching', 'Matched Sale'),
('invoice', 'Invoice Financing'),
('release', 'Release'),
('cancellation', 'Cancellation'),
], 'Presentation Type', required=True,
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
finance_stage = fields.Selection([
('prefi', 'Prefi'),
('produced', 'Produced'),
('inland_transit', 'Inland Transit'),
('port_stock', 'Port Stock'),
('on_board', 'On Board'),
('ocean_transit', 'Ocean Transit'),
('delivered', 'Delivered'),
('matched', 'Matched'),
('invoiced', 'Invoiced'),
('repaid', 'Repaid'),
('cancelled', 'Cancelled'),
], 'Finance Stage', required=True,
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
financing_type = fields.Many2One(
'trade_finance.financing_type', 'Financing Type',
ondelete='RESTRICT', states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
origin = fields.Reference(
'Origin', selection='get_origin',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
previous_presentation = fields.Many2One(
'trade.finance.presentation', 'Previous Presentation',
ondelete='RESTRICT', states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
description = fields.Text(
'Description', states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
lines = fields.One2Many(
'trade.finance.presentation.line', 'presentation', 'Movement Lines',
states=_DRAFT_STATES, depends=_DRAFT_DEPENDS)
state = fields.Selection([
('draft', 'Draft'),
('posted', 'Posted'),
('cancelled', 'Cancelled'),
], 'State', required=True, readonly=True)
currency = fields.Function(fields.Many2One(
'currency.currency', 'Currency'), 'on_change_with_currency')
bank = fields.Function(fields.Many2One(
'bank', 'Trade Finance Entity'), 'on_change_with_bank')
exposure_amount = fields.Function(fields.Numeric(
'Exposure Amount', digits=(16, 2)), 'get_exposure_amount')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('presentation_date', 'DESC'))
cls._order.insert(1, ('sequence', 'DESC'))
cls._transitions |= set((
('draft', 'posted'),
('posted', 'cancelled'),
))
cls._buttons.update({
'post': {
'invisible': Eval('state') != 'draft',
'depends': ['state'],
},
'cancel': {
'invisible': Eval('state') != 'posted',
'depends': ['state'],
},
})
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_presentation_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_presentation_type():
return 'prefi'
@staticmethod
def default_finance_stage():
return 'prefi'
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference.'
return ['stock.shipment.in', 'stock.shipment.out', 'stock.move']
@classmethod
def get_origin(cls):
Model = Pool().get('ir.model')
get_name = Model.get_name
return [(None, '')] + [
(model, get_name(model)) for model in cls._get_origin()]
@classmethod
def create(cls, vlist):
vlist = [v.copy() for v in vlist]
for values in vlist:
if values.get('trade_finance') and not values.get('sequence'):
values['sequence'] = cls._next_sequence(
values['trade_finance'])
return super().create(vlist)
@classmethod
def write(cls, *args):
actions = iter(args)
for records, values in zip(actions, actions):
if set(values) - {'state'}:
for record in records:
if record.state != 'draft':
raise UserError(
'Posted or cancelled presentations cannot be '
'modified.')
return super().write(*args)
@classmethod
def delete(cls, records):
for record in records:
if record.state != 'draft':
raise UserError(
'Posted or cancelled presentations cannot be deleted.')
return super().delete(records)
@classmethod
def _next_sequence(cls, trade_finance):
presentations = cls.search([
('trade_finance', '=', trade_finance),
], order=[('sequence', 'DESC')], limit=1)
if presentations and presentations[0].sequence:
return presentations[0].sequence + 1
return 1
@fields.depends('trade_finance', '_parent_trade_finance.currency')
def on_change_with_currency(self, name=None):
return self.trade_finance.currency if self.trade_finance else None
@fields.depends('trade_finance', '_parent_trade_finance.bank')
def on_change_with_bank(self, name=None):
return self.trade_finance.bank if self.trade_finance else None
def get_rec_name(self, name):
items = []
if self.trade_finance:
items.append(self.trade_finance.number)
if self.sequence:
items.append('#%s' % self.sequence)
items.append(self.presentation_type_string)
return ' '.join(items)
@classmethod
def get_exposure_amount(cls, records, name):
amounts = {r.id: Decimal('0.00') for r in records}
for record in records:
for line in record.lines:
amounts[record.id] += line.signed_financed_amount or 0
return amounts
@classmethod
@ModelView.button
@Workflow.transition('posted')
def post(cls, presentations):
Line = Pool().get('trade.finance.presentation.line')
lines = []
for presentation in presentations:
presentation.check_for_posting()
for line in presentation.lines:
line.apply_limit_values()
line.check_for_posting()
lines.append(line)
if lines:
Line.save(lines)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, presentations):
for presentation in presentations:
presentation.create_reversal()
def check_for_posting(self):
if not self.lines:
raise UserError(
'A presentation must contain at least one movement line before '
'posting.')
if self.presentation_type == 'transfer' and not self.previous_presentation:
raise UserError(
'Transfer presentations must reference a previous '
'presentation.')
def create_reversal(self):
Date = Pool().get('ir.date')
reversal_lines = []
for line in self.lines:
reversal_lines.append(('create', [line.get_reverse_values()]))
self.__class__.create([{
'trade_finance': self.trade_finance.id,
'sequence': self.__class__._next_sequence(self.trade_finance.id),
'presentation_date': Date.today(),
'presentation_type': 'cancellation',
'finance_stage': 'cancelled',
'financing_type': (
self.financing_type.id if self.financing_type else None),
'previous_presentation': self.id,
'description': 'Reversal of %s' % self.rec_name,
'state': 'posted',
'lines': reversal_lines,
}])
class TradeFinancePresentationLine(ModelSQL, ModelView):
'Trade Finance Presentation Line'
__name__ = 'trade.finance.presentation.line'
presentation = fields.Many2One(
'trade.finance.presentation', 'Presentation',
required=True, ondelete='CASCADE')
facility_limit = fields.Many2One(
'trade_finance.facility_limit', 'Facility Limit', required=True,
ondelete='RESTRICT')
movement_date = fields.Date('Movement Date', required=True)
finance_stage = fields.Selection([
('prefi', 'Prefi'),
('produced', 'Produced'),
('inland_transit', 'Inland Transit'),
('port_stock', 'Port Stock'),
('on_board', 'On Board'),
('ocean_transit', 'Ocean Transit'),
('delivered', 'Delivered'),
('matched', 'Matched'),
('invoiced', 'Invoiced'),
('repaid', 'Repaid'),
('cancelled', 'Cancelled'),
], 'Finance Stage', required=True)
signed_quantity = fields.Numeric('Signed Quantity', digits=(16, 4))
uom = fields.Many2One('product.uom', 'UoM', ondelete='RESTRICT')
commodity_price = fields.Numeric('Commodity Price', digits=(16, 6))
gross_collateral_value = fields.Numeric(
'Gross Collateral Value', digits=(16, 2))
haircut_percent_applied = fields.Numeric(
'Haircut % Applied', digits=(16, 2))
financed_amount = fields.Numeric('Financed Amount', digits=(16, 2))
signed_financed_amount = fields.Numeric(
'Signed Financed Amount', digits=(16, 2), required=True)
interest_rate_applied = fields.Numeric(
'Interest Rate % Applied', digits=(16, 4))
day_count_basis_applied = fields.Selection([
('360', '360'),
('365', '365'),
('act_360', 'ACT/360'),
('act_365', 'ACT/365'),
], 'Day Count Basis')
currency = fields.Many2One(
'currency.currency', 'Currency', required=True, ondelete='RESTRICT')
value_date = fields.Date('Value Date')
maturity_date = fields.Date('Maturity Date')
description = fields.Text('Description')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('movement_date', 'DESC'))
@classmethod
def create(cls, vlist):
vlist = [v.copy() for v in vlist]
for values in vlist:
cls._set_defaults_from_presentation(values)
cls._set_amounts(values)
return super().create(vlist)
@classmethod
def write(cls, *args):
actions = iter(args)
for lines, values in zip(actions, actions):
if values:
for line in lines:
if line.presentation.state != 'draft':
raise UserError(
'Posted or cancelled movement lines cannot be '
'modified.')
args = list(args)
actions = iter(args)
new_args = []
for lines, values in zip(actions, actions):
values = values.copy()
cls._set_defaults_from_presentation(values)
cls._set_amounts(values)
new_args.extend((lines, values))
return super().write(*new_args)
@classmethod
def delete(cls, lines):
for line in lines:
if line.presentation.state != 'draft':
raise UserError(
'Posted or cancelled movement lines cannot be deleted.')
return super().delete(lines)
@classmethod
def _set_defaults_from_presentation(cls, values):
if not values.get('presentation'):
return
presentation = Pool().get('trade.finance.presentation')(
values['presentation'])
values.setdefault('movement_date', presentation.presentation_date)
values.setdefault('finance_stage', presentation.finance_stage)
if presentation.trade_finance:
values.setdefault('currency', presentation.trade_finance.currency.id)
@classmethod
def _set_amounts(cls, values):
quantity = values.get('signed_quantity')
price = values.get('commodity_price')
haircut = values.get('haircut_percent_applied')
gross = values.get('gross_collateral_value')
financed = values.get('financed_amount')
if gross is None and quantity is not None and price is not None:
gross = abs(quantity) * price
values['gross_collateral_value'] = gross
if financed is None and gross is not None:
haircut = haircut or Decimal('0')
financed = gross * (Decimal('1') - haircut / Decimal('100'))
values['financed_amount'] = financed
if (values.get('signed_financed_amount') is None
and financed is not None):
sign = Decimal('1')
if quantity is not None and quantity < 0:
sign = Decimal('-1')
values['signed_financed_amount'] = financed * sign
@fields.depends('presentation', '_parent_presentation.presentation_date',
'_parent_presentation.finance_stage',
'_parent_presentation.trade_finance',
'_parent_presentation.trade_finance.currency')
def on_change_presentation(self):
if not self.presentation:
return
self.movement_date = self.presentation.presentation_date
self.finance_stage = self.presentation.finance_stage
if self.presentation.trade_finance:
self.currency = self.presentation.trade_finance.currency
@fields.depends('signed_quantity', 'commodity_price')
def on_change_with_gross_collateral_value(self, name=None):
if self.signed_quantity is None or self.commodity_price is None:
return None
return abs(self.signed_quantity) * self.commodity_price
@fields.depends('gross_collateral_value', 'haircut_percent_applied')
def on_change_with_financed_amount(self, name=None):
if self.gross_collateral_value is None:
return None
haircut = self.haircut_percent_applied or Decimal('0')
return self.gross_collateral_value * (
Decimal('1') - haircut / Decimal('100'))
@fields.depends('financed_amount', 'signed_quantity')
def on_change_with_signed_financed_amount(self, name=None):
if self.financed_amount is None:
return None
sign = Decimal('1')
if self.signed_quantity is not None and self.signed_quantity < 0:
sign = Decimal('-1')
return self.financed_amount * sign
def apply_limit_values(self):
haircut_copied = False
if self.haircut_percent_applied is None:
self.haircut_percent_applied = (
self._get_applicable_haircut() or Decimal('0'))
haircut_copied = True
if self.interest_rate_applied is None:
self.interest_rate_applied = (
self._get_applicable_interest_rate() or Decimal('0'))
if not self.day_count_basis_applied:
self.day_count_basis_applied = '360'
if self.gross_collateral_value is None:
self.gross_collateral_value = (
self.on_change_with_gross_collateral_value())
if self.financed_amount is None or haircut_copied:
self.financed_amount = self.on_change_with_financed_amount()
if self.signed_financed_amount is None or (
haircut_copied and self.signed_quantity is not None):
self.signed_financed_amount = (
self.on_change_with_signed_financed_amount())
def check_for_posting(self):
presentation = self.presentation
trade_finance = presentation.trade_finance
limit = self.facility_limit
movement_date = self.movement_date or presentation.presentation_date
if self.signed_financed_amount == 0:
raise UserError('Signed financed amount cannot be zero.')
if limit.facility.tfe != trade_finance.bank:
raise UserError(
"The movement limit must belong to the presentation's Trade "
'Finance Entity.')
if limit.currency != self.currency:
raise UserError(
'The movement currency must match the facility limit currency.')
if movement_date < limit.date_from or movement_date > limit.date_to:
raise UserError(
'The movement date must be within the facility limit validity '
'period.')
if (presentation.financing_type and limit.financing_type
and presentation.financing_type != limit.financing_type):
raise UserError(
'The movement financing type must match the facility limit '
'financing type.')
def _get_applicable_haircut(self):
for haircut in self.facility_limit.haircuts:
if self._date_matches(haircut.date_from, haircut.date_to):
return haircut.haircut_pct
return None
def _get_applicable_interest_rate(self):
for cost in self.facility_limit.costs:
if (cost.cost_type == 'interest'
and self._date_matches(cost.date_from, cost.date_to)):
return cost.spread
return None
def _date_matches(self, date_from, date_to):
date = self.movement_date or self.presentation.presentation_date
if date_from and date < date_from:
return False
if date_to and date > date_to:
return False
return True
def get_reverse_values(self):
return {
'facility_limit': self.facility_limit.id,
'movement_date': Pool().get('ir.date').today(),
'finance_stage': 'cancelled',
'signed_quantity': (
-self.signed_quantity if self.signed_quantity is not None
else None),
'uom': self.uom.id if self.uom else None,
'commodity_price': self.commodity_price,
'gross_collateral_value': self.gross_collateral_value,
'haircut_percent_applied': self.haircut_percent_applied,
'financed_amount': self.financed_amount,
'signed_financed_amount': -self.signed_financed_amount,
'interest_rate_applied': self.interest_rate_applied,
'day_count_basis_applied': self.day_count_basis_applied,
'currency': self.currency.id,
'value_date': self.value_date,
'maturity_date': self.maturity_date,
'description': 'Reversal of %s' % (self.description or self.id),
}
class TradeFinanceCreateStart(ModelView):
'Create Trade Finance Start'
__name__ = 'trade.finance.create.start'
facility_limit = fields.Many2One(
'trade_finance.facility_limit', 'Facility Limit', required=True,
ondelete='RESTRICT')
bank = fields.Function(fields.Many2One(
'bank', 'Trade Finance Entity'), 'on_change_with_bank')
company = fields.Many2One(
'company.company', 'Company', required=True, ondelete='RESTRICT')
party = fields.Many2One(
'party.party', 'Counterparty', ondelete='RESTRICT')
currency = fields.Function(fields.Many2One(
'currency.currency', 'Currency'), 'on_change_with_currency')
bank_reference = fields.Char('Bank Reference')
start_date = fields.Date('Start Date', required=True)
presentation_date = fields.Date('Presentation Date', required=True)
presentation_type = fields.Selection([
('prefi', 'Prefinancing'),
('transfer', 'Transfer'),
('shipment', 'Shipment Presentation'),
('matching', 'Matched Sale'),
('invoice', 'Invoice Financing'),
('release', 'Release'),
('cancellation', 'Cancellation'),
], 'Presentation Type', required=True)
finance_stage = fields.Selection([
('prefi', 'Prefi'),
('produced', 'Produced'),
('inland_transit', 'Inland Transit'),
('port_stock', 'Port Stock'),
('on_board', 'On Board'),
('ocean_transit', 'Ocean Transit'),
('delivered', 'Delivered'),
('matched', 'Matched'),
('invoiced', 'Invoiced'),
('repaid', 'Repaid'),
('cancelled', 'Cancelled'),
], 'Finance Stage', required=True)
financing_type = fields.Function(fields.Many2One(
'trade_finance.financing_type', 'Financing Type'),
'on_change_with_financing_type')
signed_quantity = fields.Numeric('Signed Quantity', digits=(16, 4))
uom = fields.Many2One('product.uom', 'UoM', ondelete='RESTRICT')
commodity_price = fields.Numeric('Commodity Price', digits=(16, 6))
gross_collateral_value = fields.Numeric(
'Gross Collateral Value', digits=(16, 2))
haircut_percent_applied = fields.Numeric(
'Haircut % Applied', digits=(16, 2))
financed_amount = fields.Numeric('Financed Amount', digits=(16, 2))
signed_financed_amount = fields.Numeric(
'Signed Financed Amount', digits=(16, 2), required=True)
value_date = fields.Date('Value Date')
maturity_date = fields.Date('Maturity Date')
description = fields.Text('Description')
post = fields.Boolean('Post Presentation')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_start_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_presentation_date():
Date = Pool().get('ir.date')
return Date.today()
@staticmethod
def default_presentation_type():
return 'prefi'
@staticmethod
def default_finance_stage():
return 'prefi'
@staticmethod
def default_post():
return False
@fields.depends('facility_limit', '_parent_facility_limit.facility')
def on_change_with_bank(self, name=None):
if self.facility_limit and self.facility_limit.facility:
return self.facility_limit.facility.tfe
@fields.depends('facility_limit', '_parent_facility_limit.currency')
def on_change_with_currency(self, name=None):
if self.facility_limit:
return self.facility_limit.currency
@fields.depends('facility_limit', '_parent_facility_limit.financing_type')
def on_change_with_financing_type(self, name=None):
if self.facility_limit:
return self.facility_limit.financing_type
@fields.depends('signed_quantity', 'commodity_price')
def on_change_with_gross_collateral_value(self, name=None):
if self.signed_quantity is None or self.commodity_price is None:
return None
return abs(self.signed_quantity) * self.commodity_price
@fields.depends('gross_collateral_value', 'haircut_percent_applied')
def on_change_with_financed_amount(self, name=None):
if self.gross_collateral_value is None:
return None
haircut = self.haircut_percent_applied or Decimal('0')
return self.gross_collateral_value * (
Decimal('1') - haircut / Decimal('100'))
@fields.depends('financed_amount', 'signed_quantity')
def on_change_with_signed_financed_amount(self, name=None):
if self.financed_amount is None:
return None
sign = Decimal('1')
if self.signed_quantity is not None and self.signed_quantity < 0:
sign = Decimal('-1')
return self.financed_amount * sign
class TradeFinanceCreate(Wizard):
'Create Trade Finance'
__name__ = 'trade.finance.create'
start = StateView('trade.finance.create.start',
'trade_finance.trade_finance_create_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Create', 'create_', 'tryton-ok', default=True),
])
create_ = StateAction('trade_finance.act_trade_finance')
def do_create_(self, action):
pool = Pool()
TradeFinance = pool.get('trade.finance')
Presentation = pool.get('trade.finance.presentation')
Line = pool.get('trade.finance.presentation.line')
start = self.start
facility_limit = start.facility_limit
bank = facility_limit.facility.tfe
currency = facility_limit.currency
financing_type = facility_limit.financing_type
trade_finance, = TradeFinance.create([{
'bank_reference': start.bank_reference,
'bank': bank.id,
'party': start.party.id if start.party else None,
'company': start.company.id,
'currency': currency.id,
'start_date': start.start_date,
'description': start.description,
}])
presentation, = Presentation.create([{
'trade_finance': trade_finance.id,
'presentation_date': start.presentation_date,
'presentation_type': start.presentation_type,
'finance_stage': start.finance_stage,
'financing_type': financing_type.id if financing_type else None,
'description': start.description,
}])
line, = Line.create([{
'presentation': presentation.id,
'facility_limit': facility_limit.id,
'movement_date': start.presentation_date,
'finance_stage': start.finance_stage,
'signed_quantity': start.signed_quantity,
'uom': start.uom.id if start.uom else None,
'commodity_price': start.commodity_price,
'gross_collateral_value': start.gross_collateral_value,
'haircut_percent_applied': start.haircut_percent_applied,
'financed_amount': start.financed_amount,
'signed_financed_amount': start.signed_financed_amount,
'currency': currency.id,
'value_date': start.value_date,
'maturity_date': start.maturity_date,
'description': start.description,
}])
if start.post:
Presentation.post([presentation])
else:
line.apply_limit_values()
Line.save([line])
action['res_id'] = [trade_finance.id]
action['views'].reverse()
return action, {}

View File

@@ -0,0 +1,254 @@
<?xml version="1.0"?>
<tryton>
<data>
<!-- ================================================================ -->
<!-- SEQUENCE -->
<!-- ================================================================ -->
<record model="ir.sequence.type" id="sequence_type_trade_finance">
<field name="name">Trade Finance</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_trade_finance_group_admin">
<field name="sequence_type" ref="sequence_type_trade_finance"/>
<field name="group" ref="group_trade_finance_admin"/>
</record>
<record model="ir.sequence" id="sequence_trade_finance">
<field name="name">Trade Finance</field>
<field name="sequence_type" ref="sequence_type_trade_finance"/>
<field name="prefix">TFR-</field>
<field name="padding" eval="4"/>
</record>
<!-- ================================================================ -->
<!-- SINGLE INPUT CREATION WIZARD -->
<!-- ================================================================ -->
<record model="ir.ui.view" id="trade_finance_create_start_view_form">
<field name="model">trade.finance.create.start</field>
<field name="type">form</field>
<field name="name">trade_finance_create_start_form</field>
</record>
<record model="ir.action.wizard" id="wizard_trade_finance_create">
<field name="name">New Financing</field>
<field name="wiz_name">trade.finance.create</field>
</record>
<menuitem
name="New Financing"
sequence="1"
id="menu_trade_finance_create"
parent="menu_trade_finance"
action="wizard_trade_finance_create"/>
<!-- ================================================================ -->
<!-- TRADE FINANCE -->
<!-- ================================================================ -->
<record model="ir.ui.view" id="trade_finance_view_tree">
<field name="model">trade.finance</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">trade_finance_tree</field>
</record>
<record model="ir.ui.view" id="trade_finance_view_form">
<field name="model">trade.finance</field>
<field name="type">form</field>
<field name="name">trade_finance_form</field>
</record>
<record model="ir.action.act_window" id="act_trade_finance">
<field name="name">Trade Finance Files</field>
<field name="res_model">trade.finance</field>
</record>
<record model="ir.action.act_window.view" id="act_trade_finance_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="trade_finance_view_tree"/>
<field name="act_window" ref="act_trade_finance"/>
</record>
<record model="ir.action.act_window.view" id="act_trade_finance_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="trade_finance_view_form"/>
<field name="act_window" ref="act_trade_finance"/>
</record>
<record model="ir.action.act_window.domain"
id="act_trade_finance_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', '=', 'draft')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_trade_finance"/>
</record>
<record model="ir.action.act_window.domain"
id="act_trade_finance_domain_active">
<field name="name">Active</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('state', '=', 'active')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_trade_finance"/>
</record>
<record model="ir.action.act_window.domain"
id="act_trade_finance_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_trade_finance"/>
</record>
<menuitem
name="Trade Finance Files"
sequence="5"
id="menu_trade_finance_files"
parent="menu_trade_finance"
action="act_trade_finance"/>
<record model="ir.model.access" id="access_trade_finance">
<field name="model">trade.finance</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_trade_finance_user">
<field name="model">trade.finance</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_trade_finance_admin">
<field name="model">trade.finance</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>
<!-- ================================================================ -->
<!-- PRESENTATION -->
<!-- ================================================================ -->
<record model="ir.ui.view" id="trade_finance_presentation_view_tree">
<field name="model">trade.finance.presentation</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">trade_finance_presentation_tree</field>
</record>
<record model="ir.ui.view" id="trade_finance_presentation_view_form">
<field name="model">trade.finance.presentation</field>
<field name="type">form</field>
<field name="name">trade_finance_presentation_form</field>
</record>
<record model="ir.action.act_window" id="act_trade_finance_presentation">
<field name="name">Trade Finance Presentations</field>
<field name="res_model">trade.finance.presentation</field>
</record>
<record model="ir.action.act_window.view"
id="act_trade_finance_presentation_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="trade_finance_presentation_view_tree"/>
<field name="act_window" ref="act_trade_finance_presentation"/>
</record>
<record model="ir.action.act_window.view"
id="act_trade_finance_presentation_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="trade_finance_presentation_view_form"/>
<field name="act_window" ref="act_trade_finance_presentation"/>
</record>
<menuitem
name="Presentations"
sequence="15"
id="menu_trade_finance_presentations"
parent="menu_trade_finance"
action="act_trade_finance_presentation"/>
<record model="ir.model.access" id="access_trade_finance_presentation">
<field name="model">trade.finance.presentation</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_trade_finance_presentation_user">
<field name="model">trade.finance.presentation</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_trade_finance_presentation_admin">
<field name="model">trade.finance.presentation</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>
<!-- ================================================================ -->
<!-- PRESENTATION LINE -->
<!-- ================================================================ -->
<record model="ir.ui.view" id="trade_finance_presentation_line_view_tree">
<field name="model">trade.finance.presentation.line</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">trade_finance_presentation_line_tree</field>
</record>
<record model="ir.ui.view" id="trade_finance_presentation_line_view_form">
<field name="model">trade.finance.presentation.line</field>
<field name="type">form</field>
<field name="name">trade_finance_presentation_line_form</field>
</record>
<record model="ir.action.act_window" id="act_trade_finance_presentation_line">
<field name="name">Trade Finance Movement Lines</field>
<field name="res_model">trade.finance.presentation.line</field>
</record>
<record model="ir.action.act_window.view"
id="act_trade_finance_presentation_line_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="trade_finance_presentation_line_view_tree"/>
<field name="act_window" ref="act_trade_finance_presentation_line"/>
</record>
<record model="ir.action.act_window.view"
id="act_trade_finance_presentation_line_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="trade_finance_presentation_line_view_form"/>
<field name="act_window" ref="act_trade_finance_presentation_line"/>
</record>
<menuitem
name="Movement Lines"
sequence="20"
id="menu_trade_finance_presentation_lines"
parent="menu_trade_finance"
action="act_trade_finance_presentation_line"/>
<record model="ir.model.access" id="access_trade_finance_presentation_line">
<field name="model">trade.finance.presentation.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_trade_finance_presentation_line_user">
<field name="model">trade.finance.presentation.line</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_trade_finance_presentation_line_admin">
<field name="model">trade.finance.presentation.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>
</data>
</tryton>

View File

@@ -0,0 +1,13 @@
[tryton]
version=7.2.7
depends:
ir
res
stock
price
bank
xml:
reference.xml
facility.xml
constraint_type.xml
trade.xml

View 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

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,3 @@
<tree>
<field name="name"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="sequence"/>
<field name="segment"/>
<field name="duration_days"/>
</tree>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="name"/>
<field name="amount"/>
<field name="date_from"/>
<field name="date_to"/>
</tree>

View 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>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="constraint_type"/>
<field name="is_exclusion"/>
<field name="date_from"/>
<field name="date_to"/>
</tree>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="name"/>
<field name="ratio_type"/>
<field name="threshold"/>
<field name="currency"/>
</tree>

View 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>

View File

@@ -0,0 +1,47 @@
<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"/>
<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="Global Limit" id="limits">
<field name="limits" colspan="6"
domain="[('parent', '=', None)]"
view_ids="trade_finance.facility_limit_view_tree,trade_finance.facility_limit_view_form"/>
</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>

View File

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

View 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>

View File

@@ -0,0 +1,7 @@
<tree>
<field name="attribute"/>
<field name="value"/>
<field name="variation_type"/>
<field name="variation_value"/>
<field name="variation_currency"/>
</tree>

View File

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

View File

@@ -0,0 +1,53 @@
<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="currency"/>
<field name="currency"/>
<label name="amount"/>
<field name="amount"/>
<label name="date_from"/>
<field name="date_from"/>
<label name="date_to"/>
<field name="date_to"/>
<label name="tenor"/>
<field name="tenor"/>
<label name="sequence"/>
<field name="sequence"/>
<newline/>
<label name="parent"/>
<field name="parent" colspan="3" readonly="1"/>
<label name="facility"/>
<field name="facility" colspan="3"/>
<label name="notes"/>
<field name="notes" colspan="3"/>
</group>
<notebook colspan="4">
<page string="Sub-Limits" id="children">
<field name="children" colspan="4"
view_ids="trade_finance.facility_limit_view_tree,trade_finance.facility_limit_view_form"/>
</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>

View 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>

View File

@@ -0,0 +1,4 @@
<tree>
<field name="operational_status"/>
<field name="evidence_type"/>
</tree>

View File

@@ -0,0 +1,11 @@
<tree keyword_open="1">
<field name="name" expand="2"/>
<field name="parent" tree_invisible="1"/>
<field name="children" tree_invisible="1"/>
<field name="financing_type"/>
<field name="currency"/>
<field name="amount"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="tenor"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View 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>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="sequence"/>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="days"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="index"/>
<field name="rate_date"/>
<field name="rate"/>
</tree>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,7 @@
<tree>
<field name="sequence"/>
<field name="code"/>
<field name="name"/>
<field name="financing_type"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,6 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="remaining_risk_pct"/>
<field name="active"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>

View 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>

View 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>

View File

@@ -0,0 +1,54 @@
<form col="6">
<group id="facility" col="6" colspan="6">
<label name="facility_limit"/>
<field name="facility_limit" colspan="3"/>
<label name="post"/>
<field name="post"/>
<label name="bank"/>
<field name="bank" colspan="3"/>
<label name="currency"/>
<field name="currency"/>
<label name="financing_type"/>
<field name="financing_type"/>
</group>
<group id="file" col="6" colspan="6">
<label name="company"/>
<field name="company" colspan="3"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="party"/>
<field name="party" colspan="3"/>
<label name="bank_reference"/>
<field name="bank_reference"/>
</group>
<group id="presentation" col="6" colspan="6">
<label name="presentation_date"/>
<field name="presentation_date"/>
<label name="presentation_type"/>
<field name="presentation_type"/>
<label name="finance_stage"/>
<field name="finance_stage"/>
</group>
<group id="amounts" col="6" colspan="6">
<label name="signed_quantity"/>
<field name="signed_quantity"/>
<label name="uom"/>
<field name="uom"/>
<label name="commodity_price"/>
<field name="commodity_price"/>
<label name="gross_collateral_value"/>
<field name="gross_collateral_value"/>
<label name="haircut_percent_applied"/>
<field name="haircut_percent_applied"/>
<label name="financed_amount"/>
<field name="financed_amount"/>
<label name="signed_financed_amount"/>
<field name="signed_financed_amount"/>
<label name="value_date"/>
<field name="value_date"/>
<label name="maturity_date"/>
<field name="maturity_date"/>
</group>
<label name="description"/>
<field name="description" colspan="5"/>
</form>

View File

@@ -0,0 +1,36 @@
<form col="6">
<group id="header" col="6" colspan="6">
<label name="number"/>
<field name="number"/>
<label name="bank_reference"/>
<field name="bank_reference"/>
<label name="state"/>
<field name="state"/>
<label name="bank"/>
<field name="bank" colspan="3"/>
<label name="start_date"/>
<field name="start_date"/>
<label name="party"/>
<field name="party" colspan="3"/>
<label name="currency"/>
<field name="currency"/>
<label name="company"/>
<field name="company" colspan="3"/>
<label name="exposure_amount"/>
<field name="exposure_amount"/>
<label name="description"/>
<field name="description" colspan="5"/>
</group>
<notebook colspan="6">
<page string="Presentations" id="presentations">
<field name="presentations" colspan="6"
view_ids="trade_finance.trade_finance_presentation_view_tree,trade_finance.trade_finance_presentation_view_form"/>
</page>
</notebook>
<group id="buttons" col="4" colspan="6">
<button name="activate" icon="tryton-forward"/>
<button name="close" icon="tryton-ok"/>
<button name="cancel" icon="tryton-cancel"/>
<button name="draft" icon="tryton-back"/>
</group>
</form>

View File

@@ -0,0 +1,36 @@
<form col="6">
<group id="header" col="6" colspan="6">
<label name="trade_finance"/>
<field name="trade_finance" colspan="3"/>
<label name="state"/>
<field name="state"/>
<label name="sequence"/>
<field name="sequence"/>
<label name="presentation_date"/>
<field name="presentation_date"/>
<label name="presentation_type"/>
<field name="presentation_type"/>
<label name="finance_stage"/>
<field name="finance_stage"/>
<label name="financing_type"/>
<field name="financing_type"/>
<label name="origin"/>
<field name="origin" colspan="3"/>
<label name="previous_presentation"/>
<field name="previous_presentation"/>
<label name="exposure_amount"/>
<field name="exposure_amount"/>
<label name="description"/>
<field name="description" colspan="5"/>
</group>
<notebook colspan="6">
<page string="Movement Lines" id="lines">
<field name="lines" colspan="6"
view_ids="trade_finance.trade_finance_presentation_line_view_tree,trade_finance.trade_finance_presentation_line_view_form"/>
</page>
</notebook>
<group id="buttons" col="2" colspan="6">
<button name="post" icon="tryton-ok"/>
<button name="cancel" icon="tryton-cancel"/>
</group>
</form>

View File

@@ -0,0 +1,36 @@
<form col="6">
<label name="presentation"/>
<field name="presentation" colspan="3"/>
<label name="facility_limit"/>
<field name="facility_limit"/>
<label name="movement_date"/>
<field name="movement_date"/>
<label name="finance_stage"/>
<field name="finance_stage"/>
<label name="currency"/>
<field name="currency"/>
<label name="signed_quantity"/>
<field name="signed_quantity"/>
<label name="uom"/>
<field name="uom"/>
<label name="commodity_price"/>
<field name="commodity_price"/>
<label name="gross_collateral_value"/>
<field name="gross_collateral_value"/>
<label name="haircut_percent_applied"/>
<field name="haircut_percent_applied"/>
<label name="financed_amount"/>
<field name="financed_amount"/>
<label name="signed_financed_amount"/>
<field name="signed_financed_amount"/>
<label name="interest_rate_applied"/>
<field name="interest_rate_applied"/>
<label name="day_count_basis_applied"/>
<field name="day_count_basis_applied"/>
<label name="value_date"/>
<field name="value_date"/>
<label name="maturity_date"/>
<field name="maturity_date"/>
<label name="description"/>
<field name="description" colspan="5"/>
</form>

View File

@@ -0,0 +1,14 @@
<tree>
<field name="presentation"/>
<field name="facility_limit"/>
<field name="movement_date"/>
<field name="finance_stage"/>
<field name="signed_quantity"/>
<field name="uom"/>
<field name="gross_collateral_value"/>
<field name="haircut_percent_applied"/>
<field name="financed_amount"/>
<field name="signed_financed_amount"/>
<field name="interest_rate_applied"/>
<field name="currency"/>
</tree>

View File

@@ -0,0 +1,11 @@
<tree>
<field name="trade_finance"/>
<field name="sequence"/>
<field name="presentation_date"/>
<field name="presentation_type"/>
<field name="finance_stage"/>
<field name="financing_type"/>
<field name="origin"/>
<field name="exposure_amount"/>
<field name="state"/>
</tree>

View File

@@ -0,0 +1,11 @@
<tree>
<field name="number"/>
<field name="bank_reference"/>
<field name="bank"/>
<field name="party"/>
<field name="company"/>
<field name="currency"/>
<field name="start_date"/>
<field name="exposure_amount"/>
<field name="state"/>
</tree>

View 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>

View File

@@ -0,0 +1,5 @@
<tree>
<field name="code"/>
<field name="name"/>
<field name="active"/>
</tree>