Merge dev
This commit is contained in:
@@ -7,10 +7,10 @@ from . import (
|
||||
account,
|
||||
configuration,
|
||||
purchase,
|
||||
sale,
|
||||
global_reporting,
|
||||
ctrm_reporting,
|
||||
stock,
|
||||
sale,
|
||||
global_reporting,
|
||||
ctrm_reporting,
|
||||
stock,
|
||||
derivative,
|
||||
lot,
|
||||
pricing,
|
||||
@@ -36,6 +36,8 @@ from . import (
|
||||
backtoback,
|
||||
service,
|
||||
invoice,
|
||||
process_documentation,
|
||||
routes,
|
||||
)
|
||||
|
||||
def register():
|
||||
@@ -57,43 +59,44 @@ def register():
|
||||
lc.LCMT700,
|
||||
lc.LCMessage,
|
||||
lc.CreateLCStart,
|
||||
global_reporting.GRConfiguration,
|
||||
ctrm_reporting.CTRMPhysicalPosition,
|
||||
ctrm_reporting.CTRMPhysicalPositionContext,
|
||||
ctrm_reporting.CTRMFinancialPosition,
|
||||
ctrm_reporting.CTRMFinancialPositionContext,
|
||||
ctrm_reporting.CTRMNetPosition,
|
||||
ctrm_reporting.CTRMNetPositionContext,
|
||||
ctrm_reporting.CTRMRealizedPnl,
|
||||
ctrm_reporting.CTRMRealizedPnlContext,
|
||||
ctrm_reporting.CTRMMtmPnl,
|
||||
ctrm_reporting.CTRMMtmPnlContext,
|
||||
ctrm_reporting.CTRMPnlExplain,
|
||||
ctrm_reporting.CTRMPnlExplainContext,
|
||||
ctrm_reporting.CTRMPnlDimension,
|
||||
ctrm_reporting.CTRMPnlDimensionContext,
|
||||
ctrm_reporting.CTRMCreditRisk,
|
||||
ctrm_reporting.CTRMCreditRiskContext,
|
||||
ctrm_reporting.CTRMShippingLogistics,
|
||||
ctrm_reporting.CTRMShippingLogisticsContext,
|
||||
ctrm_reporting.CTRMInventory,
|
||||
ctrm_reporting.CTRMInventoryContext,
|
||||
ctrm_reporting.CTRMContractPerformance,
|
||||
ctrm_reporting.CTRMContractPerformanceContext,
|
||||
ctrm_reporting.CTRMScheduling,
|
||||
ctrm_reporting.CTRMSchedulingContext,
|
||||
ctrm_reporting.CTRMAccruals,
|
||||
ctrm_reporting.CTRMAccrualsContext,
|
||||
ctrm_reporting.CTRMSettlements,
|
||||
ctrm_reporting.CTRMSettlementsContext,
|
||||
ctrm_reporting.CTRMCashFlow,
|
||||
ctrm_reporting.CTRMCashFlowContext,
|
||||
ctrm_reporting.CTRMGLReconciliation,
|
||||
ctrm_reporting.CTRMGLReconciliationContext,
|
||||
configuration.Configuration,
|
||||
pricing.ImportPricesStart,
|
||||
pricing.ImportPricesResult,
|
||||
module='purchase_trade', type_='model')
|
||||
global_reporting.GRConfiguration,
|
||||
ctrm_reporting.CTRMPhysicalPosition,
|
||||
ctrm_reporting.CTRMPhysicalPositionContext,
|
||||
ctrm_reporting.CTRMFinancialPosition,
|
||||
ctrm_reporting.CTRMFinancialPositionContext,
|
||||
ctrm_reporting.CTRMNetPosition,
|
||||
ctrm_reporting.CTRMNetPositionContext,
|
||||
ctrm_reporting.CTRMRealizedPnl,
|
||||
ctrm_reporting.CTRMRealizedPnlContext,
|
||||
ctrm_reporting.CTRMMtmPnl,
|
||||
ctrm_reporting.CTRMMtmPnlContext,
|
||||
ctrm_reporting.CTRMPnlExplain,
|
||||
ctrm_reporting.CTRMPnlExplainContext,
|
||||
ctrm_reporting.CTRMPnlDimension,
|
||||
ctrm_reporting.CTRMPnlDimensionContext,
|
||||
ctrm_reporting.CTRMCreditRisk,
|
||||
ctrm_reporting.CTRMCreditRiskContext,
|
||||
ctrm_reporting.CTRMShippingLogistics,
|
||||
ctrm_reporting.CTRMShippingLogisticsContext,
|
||||
ctrm_reporting.CTRMInventory,
|
||||
ctrm_reporting.CTRMInventoryContext,
|
||||
ctrm_reporting.CTRMContractPerformance,
|
||||
ctrm_reporting.CTRMContractPerformanceContext,
|
||||
ctrm_reporting.CTRMScheduling,
|
||||
ctrm_reporting.CTRMSchedulingContext,
|
||||
ctrm_reporting.CTRMAccruals,
|
||||
ctrm_reporting.CTRMAccrualsContext,
|
||||
ctrm_reporting.CTRMSettlements,
|
||||
ctrm_reporting.CTRMSettlementsContext,
|
||||
ctrm_reporting.CTRMCashFlow,
|
||||
ctrm_reporting.CTRMCashFlowContext,
|
||||
ctrm_reporting.CTRMGLReconciliation,
|
||||
ctrm_reporting.CTRMGLReconciliationContext,
|
||||
configuration.Configuration,
|
||||
process_documentation.ProcessDocumentation,
|
||||
pricing.ImportPricesStart,
|
||||
pricing.ImportPricesResult,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
incoming.ImportSwift,
|
||||
incoming.PrepareDocuments,
|
||||
@@ -117,20 +120,20 @@ def register():
|
||||
dashboard.Demos,
|
||||
party.Party,
|
||||
party.PartyExecution,
|
||||
party.PartyExecutionSla,
|
||||
party.PartyExecutionPlace,
|
||||
payment_term.PaymentTerm,
|
||||
payment_term.PaymentTermLine,
|
||||
stock.CharterRateType,
|
||||
stock.CharterConditionRate,
|
||||
stock.CharterCondition,
|
||||
stock.CharterParty,
|
||||
purchase.ContractClause,
|
||||
purchase.ContractTemplate,
|
||||
purchase.ContractTemplateLine,
|
||||
purchase.ContractClauseSelection,
|
||||
purchase.Purchase,
|
||||
purchase.Line,
|
||||
party.PartyExecutionSla,
|
||||
party.PartyExecutionPlace,
|
||||
payment_term.PaymentTerm,
|
||||
payment_term.PaymentTermLine,
|
||||
stock.CharterRateType,
|
||||
stock.CharterConditionRate,
|
||||
stock.CharterCondition,
|
||||
stock.CharterParty,
|
||||
purchase.ContractClause,
|
||||
purchase.ContractTemplate,
|
||||
purchase.ContractTemplateLine,
|
||||
purchase.ContractClauseSelection,
|
||||
purchase.Purchase,
|
||||
purchase.Line,
|
||||
purchase.Estimated,
|
||||
purchase.Component,
|
||||
purchase.Pricing,
|
||||
@@ -145,10 +148,10 @@ def register():
|
||||
open_position.OpenPosition,
|
||||
open_position.OpenPositionReport,
|
||||
open_position.OpenPositionContext,
|
||||
optional.OptionalScenario,
|
||||
fee.Fee,
|
||||
fee.FeeRule,
|
||||
fee.FeeLots,
|
||||
optional.OptionalScenario,
|
||||
fee.Fee,
|
||||
fee.FeeRule,
|
||||
fee.FeeLots,
|
||||
purchase.FeeLots,
|
||||
valuation.Valuation,
|
||||
valuation.ValuationLine,
|
||||
@@ -218,10 +221,10 @@ def register():
|
||||
purchase.ContractDocumentType,
|
||||
purchase.DocTemplate,
|
||||
purchase.DocTypeTemplate,
|
||||
purchase.PurchaseStrategy,
|
||||
purchase.PriceComposition,
|
||||
purchase.PremiumComposition,
|
||||
purchase.QualityAnalysis,
|
||||
purchase.PurchaseStrategy,
|
||||
purchase.PriceComposition,
|
||||
purchase.PremiumComposition,
|
||||
purchase.QualityAnalysis,
|
||||
purchase.Assay,
|
||||
purchase.AssayLine,
|
||||
purchase.AssayElement,
|
||||
@@ -239,13 +242,13 @@ def register():
|
||||
Pool.register(
|
||||
account.PhysicalTradeIFRS,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
configuration.AccountConfiguration,
|
||||
configuration.AccountConfigurationDefaultAccount,
|
||||
invoice.InvoiceLineLotWeight,
|
||||
invoice.InvoicePaddingReport,
|
||||
invoice.InvoicePaddingContext,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
configuration.AccountConfiguration,
|
||||
configuration.AccountConfigurationDefaultAccount,
|
||||
invoice.InvoiceLineLotWeight,
|
||||
invoice.InvoicePaddingReport,
|
||||
invoice.InvoicePaddingContext,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
invoice.Invoice,
|
||||
invoice.InvoiceLine,
|
||||
@@ -270,12 +273,12 @@ def register():
|
||||
purchase.PurchaseAssociation,
|
||||
purchase.PurchaseCrop,
|
||||
module='lot', type_='model')
|
||||
Pool.register(
|
||||
sale.ContractClauseSelection,
|
||||
sale.CharterCondition,
|
||||
sale.Sale,
|
||||
sale.SaleLine,
|
||||
sale.SaleCreatePurchaseInput,
|
||||
Pool.register(
|
||||
sale.ContractClauseSelection,
|
||||
sale.CharterCondition,
|
||||
sale.Sale,
|
||||
sale.SaleLine,
|
||||
sale.SaleCreatePurchaseInput,
|
||||
sale.Derivative,
|
||||
sale.Valuation,
|
||||
sale.ValuationLine,
|
||||
@@ -294,10 +297,10 @@ def register():
|
||||
sale.SaleStrategy,
|
||||
sale.OpenPosition,
|
||||
sale.Backtoback,
|
||||
sale.AnalyticDimensionAssignment,
|
||||
sale.PriceComposition,
|
||||
sale.PremiumComposition,
|
||||
module='sale', type_='model')
|
||||
sale.AnalyticDimensionAssignment,
|
||||
sale.PriceComposition,
|
||||
sale.PremiumComposition,
|
||||
module='sale', type_='model')
|
||||
Pool.register(
|
||||
lot.LotShipping,
|
||||
lot.LotMatching,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.pyson import Bool, Eval, Id
|
||||
from trytond.model import (ModelSQL, ModelView)
|
||||
from trytond.exceptions import UserError
|
||||
@@ -68,12 +69,19 @@ class ImportPricesStart(ModelView):
|
||||
"Import Prices"
|
||||
__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')
|
||||
filename = fields.Char('Filename')
|
||||
create_missing_price_index = fields.Boolean(
|
||||
"Create price index if missing")
|
||||
overwrite_existing_price = fields.Boolean("Overwrite existing price")
|
||||
|
||||
@staticmethod
|
||||
def default_file_structure():
|
||||
return 'historical'
|
||||
|
||||
class ImportPricesResult(ModelView):
|
||||
"Import Prices Result"
|
||||
@@ -111,7 +119,8 @@ class ImportPrices(Wizard):
|
||||
}
|
||||
|
||||
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(
|
||||
rows,
|
||||
create_missing_price_index=(
|
||||
@@ -142,6 +151,7 @@ class ImportPrices(Wizard):
|
||||
}
|
||||
|
||||
for row_number, row in enumerate(rows, start=2):
|
||||
row_number = row.get('_row_number', index)
|
||||
price_index = (row.get('price_index') or '').strip()
|
||||
try:
|
||||
price_date = cls._as_date(row.get('price_date'))
|
||||
@@ -217,7 +227,7 @@ class ImportPrices(Wizard):
|
||||
}
|
||||
references = [
|
||||
('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_unit', 'product.uom', [('name', '=', 'Mt')]),
|
||||
]
|
||||
@@ -340,7 +350,7 @@ class ImportPrices(Wizard):
|
||||
return ', '.join(parts) or 'price imported'
|
||||
|
||||
@classmethod
|
||||
def _read_xlsx(cls, data):
|
||||
def _read_xlsx(cls, data, file_structure='historical'):
|
||||
try:
|
||||
with zipfile.ZipFile(BytesIO(data)) as workbook:
|
||||
shared_strings = cls._read_shared_strings(workbook)
|
||||
@@ -354,6 +364,12 @@ class ImportPrices(Wizard):
|
||||
if not rows:
|
||||
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 = {}
|
||||
for cell in rows[0].findall('s:c', ns):
|
||||
index = cls._cell_column_index(cell.get('r'))
|
||||
@@ -380,6 +396,59 @@ class ImportPrices(Wizard):
|
||||
result.append(values)
|
||||
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
|
||||
def _read_shared_strings(workbook):
|
||||
try:
|
||||
@@ -472,6 +541,18 @@ class ImportPrices(Wizard):
|
||||
except ValueError:
|
||||
pass
|
||||
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):
|
||||
"MtM Scenario"
|
||||
__name__ = 'mtm.scenario'
|
||||
@@ -511,13 +592,13 @@ class MtmStrategy(ModelSQL, ModelView):
|
||||
Currency = pool.get('currency.currency')
|
||||
total = Decimal(0)
|
||||
|
||||
scenario = self.scenario
|
||||
dt = scenario.valuation_date
|
||||
|
||||
for comp in self.components:
|
||||
value = Decimal(0)
|
||||
|
||||
if comp.price_source_type == 'curve' and comp.price_index:
|
||||
scenario = self.scenario
|
||||
dt = scenario.valuation_date
|
||||
|
||||
for comp in self.components:
|
||||
value = Decimal(0)
|
||||
|
||||
if comp.price_source_type == 'curve' and comp.price_index:
|
||||
value = Decimal(
|
||||
comp.price_index.get_price(
|
||||
dt,
|
||||
@@ -526,22 +607,22 @@ class MtmStrategy(ModelSQL, ModelView):
|
||||
last=scenario.use_last_price
|
||||
)
|
||||
)
|
||||
|
||||
elif comp.price_source_type == 'matrix' and comp.price_matrix:
|
||||
value = self._get_matrix_price(comp, line, dt)
|
||||
|
||||
elif comp.price_source_type == 'fixed':
|
||||
value = Decimal(comp.get_price(
|
||||
dt,
|
||||
line.unit,
|
||||
self.currency,
|
||||
last=scenario.use_last_price
|
||||
))
|
||||
|
||||
if comp.ratio:
|
||||
value *= Decimal(comp.ratio) / Decimal(100)
|
||||
|
||||
total += value * qty
|
||||
|
||||
elif comp.price_source_type == 'matrix' and comp.price_matrix:
|
||||
value = self._get_matrix_price(comp, line, dt)
|
||||
|
||||
elif comp.price_source_type == 'fixed':
|
||||
value = Decimal(comp.get_price(
|
||||
dt,
|
||||
line.unit,
|
||||
self.currency,
|
||||
last=scenario.use_last_price
|
||||
))
|
||||
|
||||
if comp.ratio:
|
||||
value *= Decimal(comp.ratio) / Decimal(100)
|
||||
|
||||
total += value * qty
|
||||
|
||||
return Decimal(str(total)).quantize(Decimal("0.01"))
|
||||
|
||||
@@ -699,198 +780,198 @@ class Component(ModelSQL, ModelView):
|
||||
required=False, ondelete='CASCADE'
|
||||
)
|
||||
|
||||
price_source_type = fields.Selection([
|
||||
('curve', 'Curve'),
|
||||
('matrix', 'Matrix'),
|
||||
('fixed', 'Fixed'),
|
||||
], "Price Source", required=True)
|
||||
|
||||
fix_type = fields.Many2One('price.fixtype',"Fixation type")
|
||||
ratio = fields.Numeric("%",digits=(16,7))
|
||||
price_index = fields.Many2One(
|
||||
'price.price', "Curve",
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'curve',
|
||||
'required': Eval('price_source_type') == 'curve',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
price_matrix = fields.Many2One(
|
||||
'price.matrix', "Price Matrix",
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'matrix',
|
||||
'required': Eval('price_source_type') == 'matrix',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
fixed_price = fields.Numeric(
|
||||
"Fixed Price", digits=(16, 6),
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'fixed',
|
||||
'required': Eval('price_source_type') == 'fixed',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
fixed_currency = fields.Many2One('currency.currency', "Fixed Curr.")
|
||||
currency = fields.Function(
|
||||
fields.Many2One(
|
||||
'currency.currency', "Curr.",
|
||||
states={
|
||||
'required': Eval('price_source_type') == 'fixed',
|
||||
},
|
||||
depends=['price_source_type']),
|
||||
'get_cur', setter='set_cur')
|
||||
auto = fields.Boolean("Auto")
|
||||
fallback = fields.Boolean("Fallback")
|
||||
calendar = fields.Many2One('price.calendar',"Calendar")
|
||||
nbdays = fields.Function(fields.Integer("Nb days"),'get_nbdays')
|
||||
triggers = fields.One2Many('pricing.trigger','component',"Period rules")
|
||||
price_source_type = fields.Selection([
|
||||
('curve', 'Curve'),
|
||||
('matrix', 'Matrix'),
|
||||
('fixed', 'Fixed'),
|
||||
], "Price Source", required=True)
|
||||
|
||||
fix_type = fields.Many2One('price.fixtype',"Fixation type")
|
||||
ratio = fields.Numeric("%",digits=(16,7))
|
||||
price_index = fields.Many2One(
|
||||
'price.price', "Curve",
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'curve',
|
||||
'required': Eval('price_source_type') == 'curve',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
price_matrix = fields.Many2One(
|
||||
'price.matrix', "Price Matrix",
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'matrix',
|
||||
'required': Eval('price_source_type') == 'matrix',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
fixed_price = fields.Numeric(
|
||||
"Fixed Price", digits=(16, 6),
|
||||
states={
|
||||
'readonly': Eval('price_source_type') != 'fixed',
|
||||
'required': Eval('price_source_type') == 'fixed',
|
||||
},
|
||||
depends=['price_source_type'])
|
||||
fixed_currency = fields.Many2One('currency.currency', "Fixed Curr.")
|
||||
currency = fields.Function(
|
||||
fields.Many2One(
|
||||
'currency.currency', "Curr.",
|
||||
states={
|
||||
'required': Eval('price_source_type') == 'fixed',
|
||||
},
|
||||
depends=['price_source_type']),
|
||||
'get_cur', setter='set_cur')
|
||||
auto = fields.Boolean("Auto")
|
||||
fallback = fields.Boolean("Fallback")
|
||||
calendar = fields.Many2One('price.calendar',"Calendar")
|
||||
nbdays = fields.Function(fields.Integer("Nb days"),'get_nbdays')
|
||||
triggers = fields.One2Many('pricing.trigger','component',"Period rules")
|
||||
pricing_date = fields.Date("Pricing date max")
|
||||
|
||||
def get_rec_name(self, name=None):
|
||||
if self.price_index:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
||||
if self.price_matrix:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_matrix.name
|
||||
if self.price_source_type == 'fixed':
|
||||
return '[' + self.fix_type.name + '] Fixed'
|
||||
else:
|
||||
return '[' + self.fix_type.name + '] '
|
||||
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
|
||||
@fields.depends(
|
||||
'price_source_type', 'price_index', 'price_matrix',
|
||||
'fixed_currency')
|
||||
def on_change_with_currency(self):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
|
||||
@classmethod
|
||||
def set_cur(cls, components, name, value):
|
||||
cls.write(components, {'fixed_currency': value})
|
||||
|
||||
def get_calendar(self):
|
||||
if self.calendar:
|
||||
return self.calendar
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.calendar
|
||||
|
||||
@staticmethod
|
||||
def _record_id(record):
|
||||
return getattr(record, 'id', record)
|
||||
|
||||
@classmethod
|
||||
def _matches_optional_field(cls, matrix_line, field_name, target):
|
||||
value = getattr(matrix_line, field_name, None)
|
||||
if not value:
|
||||
return True, 0
|
||||
if target and cls._record_id(value) == cls._record_id(target):
|
||||
return True, 1
|
||||
return False, 0
|
||||
|
||||
def _matrix_valid_on(self, price_date):
|
||||
if not self.price_matrix:
|
||||
return False
|
||||
if hasattr(price_date, 'date'):
|
||||
price_date = price_date.date()
|
||||
valid_from = getattr(self.price_matrix, 'valid_from', None)
|
||||
valid_to = getattr(self.price_matrix, 'valid_to', None)
|
||||
if valid_from and price_date < valid_from:
|
||||
return False
|
||||
if valid_to and price_date > valid_to:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_matrix_owner_values(self):
|
||||
line = getattr(self, 'line', None) or getattr(self, 'sale_line', None)
|
||||
document = getattr(line, 'purchase', None) or getattr(line, 'sale', None)
|
||||
return {
|
||||
'origin': getattr(document, 'from_location', None),
|
||||
'destination': getattr(document, 'to_location', None),
|
||||
'product': getattr(line, 'product', None),
|
||||
'quality': getattr(line, 'quality', None),
|
||||
}
|
||||
|
||||
def _get_matrix_line(self):
|
||||
MatrixLine = Pool().get('price.matrix.line')
|
||||
lines = MatrixLine.search([('matrix', '=', self.price_matrix.id)])
|
||||
owner_values = self._get_matrix_owner_values()
|
||||
best_line = None
|
||||
best_score = -1
|
||||
for line in lines:
|
||||
score = 0
|
||||
for field_name, target in owner_values.items():
|
||||
matches, field_score = self._matches_optional_field(
|
||||
line, field_name, target)
|
||||
if not matches:
|
||||
break
|
||||
score += field_score
|
||||
else:
|
||||
if score > best_score:
|
||||
best_line = line
|
||||
best_score = score
|
||||
return best_line
|
||||
|
||||
def _convert_matrix_price(self, price, unit, currency):
|
||||
price_qt = Decimal(str(price or 0))
|
||||
matrix_unit = getattr(self.price_matrix, 'unit', None)
|
||||
matrix_currency = getattr(self.price_matrix, 'currency', None)
|
||||
if matrix_unit and unit:
|
||||
Uom = Pool().get('product.uom')
|
||||
price_qt *= Decimal(str(
|
||||
Uom.compute_qty(unit, float(1), matrix_unit)))
|
||||
if matrix_currency and currency and currency != matrix_currency:
|
||||
Currency = Pool().get('currency.currency')
|
||||
rates = Currency._get_rate([matrix_currency])
|
||||
rate = rates.get(matrix_currency.id)
|
||||
if rate:
|
||||
price_qt *= Decimal(str(rate))
|
||||
return round(price_qt, 4)
|
||||
|
||||
def _convert_fixed_price(self, price, currency):
|
||||
price_qt = Decimal(str(price or 0))
|
||||
price_currency = getattr(self, 'fixed_currency', None)
|
||||
if price_currency and currency and currency != price_currency:
|
||||
Currency = Pool().get('currency.currency')
|
||||
rates = Currency._get_rate([price_currency])
|
||||
rate = rates.get(price_currency.id)
|
||||
if rate:
|
||||
price_qt *= Decimal(str(rate))
|
||||
return round(price_qt, 4)
|
||||
|
||||
def get_price(self, price_date, unit, currency, last=False):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.get_price(price_date, unit, currency, last)
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
if not self._matrix_valid_on(price_date):
|
||||
return Decimal(0)
|
||||
line = self._get_matrix_line()
|
||||
if line:
|
||||
return self._convert_matrix_price(
|
||||
line.price_value, unit, currency)
|
||||
if self.price_source_type == 'fixed':
|
||||
return self._convert_fixed_price(self.fixed_price, currency)
|
||||
return Decimal(0)
|
||||
|
||||
def get_nbdays(self, name):
|
||||
days = 0
|
||||
if self.triggers:
|
||||
for t in self.triggers:
|
||||
l,l2 = t.getApplicationListDates(self.get_calendar())
|
||||
days += len(l)
|
||||
return days
|
||||
def get_rec_name(self, name=None):
|
||||
if self.price_index:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
||||
if self.price_matrix:
|
||||
return '[' + self.fix_type.name + '] ' + self.price_matrix.name
|
||||
if self.price_source_type == 'fixed':
|
||||
return '[' + self.fix_type.name + '] Fixed'
|
||||
else:
|
||||
return '[' + self.fix_type.name + '] '
|
||||
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
|
||||
@fields.depends(
|
||||
'price_source_type', 'price_index', 'price_matrix',
|
||||
'fixed_currency')
|
||||
def on_change_with_currency(self):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
|
||||
@classmethod
|
||||
def set_cur(cls, components, name, value):
|
||||
cls.write(components, {'fixed_currency': value})
|
||||
|
||||
def get_calendar(self):
|
||||
if self.calendar:
|
||||
return self.calendar
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.calendar
|
||||
|
||||
@staticmethod
|
||||
def _record_id(record):
|
||||
return getattr(record, 'id', record)
|
||||
|
||||
@classmethod
|
||||
def _matches_optional_field(cls, matrix_line, field_name, target):
|
||||
value = getattr(matrix_line, field_name, None)
|
||||
if not value:
|
||||
return True, 0
|
||||
if target and cls._record_id(value) == cls._record_id(target):
|
||||
return True, 1
|
||||
return False, 0
|
||||
|
||||
def _matrix_valid_on(self, price_date):
|
||||
if not self.price_matrix:
|
||||
return False
|
||||
if hasattr(price_date, 'date'):
|
||||
price_date = price_date.date()
|
||||
valid_from = getattr(self.price_matrix, 'valid_from', None)
|
||||
valid_to = getattr(self.price_matrix, 'valid_to', None)
|
||||
if valid_from and price_date < valid_from:
|
||||
return False
|
||||
if valid_to and price_date > valid_to:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_matrix_owner_values(self):
|
||||
line = getattr(self, 'line', None) or getattr(self, 'sale_line', None)
|
||||
document = getattr(line, 'purchase', None) or getattr(line, 'sale', None)
|
||||
return {
|
||||
'origin': getattr(document, 'from_location', None),
|
||||
'destination': getattr(document, 'to_location', None),
|
||||
'product': getattr(line, 'product', None),
|
||||
'quality': getattr(line, 'quality', None),
|
||||
}
|
||||
|
||||
def _get_matrix_line(self):
|
||||
MatrixLine = Pool().get('price.matrix.line')
|
||||
lines = MatrixLine.search([('matrix', '=', self.price_matrix.id)])
|
||||
owner_values = self._get_matrix_owner_values()
|
||||
best_line = None
|
||||
best_score = -1
|
||||
for line in lines:
|
||||
score = 0
|
||||
for field_name, target in owner_values.items():
|
||||
matches, field_score = self._matches_optional_field(
|
||||
line, field_name, target)
|
||||
if not matches:
|
||||
break
|
||||
score += field_score
|
||||
else:
|
||||
if score > best_score:
|
||||
best_line = line
|
||||
best_score = score
|
||||
return best_line
|
||||
|
||||
def _convert_matrix_price(self, price, unit, currency):
|
||||
price_qt = Decimal(str(price or 0))
|
||||
matrix_unit = getattr(self.price_matrix, 'unit', None)
|
||||
matrix_currency = getattr(self.price_matrix, 'currency', None)
|
||||
if matrix_unit and unit:
|
||||
Uom = Pool().get('product.uom')
|
||||
price_qt *= Decimal(str(
|
||||
Uom.compute_qty(unit, float(1), matrix_unit)))
|
||||
if matrix_currency and currency and currency != matrix_currency:
|
||||
Currency = Pool().get('currency.currency')
|
||||
rates = Currency._get_rate([matrix_currency])
|
||||
rate = rates.get(matrix_currency.id)
|
||||
if rate:
|
||||
price_qt *= Decimal(str(rate))
|
||||
return round(price_qt, 4)
|
||||
|
||||
def _convert_fixed_price(self, price, currency):
|
||||
price_qt = Decimal(str(price or 0))
|
||||
price_currency = getattr(self, 'fixed_currency', None)
|
||||
if price_currency and currency and currency != price_currency:
|
||||
Currency = Pool().get('currency.currency')
|
||||
rates = Currency._get_rate([price_currency])
|
||||
rate = rates.get(price_currency.id)
|
||||
if rate:
|
||||
price_qt *= Decimal(str(rate))
|
||||
return round(price_qt, 4)
|
||||
|
||||
def get_price(self, price_date, unit, currency, last=False):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.get_price(price_date, unit, currency, last)
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
if not self._matrix_valid_on(price_date):
|
||||
return Decimal(0)
|
||||
line = self._get_matrix_line()
|
||||
if line:
|
||||
return self._convert_matrix_price(
|
||||
line.price_value, unit, currency)
|
||||
if self.price_source_type == 'fixed':
|
||||
return self._convert_fixed_price(self.fixed_price, currency)
|
||||
return Decimal(0)
|
||||
|
||||
def get_nbdays(self, name):
|
||||
days = 0
|
||||
if self.triggers:
|
||||
for t in self.triggers:
|
||||
l,l2 = t.getApplicationListDates(self.get_calendar())
|
||||
days += len(l)
|
||||
return days
|
||||
|
||||
@classmethod
|
||||
def delete(cls, components):
|
||||
@@ -1239,9 +1320,9 @@ class Trigger(ModelSQL,ModelView):
|
||||
states={
|
||||
'readonly': Eval('pricing_period') != None,
|
||||
})
|
||||
average = fields.Boolean("Avg")
|
||||
last = fields.Boolean("Latest")
|
||||
application_period = fields.Many2One('pricing.period',"Application period")
|
||||
average = fields.Boolean("Avg")
|
||||
last = fields.Boolean("Latest")
|
||||
application_period = fields.Many2One('pricing.period',"Application period")
|
||||
from_a = fields.Date("From",
|
||||
states={
|
||||
'readonly': Eval('application_period') != None,
|
||||
@@ -1249,26 +1330,26 @@ class Trigger(ModelSQL,ModelView):
|
||||
to_a = fields.Date("To",
|
||||
states={
|
||||
'readonly': Eval('application_period') != None,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def default_average(cls):
|
||||
return True
|
||||
|
||||
@fields.depends('pricing_period')
|
||||
def on_change_with_application_period(self):
|
||||
if not self.application_period and self.pricing_period:
|
||||
return self.pricing_period
|
||||
|
||||
@fields.depends('from_p', 'from_a', 'application_period')
|
||||
def on_change_from_p(self):
|
||||
if not self.application_period and not self.from_a:
|
||||
self.from_a = self.from_p
|
||||
|
||||
@fields.depends('to_p', 'to_a', 'application_period')
|
||||
def on_change_to_p(self):
|
||||
if not self.application_period and not self.to_a:
|
||||
self.to_a = self.to_p
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def default_average(cls):
|
||||
return True
|
||||
|
||||
@fields.depends('pricing_period')
|
||||
def on_change_with_application_period(self):
|
||||
if not self.application_period and self.pricing_period:
|
||||
return self.pricing_period
|
||||
|
||||
@fields.depends('from_p', 'from_a', 'application_period')
|
||||
def on_change_from_p(self):
|
||||
if not self.application_period and not self.from_a:
|
||||
self.from_a = self.from_p
|
||||
|
||||
@fields.depends('to_p', 'to_a', 'application_period')
|
||||
def on_change_to_p(self):
|
||||
if not self.application_period and not self.to_a:
|
||||
self.to_a = self.to_p
|
||||
|
||||
def getDateWithEstTrigger(self, period):
|
||||
PP = Pool().get('pricing.period')
|
||||
@@ -1285,73 +1366,73 @@ class Trigger(ModelSQL,ModelView):
|
||||
date_from,date_to,dates = pp.getDates(d)
|
||||
return date_from,date_to,d,pp.include,dates
|
||||
|
||||
def getApplicationListDates(self, cal):
|
||||
ld = []
|
||||
dates = []
|
||||
if self.application_period:
|
||||
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(2)
|
||||
else:
|
||||
date_from = self.from_a or self.from_p
|
||||
date_to = self.to_a or self.to_p
|
||||
d = None
|
||||
include = False
|
||||
ld, lprice = self.getListDates(date_from,date_to,d,include,cal,2,dates)
|
||||
return ld, lprice
|
||||
|
||||
def getPricingListDates(self,cal):
|
||||
ld = []
|
||||
dates = []
|
||||
if self.pricing_period:
|
||||
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(1)
|
||||
else:
|
||||
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)
|
||||
def getApplicationListDates(self, cal):
|
||||
ld = []
|
||||
dates = []
|
||||
if self.application_period:
|
||||
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(2)
|
||||
else:
|
||||
date_from = self.from_a or self.from_p
|
||||
date_to = self.to_a or self.to_p
|
||||
d = None
|
||||
include = False
|
||||
ld, lprice = self.getListDates(date_from,date_to,d,include,cal,2,dates)
|
||||
return ld, lprice
|
||||
|
||||
def getPricingListDates(self,cal):
|
||||
ld = []
|
||||
dates = []
|
||||
if self.pricing_period:
|
||||
date_from, date_to, d, include,dates = self.getDateWithEstTrigger(1)
|
||||
else:
|
||||
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)
|
||||
d = None
|
||||
include = False
|
||||
ld, lprice = self.getListDates(date_from,date_to,d,include,cal,1,dates)
|
||||
return ld, lprice
|
||||
|
||||
def getListDates(self,df,dt,t,i,cal,pricing,dates):
|
||||
l = []
|
||||
lprice = []
|
||||
def append_date(date):
|
||||
if cal:
|
||||
if not cal.IsQuote(date):
|
||||
return
|
||||
l.append(date)
|
||||
if pricing == 1:
|
||||
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 getListDates(self,df,dt,t,i,cal,pricing,dates):
|
||||
l = []
|
||||
lprice = []
|
||||
def append_date(date):
|
||||
if cal:
|
||||
if not cal.IsQuote(date):
|
||||
return
|
||||
l.append(date)
|
||||
if pricing == 1:
|
||||
lprice.append(self.getprice(date))
|
||||
|
||||
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
|
||||
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):
|
||||
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
|
||||
|
||||
class Period(ModelSQL,ModelView):
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<form col="4">
|
||||
<label name="file_structure"/>
|
||||
<field name="file_structure"/>
|
||||
<newline/>
|
||||
<label name="file_"/>
|
||||
<field name="file_"/>
|
||||
<newline/>
|
||||
|
||||
7
modules/trade_finance/AGENTS.md
Normal file
7
modules/trade_finance/AGENTS.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# AGENTS.md
|
||||
|
||||
Instructions locales pour les agents qui interviennent dans `modules/trade_finance/`.
|
||||
|
||||
- `trade_finance` est un module additionnel de Tryton standard.
|
||||
- Le traiter comme un module standard/upstream Tryton: privilegier les patterns Tryton existants, garder les changements generiques, et eviter d'y introduire de la logique metier specifique a un client sauf demande explicite.
|
||||
- Limiter les modifications a ce module et a ses tests proches quand la demande concerne `trade_finance`.
|
||||
414
modules/trade_finance/Documentation/backlog.md
Normal file
414
modules/trade_finance/Documentation/backlog.md
Normal 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.
|
||||
175
modules/trade_finance/Documentation/development_guidance.md
Normal file
175
modules/trade_finance/Documentation/development_guidance.md
Normal 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
66
modules/trade_finance/__init__.py
Normal file
66
modules/trade_finance/__init__.py
Normal 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')
|
||||
54
modules/trade_finance/constraint_type.py
Normal file
54
modules/trade_finance/constraint_type.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
__all__ = ['ConstraintType']
|
||||
|
||||
|
||||
class ConstraintType(ModelSQL, ModelView):
|
||||
'Constraint Type'
|
||||
__name__ = 'trade_finance.constraint_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char('Type', required=True, size=50)
|
||||
view_name = fields.Selection([], 'View')
|
||||
value_field = fields.Selection(
|
||||
'get_column_selection', 'Value Field',
|
||||
selection_change_with=['view_name'])
|
||||
label_field = fields.Selection(
|
||||
'get_column_selection', 'Label Field',
|
||||
selection_change_with=['view_name'])
|
||||
|
||||
@fields.depends('view_name')
|
||||
def get_column_selection(self):
|
||||
if not self.view_name:
|
||||
return [('', '')]
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute(
|
||||
"SELECT column_name FROM information_schema.columns "
|
||||
"WHERE table_schema = 'public' AND table_name = %s "
|
||||
"ORDER BY ordinal_position",
|
||||
(self.view_name,)
|
||||
)
|
||||
return [('', '')] + [(r[0], r[0]) for r in cursor.fetchall()]
|
||||
|
||||
@fields.depends('view_name')
|
||||
def on_change_view_name(self):
|
||||
self.value_field = None
|
||||
self.label_field = None
|
||||
|
||||
@classmethod
|
||||
def fields_get(cls, fields_names=None, level=0):
|
||||
res = super().fields_get(fields_names, level)
|
||||
if 'view_name' in res:
|
||||
cursor = Transaction().connection.cursor()
|
||||
cursor.execute(
|
||||
"SELECT table_name FROM information_schema.views "
|
||||
"WHERE table_schema = 'public' AND table_name LIKE 'vw_tf_const_%' "
|
||||
"ORDER BY table_name"
|
||||
)
|
||||
res['view_name']['selection'] = (
|
||||
[('', '')] + [(r[0], r[0]) for r in cursor.fetchall()])
|
||||
return res
|
||||
66
modules/trade_finance/constraint_type.xml
Normal file
66
modules/trade_finance/constraint_type.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- CONSTRAINT TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="constraint_type_view_tree">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">constraint_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="constraint_type_view_form">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">constraint_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_constraint_type">
|
||||
<field name="name">Constraint Types</field>
|
||||
<field name="res_model">trade_finance.constraint_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_constraint_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="constraint_type_view_tree"/>
|
||||
<field name="act_window" ref="act_constraint_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_constraint_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="constraint_type_view_form"/>
|
||||
<field name="act_window" ref="act_constraint_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Constraint Types"
|
||||
sequence="10"
|
||||
id="menu_constraint_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_constraint_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_constraint_type">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_constraint_type_user">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="group" ref="group_trade_finance_user"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_constraint_type_admin">
|
||||
<field name="model">trade_finance.constraint_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
39
modules/trade_finance/counterparty.py
Normal file
39
modules/trade_finance/counterparty.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['ReceivableCategory', 'PaymentConditionType']
|
||||
|
||||
|
||||
class ReceivableCategory(ModelSQL, ModelView):
|
||||
'Receivable Category'
|
||||
__name__ = 'trade_finance.receivable_category'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class PaymentConditionType(ModelSQL, ModelView):
|
||||
'Payment Condition Type'
|
||||
__name__ = 'trade_finance.payment_condition_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
remaining_risk_pct = fields.Numeric(
|
||||
'Remaining Risk (%)', digits=(16, 2),
|
||||
help='Percentage of residual credit risk retained by the company '
|
||||
'under this payment condition')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
21
modules/trade_finance/document.py
Normal file
21
modules/trade_finance/document.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['EvidenceType']
|
||||
|
||||
|
||||
class EvidenceType(ModelSQL, ModelView):
|
||||
'Evidence Type'
|
||||
__name__ = 'trade_finance.evidence_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
646
modules/trade_finance/facility.py
Normal file
646
modules/trade_finance/facility.py
Normal 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
|
||||
448
modules/trade_finance/facility.xml
Normal file
448
modules/trade_finance/facility.xml
Normal 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>
|
||||
47
modules/trade_finance/financing.py
Normal file
47
modules/trade_finance/financing.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['FinancingType', 'OperationalStatus']
|
||||
|
||||
|
||||
class FinancingType(ModelSQL, ModelView):
|
||||
'Financing Type'
|
||||
__name__ = 'trade_finance.financing_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
sequence = fields.Integer('Sequence')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def default_sequence():
|
||||
return 10
|
||||
|
||||
|
||||
class OperationalStatus(ModelSQL, ModelView):
|
||||
'Operational Status'
|
||||
__name__ = 'trade_finance.operational_status'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
financing_type = fields.Many2One(
|
||||
'trade_finance.financing_type', 'Default Financing Type',
|
||||
ondelete='RESTRICT')
|
||||
sequence = fields.Integer('Sequence')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def default_sequence():
|
||||
return 10
|
||||
37
modules/trade_finance/fx.py
Normal file
37
modules/trade_finance/fx.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
__all__ = ['FxFeeder', 'PriceCalendar']
|
||||
|
||||
|
||||
class FxFeeder(ModelSQL, ModelView):
|
||||
'FX Rate Feeder'
|
||||
__name__ = 'trade_finance.fx_feeder'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
source_description = fields.Text(
|
||||
'Source Description',
|
||||
help='Description of the FX rate source / provider')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class PriceCalendar(metaclass=PoolMeta):
|
||||
__name__ = 'price.calendar'
|
||||
|
||||
purpose = fields.Selection([
|
||||
(None, ''),
|
||||
('banking', 'Banking'),
|
||||
('market', 'Market'),
|
||||
('factoring', 'Factoring'),
|
||||
], 'Purpose',
|
||||
help='Use of this calendar: Banking days, Market trading days, '
|
||||
'or Factoring program calendar')
|
||||
64
modules/trade_finance/market.py
Normal file
64
modules/trade_finance/market.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['MarketIndex', 'MarketIndexRate', 'IndexTerm', 'InterestFormula']
|
||||
|
||||
|
||||
class MarketIndex(ModelSQL, ModelView):
|
||||
'Market Index'
|
||||
__name__ = 'trade_finance.market_index'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
rates = fields.One2Many(
|
||||
'trade_finance.market_index_rate', 'index', 'Rates')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class MarketIndexRate(ModelSQL, ModelView):
|
||||
'Market Index Rate'
|
||||
__name__ = 'trade_finance.market_index_rate'
|
||||
|
||||
index = fields.Many2One(
|
||||
'trade_finance.market_index', 'Index',
|
||||
required=True, ondelete='CASCADE')
|
||||
rate_date = fields.Date('Date', required=True)
|
||||
rate = fields.Numeric('Rate (%)', digits=(16, 6), required=True)
|
||||
|
||||
|
||||
class IndexTerm(ModelSQL, ModelView):
|
||||
'Index Term'
|
||||
__name__ = 'trade_finance.index_term'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
days = fields.Integer('Days', required=True,
|
||||
help='Approximate number of days for this term (O/N=1, 1W=7, 1M=30, '
|
||||
'3M=90, 6M=180, 1Y=365)')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class InterestFormula(ModelSQL, ModelView):
|
||||
'Interest Calculation Formula'
|
||||
__name__ = 'trade_finance.interest_formula'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
35
modules/trade_finance/operational.py
Normal file
35
modules/trade_finance/operational.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['BlockingReason', 'ChargeType']
|
||||
|
||||
|
||||
class BlockingReason(ModelSQL, ModelView):
|
||||
'Blocking Reason'
|
||||
__name__ = 'trade_finance.blocking_reason'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ChargeType(ModelSQL, ModelView):
|
||||
'Charge Type'
|
||||
__name__ = 'trade_finance.charge_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
947
modules/trade_finance/reference.xml
Normal file
947
modules/trade_finance/reference.xml
Normal file
@@ -0,0 +1,947 @@
|
||||
<?xml version="1.0"?>
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- ACCESS GROUPS -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="res.group" id="group_trade_finance_admin">
|
||||
<field name="name">Trade Finance Administration</field>
|
||||
</record>
|
||||
<record model="res.user-res.group"
|
||||
id="user_admin_group_trade_finance_admin">
|
||||
<field name="user" ref="res.user_admin"/>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- TOP-LEVEL MENU -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<menuitem
|
||||
name="Trade Finance"
|
||||
sequence="70"
|
||||
id="menu_trade_finance"/>
|
||||
|
||||
<menuitem
|
||||
name="Configuration"
|
||||
sequence="99"
|
||||
id="menu_trade_finance_configuration"
|
||||
parent="menu_trade_finance"/>
|
||||
|
||||
<menuitem
|
||||
name="Market Data"
|
||||
sequence="20"
|
||||
id="menu_trade_finance_market_data"
|
||||
parent="menu_trade_finance"/>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FINANCING TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="financing_type_view_tree">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">financing_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="financing_type_view_form">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">financing_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_financing_type">
|
||||
<field name="name">Financing Types</field>
|
||||
<field name="res_model">trade_finance.financing_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_financing_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="financing_type_view_tree"/>
|
||||
<field name="act_window" ref="act_financing_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_financing_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="financing_type_view_form"/>
|
||||
<field name="act_window" ref="act_financing_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Financing Types"
|
||||
sequence="10"
|
||||
id="menu_financing_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_financing_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_financing_type">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_financing_type_admin">
|
||||
<field name="model">trade_finance.financing_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- OPERATIONAL STATUS -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="operational_status_view_tree">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">operational_status_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="operational_status_view_form">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">operational_status_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_operational_status">
|
||||
<field name="name">Operational Statuses</field>
|
||||
<field name="res_model">trade_finance.operational_status</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_operational_status_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="operational_status_view_tree"/>
|
||||
<field name="act_window" ref="act_operational_status"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_operational_status_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="operational_status_view_form"/>
|
||||
<field name="act_window" ref="act_operational_status"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Operational Statuses"
|
||||
sequence="20"
|
||||
id="menu_operational_status"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_operational_status"/>
|
||||
|
||||
<record model="ir.model.access" id="access_operational_status">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_operational_status_admin">
|
||||
<field name="model">trade_finance.operational_status</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- MARKET INDEX -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="market_index_view_tree">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">market_index_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="market_index_view_form">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">market_index_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_market_index">
|
||||
<field name="name">Market Indexes</field>
|
||||
<field name="res_model">trade_finance.market_index</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="market_index_view_tree"/>
|
||||
<field name="act_window" ref="act_market_index"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="market_index_view_form"/>
|
||||
<field name="act_window" ref="act_market_index"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Market Indexes"
|
||||
sequence="10"
|
||||
id="menu_market_index"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_market_index"/>
|
||||
|
||||
<record model="ir.model.access" id="access_market_index">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_market_index_admin">
|
||||
<field name="model">trade_finance.market_index</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- MARKET INDEX RATE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="market_index_rate_view_tree">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">market_index_rate_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="market_index_rate_view_form">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">market_index_rate_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_market_index_rate">
|
||||
<field name="name">Market Index Rates</field>
|
||||
<field name="res_model">trade_finance.market_index_rate</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_rate_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="market_index_rate_view_tree"/>
|
||||
<field name="act_window" ref="act_market_index_rate"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_market_index_rate_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="market_index_rate_view_form"/>
|
||||
<field name="act_window" ref="act_market_index_rate"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Market Index Rates"
|
||||
sequence="10"
|
||||
id="menu_market_index_rate"
|
||||
parent="menu_trade_finance_market_data"
|
||||
action="act_market_index_rate"/>
|
||||
|
||||
<record model="ir.model.access" id="access_market_index_rate">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_market_index_rate_admin">
|
||||
<field name="model">trade_finance.market_index_rate</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- INDEX TERM -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="index_term_view_tree">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">index_term_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="index_term_view_form">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">index_term_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_index_term">
|
||||
<field name="name">Index Terms</field>
|
||||
<field name="res_model">trade_finance.index_term</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_index_term_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="index_term_view_tree"/>
|
||||
<field name="act_window" ref="act_index_term"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_index_term_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="index_term_view_form"/>
|
||||
<field name="act_window" ref="act_index_term"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Index Terms"
|
||||
sequence="30"
|
||||
id="menu_index_term"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_index_term"/>
|
||||
|
||||
<record model="ir.model.access" id="access_index_term">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_index_term_admin">
|
||||
<field name="model">trade_finance.index_term</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- INTEREST FORMULA -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="interest_formula_view_tree">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">interest_formula_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="interest_formula_view_form">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">interest_formula_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_interest_formula">
|
||||
<field name="name">Interest Formulas</field>
|
||||
<field name="res_model">trade_finance.interest_formula</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_interest_formula_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="interest_formula_view_tree"/>
|
||||
<field name="act_window" ref="act_interest_formula"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_interest_formula_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="interest_formula_view_form"/>
|
||||
<field name="act_window" ref="act_interest_formula"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Interest Formulas"
|
||||
sequence="40"
|
||||
id="menu_interest_formula"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_interest_formula"/>
|
||||
|
||||
<record model="ir.model.access" id="access_interest_formula">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_interest_formula_admin">
|
||||
<field name="model">trade_finance.interest_formula</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- HAIRCUT FORMULA -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="haircut_formula_view_tree">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">haircut_formula_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="haircut_formula_view_form">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">haircut_formula_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_haircut_formula">
|
||||
<field name="name">Haircut Formulas</field>
|
||||
<field name="res_model">trade_finance.haircut_formula</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_haircut_formula_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="haircut_formula_view_tree"/>
|
||||
<field name="act_window" ref="act_haircut_formula"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_haircut_formula_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="haircut_formula_view_form"/>
|
||||
<field name="act_window" ref="act_haircut_formula"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Haircut Formulas"
|
||||
sequence="50"
|
||||
id="menu_haircut_formula"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_haircut_formula"/>
|
||||
|
||||
<record model="ir.model.access" id="access_haircut_formula">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_haircut_formula_admin">
|
||||
<field name="model">trade_finance.haircut_formula</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- VALUATION METHOD -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="valuation_method_view_tree">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">valuation_method_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_method_view_form">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">valuation_method_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_valuation_method">
|
||||
<field name="name">Valuation Methods</field>
|
||||
<field name="res_model">trade_finance.valuation_method</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_valuation_method_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="valuation_method_view_tree"/>
|
||||
<field name="act_window" ref="act_valuation_method"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_valuation_method_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="valuation_method_view_form"/>
|
||||
<field name="act_window" ref="act_valuation_method"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Valuation Methods"
|
||||
sequence="60"
|
||||
id="menu_valuation_method"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_valuation_method"/>
|
||||
|
||||
<record model="ir.model.access" id="access_valuation_method">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_valuation_method_admin">
|
||||
<field name="model">trade_finance.valuation_method</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- COLLATERAL TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="collateral_type_view_tree">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">collateral_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="collateral_type_view_form">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">collateral_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_collateral_type">
|
||||
<field name="name">Collateral Types</field>
|
||||
<field name="res_model">trade_finance.collateral_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_collateral_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="collateral_type_view_tree"/>
|
||||
<field name="act_window" ref="act_collateral_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_collateral_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="collateral_type_view_form"/>
|
||||
<field name="act_window" ref="act_collateral_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Collateral Types"
|
||||
sequence="70"
|
||||
id="menu_collateral_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_collateral_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_collateral_type">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_collateral_type_admin">
|
||||
<field name="model">trade_finance.collateral_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- EVIDENCE TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="evidence_type_view_tree">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">evidence_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="evidence_type_view_form">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">evidence_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_evidence_type">
|
||||
<field name="name">Evidence Types</field>
|
||||
<field name="res_model">trade_finance.evidence_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_evidence_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="evidence_type_view_tree"/>
|
||||
<field name="act_window" ref="act_evidence_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_evidence_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="evidence_type_view_form"/>
|
||||
<field name="act_window" ref="act_evidence_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Evidence Types"
|
||||
sequence="80"
|
||||
id="menu_evidence_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_evidence_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_evidence_type">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_evidence_type_admin">
|
||||
<field name="model">trade_finance.evidence_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- TEMPLATE SEGMENT -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="template_segment_view_tree">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">template_segment_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="template_segment_view_form">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_segment_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_template_segment">
|
||||
<field name="name">Template Segments</field>
|
||||
<field name="res_model">trade_finance.template_segment</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_template_segment_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="template_segment_view_tree"/>
|
||||
<field name="act_window" ref="act_template_segment"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_template_segment_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="template_segment_view_form"/>
|
||||
<field name="act_window" ref="act_template_segment"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Template Segments"
|
||||
sequence="90"
|
||||
id="menu_template_segment"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_template_segment"/>
|
||||
|
||||
<record model="ir.model.access" id="access_template_segment">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_template_segment_admin">
|
||||
<field name="model">trade_finance.template_segment</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- EXECUTION TEMPLATE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="execution_template_view_tree">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">execution_template_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="execution_template_view_form">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">execution_template_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_execution_template">
|
||||
<field name="name">Execution Templates</field>
|
||||
<field name="res_model">trade_finance.execution_template</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_execution_template_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="execution_template_view_tree"/>
|
||||
<field name="act_window" ref="act_execution_template"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_execution_template_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="execution_template_view_form"/>
|
||||
<field name="act_window" ref="act_execution_template"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Execution Templates"
|
||||
sequence="100"
|
||||
id="menu_execution_template"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_execution_template"/>
|
||||
|
||||
<record model="ir.model.access" id="access_execution_template">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_execution_template_admin">
|
||||
<field name="model">trade_finance.execution_template</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ExecutionTemplateLine — no separate menu (child of ExecutionTemplate) -->
|
||||
<record model="ir.ui.view" id="execution_template_line_view_tree">
|
||||
<field name="model">trade_finance.execution_template_line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">execution_template_line_tree</field>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_execution_template_line">
|
||||
<field name="model">trade_finance.execution_template_line</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_execution_template_line_admin">
|
||||
<field name="model">trade_finance.execution_template_line</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- RECEIVABLE CATEGORY -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="receivable_category_view_tree">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">receivable_category_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="receivable_category_view_form">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">receivable_category_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_receivable_category">
|
||||
<field name="name">Receivable Categories</field>
|
||||
<field name="res_model">trade_finance.receivable_category</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_receivable_category_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="receivable_category_view_tree"/>
|
||||
<field name="act_window" ref="act_receivable_category"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_receivable_category_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="receivable_category_view_form"/>
|
||||
<field name="act_window" ref="act_receivable_category"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Receivable Categories"
|
||||
sequence="110"
|
||||
id="menu_receivable_category"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_receivable_category"/>
|
||||
|
||||
<record model="ir.model.access" id="access_receivable_category">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_receivable_category_admin">
|
||||
<field name="model">trade_finance.receivable_category</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- PAYMENT CONDITION TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="payment_condition_type_view_tree">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">payment_condition_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="payment_condition_type_view_form">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">payment_condition_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_payment_condition_type">
|
||||
<field name="name">Payment Condition Types</field>
|
||||
<field name="res_model">trade_finance.payment_condition_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_payment_condition_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="payment_condition_type_view_tree"/>
|
||||
<field name="act_window" ref="act_payment_condition_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_payment_condition_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="payment_condition_type_view_form"/>
|
||||
<field name="act_window" ref="act_payment_condition_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Payment Condition Types"
|
||||
sequence="120"
|
||||
id="menu_payment_condition_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_payment_condition_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_payment_condition_type">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_payment_condition_type_admin">
|
||||
<field name="model">trade_finance.payment_condition_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- FX FEEDER -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="fx_feeder_view_tree">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">fx_feeder_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="fx_feeder_view_form">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">fx_feeder_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_fx_feeder">
|
||||
<field name="name">FX Rate Feeders</field>
|
||||
<field name="res_model">trade_finance.fx_feeder</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_fx_feeder_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="fx_feeder_view_tree"/>
|
||||
<field name="act_window" ref="act_fx_feeder"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_fx_feeder_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="fx_feeder_view_form"/>
|
||||
<field name="act_window" ref="act_fx_feeder"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="FX Rate Feeders"
|
||||
sequence="130"
|
||||
id="menu_fx_feeder"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_fx_feeder"/>
|
||||
|
||||
<record model="ir.model.access" id="access_fx_feeder">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_fx_feeder_admin">
|
||||
<field name="model">trade_finance.fx_feeder</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- BLOCKING REASON -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="blocking_reason_view_tree">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">blocking_reason_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="blocking_reason_view_form">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">blocking_reason_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_blocking_reason">
|
||||
<field name="name">Blocking Reasons</field>
|
||||
<field name="res_model">trade_finance.blocking_reason</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_blocking_reason_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="blocking_reason_view_tree"/>
|
||||
<field name="act_window" ref="act_blocking_reason"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_blocking_reason_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="blocking_reason_view_form"/>
|
||||
<field name="act_window" ref="act_blocking_reason"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Blocking Reasons"
|
||||
sequence="140"
|
||||
id="menu_blocking_reason"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_blocking_reason"/>
|
||||
|
||||
<record model="ir.model.access" id="access_blocking_reason">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_blocking_reason_admin">
|
||||
<field name="model">trade_finance.blocking_reason</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ================================================================ -->
|
||||
<!-- CHARGE TYPE -->
|
||||
<!-- ================================================================ -->
|
||||
|
||||
<record model="ir.ui.view" id="charge_type_view_tree">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority" eval="10"/>
|
||||
<field name="name">charge_type_tree</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="charge_type_view_form">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">charge_type_form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_charge_type">
|
||||
<field name="name">Charge Types</field>
|
||||
<field name="res_model">trade_finance.charge_type</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_charge_type_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="charge_type_view_tree"/>
|
||||
<field name="act_window" ref="act_charge_type"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_charge_type_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="charge_type_view_form"/>
|
||||
<field name="act_window" ref="act_charge_type"/>
|
||||
</record>
|
||||
<menuitem
|
||||
name="Charge Types"
|
||||
sequence="150"
|
||||
id="menu_charge_type"
|
||||
parent="menu_trade_finance_configuration"
|
||||
action="act_charge_type"/>
|
||||
|
||||
<record model="ir.model.access" id="access_charge_type">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_charge_type_admin">
|
||||
<field name="model">trade_finance.charge_type</field>
|
||||
<field name="group" ref="group_trade_finance_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
61
modules/trade_finance/template_execution.py
Normal file
61
modules/trade_finance/template_execution.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields, sequence_ordered
|
||||
|
||||
__all__ = ['TemplateSegment', 'ExecutionTemplate', 'ExecutionTemplateLine']
|
||||
|
||||
|
||||
class TemplateSegment(ModelSQL, ModelView):
|
||||
'Template Segment'
|
||||
__name__ = 'trade_finance.template_segment'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
from_place = fields.Many2One(
|
||||
'stock.location', 'From Place',
|
||||
help='Origin location / port of loading')
|
||||
to_place = fields.Many2One(
|
||||
'stock.location', 'To Place',
|
||||
help='Destination location / port of discharge')
|
||||
default_duration_days = fields.Integer(
|
||||
'Default Duration (days)',
|
||||
help='Default number of days for this segment')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ExecutionTemplate(ModelSQL, ModelView):
|
||||
'Execution Template'
|
||||
__name__ = 'trade_finance.execution_template'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
lines = fields.One2Many(
|
||||
'trade_finance.execution_template_line', 'template', 'Segments')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class ExecutionTemplateLine(sequence_ordered(), ModelSQL, ModelView):
|
||||
'Execution Template Line'
|
||||
__name__ = 'trade_finance.execution_template_line'
|
||||
|
||||
template = fields.Many2One(
|
||||
'trade_finance.execution_template', 'Template',
|
||||
required=True, ondelete='CASCADE')
|
||||
segment = fields.Many2One(
|
||||
'trade_finance.template_segment', 'Segment',
|
||||
required=True, ondelete='RESTRICT')
|
||||
duration_days = fields.Integer(
|
||||
'Duration (days)',
|
||||
help='Overrides the segment default duration for this template')
|
||||
2
modules/trade_finance/tests/__init__.py
Normal file
2
modules/trade_finance/tests/__init__.py
Normal 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.
|
||||
328
modules/trade_finance/tests/test_module.py
Normal file
328
modules/trade_finance/tests/test_module.py
Normal 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
|
||||
854
modules/trade_finance/trade.py
Normal file
854
modules/trade_finance/trade.py
Normal 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, {}
|
||||
254
modules/trade_finance/trade.xml
Normal file
254
modules/trade_finance/trade.xml
Normal 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>
|
||||
13
modules/trade_finance/tryton.cfg
Normal file
13
modules/trade_finance/tryton.cfg
Normal 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
|
||||
50
modules/trade_finance/valuation.py
Normal file
50
modules/trade_finance/valuation.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# This file is part of Tradon. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model import ModelSQL, ModelView, fields
|
||||
|
||||
__all__ = ['ValuationMethod', 'HaircutFormula', 'CollateralType']
|
||||
|
||||
|
||||
class ValuationMethod(ModelSQL, ModelView):
|
||||
'Valuation Method'
|
||||
__name__ = 'trade_finance.valuation_method'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class HaircutFormula(ModelSQL, ModelView):
|
||||
'Haircut Formula'
|
||||
__name__ = 'trade_finance.haircut_formula'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
description = fields.Text('Description')
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
|
||||
|
||||
class CollateralType(ModelSQL, ModelView):
|
||||
'Collateral Type'
|
||||
__name__ = 'trade_finance.collateral_type'
|
||||
_rec_name = 'name'
|
||||
|
||||
code = fields.Char('Code', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
active = fields.Boolean('Active')
|
||||
|
||||
@staticmethod
|
||||
def default_active():
|
||||
return True
|
||||
8
modules/trade_finance/view/blocking_reason_form.xml
Normal file
8
modules/trade_finance/view/blocking_reason_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/blocking_reason_tree.xml
Normal file
5
modules/trade_finance/view/blocking_reason_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/charge_type_form.xml
Normal file
10
modules/trade_finance/view/charge_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/charge_type_tree.xml
Normal file
5
modules/trade_finance/view/charge_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/collateral_type_form.xml
Normal file
8
modules/trade_finance/view/collateral_type_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/collateral_type_tree.xml
Normal file
5
modules/trade_finance/view/collateral_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/constraint_type_form.xml
Normal file
12
modules/trade_finance/view/constraint_type_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<group id="main" col="4" colspan="4">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<label name="view_name"/>
|
||||
<field name="view_name" colspan="3"/>
|
||||
<label name="value_field"/>
|
||||
<field name="value_field" colspan="3"/>
|
||||
<label name="label_field"/>
|
||||
<field name="label_field" colspan="3"/>
|
||||
</group>
|
||||
</form>
|
||||
3
modules/trade_finance/view/constraint_type_tree.xml
Normal file
3
modules/trade_finance/view/constraint_type_tree.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/evidence_type_form.xml
Normal file
10
modules/trade_finance/view/evidence_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/evidence_type_tree.xml
Normal file
5
modules/trade_finance/view/evidence_type_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/execution_template_form.xml
Normal file
12
modules/trade_finance/view/execution_template_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
<newline/>
|
||||
<field name="lines" colspan="4"/>
|
||||
</form>
|
||||
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="segment"/>
|
||||
<field name="duration_days"/>
|
||||
</tree>
|
||||
5
modules/trade_finance/view/execution_template_tree.xml
Normal file
5
modules/trade_finance/view/execution_template_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
7
modules/trade_finance/view/facility_cap_haircut_tree.xml
Normal file
7
modules/trade_finance/view/facility_cap_haircut_tree.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
6
modules/trade_finance/view/facility_cap_tree.xml
Normal file
6
modules/trade_finance/view/facility_cap_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="amount"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/facility_constraint_form.xml
Normal file
10
modules/trade_finance/view/facility_constraint_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="constraint_type"/>
|
||||
<field name="constraint_type" colspan="3"/>
|
||||
<label name="date_from"/>
|
||||
<field name="date_from"/>
|
||||
<label name="date_to"/>
|
||||
<field name="date_to"/>
|
||||
<label name="is_exclusion"/>
|
||||
<field name="is_exclusion"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/facility_constraint_tree.xml
Normal file
6
modules/trade_finance/view/facility_constraint_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="constraint_type"/>
|
||||
<field name="is_exclusion"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
6
modules/trade_finance/view/facility_covenant_tree.xml
Normal file
6
modules/trade_finance/view/facility_covenant_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="ratio_type"/>
|
||||
<field name="threshold"/>
|
||||
<field name="currency"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/facility_currency_tree.xml
Normal file
8
modules/trade_finance/view/facility_currency_tree.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="currency"/>
|
||||
<field name="valuation_method"/>
|
||||
<field name="fx_haircut_formula"/>
|
||||
<field name="fx_feeder"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
47
modules/trade_finance/view/facility_form.xml
Normal file
47
modules/trade_finance/view/facility_form.xml
Normal 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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="bank_account"/>
|
||||
<field name="currency"/>
|
||||
<field name="is_default"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
11
modules/trade_finance/view/facility_limit_cost_tree.xml
Normal file
11
modules/trade_finance/view/facility_limit_cost_tree.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<tree>
|
||||
<field name="cost_type"/>
|
||||
<field name="spread"/>
|
||||
<field name="index"/>
|
||||
<field name="index_term"/>
|
||||
<field name="interest_formula"/>
|
||||
<field name="flat_amount"/>
|
||||
<field name="flat_currency"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="variation_type"/>
|
||||
<field name="variation_value"/>
|
||||
<field name="variation_currency"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="currency"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="fx_feeder"/>
|
||||
<field name="valuation_method"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
53
modules/trade_finance/view/facility_limit_form.xml
Normal file
53
modules/trade_finance/view/facility_limit_form.xml
Normal 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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="attribute"/>
|
||||
<field name="value"/>
|
||||
<field name="haircut_pct"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
@@ -0,0 +1,4 @@
|
||||
<tree>
|
||||
<field name="operational_status"/>
|
||||
<field name="evidence_type"/>
|
||||
</tree>
|
||||
11
modules/trade_finance/view/facility_limit_tree.xml
Normal file
11
modules/trade_finance/view/facility_limit_tree.xml
Normal 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>
|
||||
10
modules/trade_finance/view/facility_status_form.xml
Normal file
10
modules/trade_finance/view/facility_status_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<group id="main" col="4" colspan="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
</group>
|
||||
</form>
|
||||
5
modules/trade_finance/view/facility_status_tree.xml
Normal file
5
modules/trade_finance/view/facility_status_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
9
modules/trade_finance/view/facility_tree.xml
Normal file
9
modules/trade_finance/view/facility_tree.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="tfe"/>
|
||||
<field name="currency"/>
|
||||
<field name="status"/>
|
||||
<field name="commitment_status"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/financing_type_form.xml
Normal file
10
modules/trade_finance/view/financing_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/financing_type_tree.xml
Normal file
6
modules/trade_finance/view/financing_type_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/fx_feeder_form.xml
Normal file
10
modules/trade_finance/view/fx_feeder_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="source_description"/>
|
||||
<field name="source_description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/fx_feeder_tree.xml
Normal file
5
modules/trade_finance/view/fx_feeder_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/haircut_formula_form.xml
Normal file
10
modules/trade_finance/view/haircut_formula_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/haircut_formula_tree.xml
Normal file
5
modules/trade_finance/view/haircut_formula_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/index_term_form.xml
Normal file
10
modules/trade_finance/view/index_term_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="days"/>
|
||||
<field name="days"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
6
modules/trade_finance/view/index_term_tree.xml
Normal file
6
modules/trade_finance/view/index_term_tree.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="days"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
8
modules/trade_finance/view/interest_formula_form.xml
Normal file
8
modules/trade_finance/view/interest_formula_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/interest_formula_tree.xml
Normal file
5
modules/trade_finance/view/interest_formula_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/market_index_form.xml
Normal file
10
modules/trade_finance/view/market_index_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<newline/>
|
||||
<field name="rates" colspan="4"/>
|
||||
</form>
|
||||
8
modules/trade_finance/view/market_index_rate_form.xml
Normal file
8
modules/trade_finance/view/market_index_rate_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<form col="4">
|
||||
<label name="index"/>
|
||||
<field name="index"/>
|
||||
<label name="rate_date"/>
|
||||
<field name="rate_date"/>
|
||||
<label name="rate"/>
|
||||
<field name="rate"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/market_index_rate_tree.xml
Normal file
5
modules/trade_finance/view/market_index_rate_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="index"/>
|
||||
<field name="rate_date"/>
|
||||
<field name="rate"/>
|
||||
</tree>
|
||||
5
modules/trade_finance/view/market_index_tree.xml
Normal file
5
modules/trade_finance/view/market_index_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
12
modules/trade_finance/view/operational_status_form.xml
Normal file
12
modules/trade_finance/view/operational_status_form.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="financing_type"/>
|
||||
<field name="financing_type"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
7
modules/trade_finance/view/operational_status_tree.xml
Normal file
7
modules/trade_finance/view/operational_status_tree.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<tree>
|
||||
<field name="sequence"/>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="financing_type"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/payment_condition_type_form.xml
Normal file
10
modules/trade_finance/view/payment_condition_type_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="remaining_risk_pct"/>
|
||||
<field name="remaining_risk_pct"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
@@ -0,0 +1,6 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="remaining_risk_pct"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
10
modules/trade_finance/view/receivable_category_form.xml
Normal file
10
modules/trade_finance/view/receivable_category_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/receivable_category_tree.xml
Normal file
5
modules/trade_finance/view/receivable_category_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
14
modules/trade_finance/view/template_segment_form.xml
Normal file
14
modules/trade_finance/view/template_segment_form.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="from_place"/>
|
||||
<field name="from_place"/>
|
||||
<label name="to_place"/>
|
||||
<field name="to_place"/>
|
||||
<label name="default_duration_days"/>
|
||||
<field name="default_duration_days"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
</form>
|
||||
8
modules/trade_finance/view/template_segment_tree.xml
Normal file
8
modules/trade_finance/view/template_segment_tree.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="from_place"/>
|
||||
<field name="to_place"/>
|
||||
<field name="default_duration_days"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
@@ -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>
|
||||
36
modules/trade_finance/view/trade_finance_form.xml
Normal file
36
modules/trade_finance/view/trade_finance_form.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
11
modules/trade_finance/view/trade_finance_tree.xml
Normal file
11
modules/trade_finance/view/trade_finance_tree.xml
Normal 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>
|
||||
10
modules/trade_finance/view/valuation_method_form.xml
Normal file
10
modules/trade_finance/view/valuation_method_form.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<form col="4">
|
||||
<label name="code"/>
|
||||
<field name="code"/>
|
||||
<label name="name"/>
|
||||
<field name="name"/>
|
||||
<label name="active"/>
|
||||
<field name="active"/>
|
||||
<label name="description"/>
|
||||
<field name="description" colspan="4"/>
|
||||
</form>
|
||||
5
modules/trade_finance/view/valuation_method_tree.xml
Normal file
5
modules/trade_finance/view/valuation_method_tree.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<tree>
|
||||
<field name="code"/>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user