647 lines
25 KiB
Python
647 lines
25 KiB
Python
# 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
|