# 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, {}