399 lines
15 KiB
Python
Executable File
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
|
|
|