Files
2026-01-07 16:17:21 +01:00

399 lines
15 KiB
Python
Executable File

# 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