# forex.py from trytond.model import ModelSingleton, ModelSQL, ModelView, fields from trytond.pool import PoolMeta, Pool from trytond.pyson import Bool, Eval, Id, If from trytond.transaction import Transaction, check_access from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction import jwt import datetime from decimal import Decimal import logging logger = logging.getLogger(__name__) __all__ = ['Forex', 'ForexCoverPhysicalContract', 'ForexCoverFees'] __metaclass__ = PoolMeta class Forex(ModelSQL, ModelView): 'Forex Deal' __name__ = 'forex.forex' _rec_name = 'number' company = fields.Many2One('company.company', 'Company', required=True) number = fields.Char('Number', required=True) ex_no_ctr = fields.Char('Internal Ref') date = fields.Date('Date', required=True) value_date = fields.Date('Maturity Date') buy_currency = fields.Many2One('currency.currency', 'Buy Currency', required=True) buy_amount = fields.Numeric('Buy Amount', digits=(16, 2)) for_currency = fields.Many2One('currency.currency', 'Sale Currency', required=True) for_amount = fields.Numeric('Sale Amount', digits=(16, 2)) bank = fields.Many2One('bank', 'Party') reason = fields.Many2One('forex.category', 'Category', required=True) operation_type = fields.Selection([ ('normal', 'Normal'), ('swap', 'Swap'), ], 'Operation Type', required=True) spot_rate = fields.Numeric('Rate', digits=(16, 6)) differential = fields.Numeric('Differential', digits=(16, 6)) rate = fields.Numeric('Agreed Rate', digits=(16, 6), required=True) confirmed = fields.Boolean('Confirmed') accepts_bls = fields.Boolean('Accepts B/Ls') voucher = fields.Boolean('Voucher') remarks = fields.Text("Remarks") cover_fees = fields.One2Many( 'forex.cover.fees', 'forex', 'Cover Fees' ) invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line', readonly=True) invoice_state = fields.Function(fields.Selection([ ('', ''), ('invoiced', 'Invoiced'), ('paid', 'Paid'), ('cancelled', 'Cancelled'), ], "Invoice State", help="The current state of the invoice " "that the forex appears on."), 'get_invoice_state') move = fields.Many2One('account.move', 'Move', readonly=True) move_state = fields.Function(fields.Selection([ ('', ''), ('not executed', 'Not Executed'), ('executed', 'Executed'), ], "Move State", help="The current state of the move " "that the forex appears on."), 'get_move_state') @classmethod def __setup__(cls): super(Forex, cls).__setup__() # cls.__access__.add('agent') cls._buttons.update({ 'invoice': { 'invisible': True, #Bool(Eval('invoice_line')), #'depends': ['invoice_line'], }, 'execute': { 'invisible': Bool(Eval('move')), 'depends': ['move'], }, }) @staticmethod def default_company(): return Transaction().context.get('company') @classmethod @ModelView.button def invoice(cls, forexs): pool = Pool() Invoice = pool.get('account.invoice') InvoiceLine = pool.get('account.invoice.line') invoices = [] invoice_lines = [] to_save = [] for forex in forexs: invoice = cls._get_invoice(forex) invoices.append(invoice) invoice_line = cls._get_invoice_line(invoice, forex) invoice_lines.append(invoice_line) forex.invoice_line = invoice_line to_save.append(forex) Invoice.save(invoices) InvoiceLine.save(invoice_lines) # Invoice.update_taxes(invoices) cls.save(to_save) @classmethod @ModelView.button def execute(cls, forexs): Move = Pool().get('account.move') Period = Pool().get('account.period') for forex in forexs: move = Move() move_lines = forex._get_move_lines() move.journal = cls.get_journal() period = Period.find(forex.company, date=forex.value_date) move.date = forex.value_date move.period = period #move.origin = forex move.company = forex.company move.lines = move_lines Move.save([move]) forex.create_exchange_move(move.lines[1].id) forex.move = move cls.save([forex]) @classmethod def get_journal(cls): pool = Pool() Journal = pool.get('account.journal') journals = Journal.search([ ('type', '=', 'cash'), ('name', '=', 'Forex'), ], limit=1) if journals: return journals[0] @classmethod def _get_invoice(cls, forex): pool = Pool() Invoice = pool.get('account.invoice') payment_term = forex.bank.party.supplier_payment_term return Invoice( company=forex.company, type='in', journal=cls.get_journal(), party=forex.bank.party, invoice_address=forex.bank.party.address_get(type='invoice'), currency=forex.for_currency, account=forex.bank.party.account_payable_used, payment_term=payment_term, ) @classmethod def _get_invoice_line(cls, invoice, forex): pool = Pool() InvoiceLine = pool.get('account.invoice.line') Product = pool.get('product.product') product = None invoice_line = InvoiceLine() ch_ct = Product.search(['name','=','Forex']) if ch_ct: product = ch_ct[0] invoice_line.account = product.account_expense_used invoice_line.unit = product.default_uom amount = invoice.currency.round(forex.for_amount) invoice_line.invoice = invoice invoice_line.currency = invoice.currency invoice_line.company = invoice.company invoice_line.type = 'line' # Use product.id to instantiate it with the correct context invoice_line.product = product.id invoice_line.quantity = 1 # invoice_line.on_change_product() invoice_line.unit_price = amount return invoice_line def create_exchange_move(self,id): Currency = Pool().get('currency.currency') MoveLine = Pool().get('account.move.line') Configuration = Pool().get('account.configuration') configuration = Configuration(1) Period = Pool().get('account.period') Move = Pool().get('account.move') PaymentMethod = Pool().get('account.invoice.payment.method') pm = PaymentMethod.search(['name','=','Forex']) with Transaction().set_context(date=self.value_date): amount_converted = Currency.compute(self.buy_currency,self.buy_amount, self.company.currency) to_add = amount_converted - self.for_amount if to_add != 0: second_amount_to_add = Currency.compute(self.company.currency,to_add,self.buy_currency) line = MoveLine() line.account = pm[0].debit_account line.revaluate = id line.credit = -to_add if to_add < 0 else 0 line.debit = to_add if to_add > 0 else 0 # line.amount_second_currency = second_amount_to_add # line.second_currency = self.buy_currency line.party = 33 line.maturity_date = self.value_date logger.info("REVALUATE_ACC:%s",line) line_ = MoveLine() if to_add < 0: line_.account = configuration.get_multivalue('currency_exchange_credit_account', company=self.company.id) else: line_.account = configuration.get_multivalue('currency_exchange_debit_account', company=self.company.id) line_.credit = to_add if to_add > 0 else 0 line_.debit = -to_add if to_add < 0 else 0 # line_.amount_second_currency = -second_amount_to_add # line_.second_currency = self.buy_currency line_.maturity_date = self.value_date logger.info("REVALUATE_EX:%s",line_) move = Move() move.journal = configuration.get_multivalue('currency_exchange_journal', company=self.company.id) period = Period.find(self.company, date=self.value_date) move.date = self.value_date move.period = period #move.origin = forex move.company = self.company move.lines = [line,line_] Move.save([move]) def _get_move_lines(self): ''' Return move line ''' MoveLine = Pool().get('account.move.line') Currency = Pool().get('currency.currency') PaymentMethod = Pool().get('account.invoice.payment.method') pm = PaymentMethod.search(['name','=','Forex']) move_lines = [] if pm: line = MoveLine() line.amount_second_currency = self.buy_amount line.second_currency = self.buy_currency line.debit, line.credit = self.for_amount, 0 line.account = pm[0].debit_account line.maturity_date = self.value_date line.description = 'Forex' line.party = self.bank.party line.origin = self move_lines.append(line) line = MoveLine() line.amount_second_currency = -self.buy_amount line.second_currency = self.buy_currency line.debit, line.credit = 0,self.for_amount line.account = pm[0].credit_account line.maturity_date = self.value_date line.description = 'Forex' line.party = self.bank.party line.origin = self move_lines.append(line) return move_lines def get_invoice_state(self, name): state = '' if self.invoice_line: state = 'invoiced' invoice = self.invoice_line.invoice if invoice and invoice.state in {'paid', 'cancelled'}: state = invoice.state return state def get_move_state(self, name): state = 'not executed' if self.move: state = 'executed' return state @classmethod def default_differential(cls): return Decimal(0) @fields.depends('buy_amount','for_amount','rate') def on_change_rate(self): if self.buy_amount and self.rate: self.for_amount = (Decimal(self.buy_amount) * Decimal(self.rate)) self.spot_rate = None self.differential = None @fields.depends('buy_amount','for_amount','rate') def on_change_for_amount(self): if self.buy_amount and self.for_amount: self.rate = round(Decimal(self.for_amount) / Decimal(self.buy_amount),6) self.spot_rate = None self.differential = None @fields.depends('buy_amount','for_amount','spot_rate','differential','rate') def on_change_spot_rate(self): if self.buy_amount and self.rate and self.for_amount: self.differential = Decimal(self.rate) - Decimal(self.spot_rate if self.spot_rate else 0) @fields.depends('buy_amount','for_amount','spot_rate','differential','rate') def on_change_differential(self): if self.buy_amount and self.differential and self.for_amount: self.spot_rate = Decimal(self.rate if self.rate else 0) - Decimal(self.differential) class PForex(metaclass=PoolMeta): 'Forex Deal' __name__ = 'forex.forex' cover_physical_contracts = fields.One2Many( 'forex.cover.physical.contract', 'forex', 'Cover Physical Purchase' ) class SForex(metaclass=PoolMeta): 'Forex Deal' __name__ = 'forex.forex' cover_physical_sales = fields.One2Many( 'forex.cover.physical.sale', 'forex', 'Cover Physical Sale' ) class ForexCategory(ModelSQL, ModelView): 'Forex Category' __name__ = 'forex.category' name = fields.Char('Name') class ForexCoverPhysicalContract(ModelSQL, ModelView): 'Forex Cover Physical Contract' __name__ = 'forex.cover.physical.contract' forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE') contract = fields.Many2One('purchase.purchase', 'Purchase', required=True) amount = fields.Numeric('Amount', digits=(16, 2)) quantity = fields.Numeric('Quantity', digits=(16, 5)) unit = fields.Many2One('product.uom',"Unit") class ForexCoverPhysicalSale(ModelSQL, ModelView): 'Forex Cover Physical Contract' __name__ = 'forex.cover.physical.sale' forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE') contract = fields.Many2One('sale.sale', 'Sale', required=True) amount = fields.Numeric('Amount', digits=(16, 2)) quantity = fields.Numeric('Quantity', digits=(16, 5)) unit = fields.Many2One('product.uom',"Unit") class ForexCoverFees(ModelSQL, ModelView): 'Forex Cover Fees' __name__ = 'forex.cover.fees' forex = fields.Many2One('forex.forex', 'Forex', required=True, ondelete='CASCADE') description = fields.Char('Description') amount = fields.Numeric('Amount', digits=(16, 2)) currency = fields.Many2One('currency.currency', 'Currency') class ForexReport(Wizard): 'Forex report' __name__ = 'forex.report' start = StateAction('purchase_trade.act_forex_bi') def do_start(self, action): pool = Pool() # action['views'].reverse() return action, {'res_id': [1]} class ForexBI(ModelSingleton,ModelSQL, ModelView): 'Forex BI' __name__ = 'forex.bi' input = fields.Text("BI") metabase = fields.Function(fields.Text(""),'get_bi') def get_bi(self,name=None): Configuration = Pool().get('gr.configuration') config = Configuration.search(['id','>',0])[0] payload = { "resource": {"dashboard": config.forex_id}, "params": {}, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30), } token = jwt.encode(payload, config.payload, algorithm="HS256") logger.info("TOKEN:%s",token) if config.dark: url = f"metabase:{config.bi}/embed/dashboard/{token}#theme=night&bordered=true&titled=true" else: url = f"metabase:{config.bi}/embed/dashboard/{token}#bordered=true&titled=true" return url