Files
tradon/modules/trade_finance/trade.py
2026-06-02 14:53:07 +02:00

855 lines
32 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 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, {}