2008-10-08 12:18:56 +02:00
|
|
|
#This file is part of Tryton. The COPYRIGHT file at the top level
|
|
|
|
|
#of this repository contains the full copyright notices and license terms.
|
2010-08-10 11:58:58 -04:00
|
|
|
import datetime
|
|
|
|
|
from decimal import Decimal, ROUND_HALF_EVEN
|
2009-02-28 16:59:47 +01:00
|
|
|
from trytond.model import ModelView, ModelSQL, fields
|
2009-12-17 11:42:42 +01:00
|
|
|
from trytond.tools import safe_eval, datetime_strftime
|
2010-08-10 11:58:58 -04:00
|
|
|
from trytond.transaction import Transaction
|
2011-07-03 13:18:55 +02:00
|
|
|
from trytond.pool import Pool
|
2012-09-24 22:17:53 +02:00
|
|
|
from trytond.rpc import RPC
|
|
|
|
|
|
|
|
|
|
__all__ = ['Currency', 'Rate']
|
2008-05-09 17:26:14 +02:00
|
|
|
|
|
|
|
|
|
2009-02-28 16:59:47 +01:00
|
|
|
class Currency(ModelSQL, ModelView):
|
2008-05-09 17:26:14 +02:00
|
|
|
'Currency'
|
2012-09-24 22:17:53 +02:00
|
|
|
__name__ = 'currency.currency'
|
2008-06-18 17:15:27 +02:00
|
|
|
name = fields.Char('Name', required=True, translate=True)
|
|
|
|
|
symbol = fields.Char('Symbol', size=10, required=True)
|
2008-05-09 17:26:14 +02:00
|
|
|
code = fields.Char('Code', size=3, required=True)
|
2010-02-25 17:53:28 +01:00
|
|
|
numeric_code = fields.Char('Numeric Code', size=3)
|
2010-03-11 16:47:49 +01:00
|
|
|
rate = fields.Function(fields.Numeric('Current rate', digits=(12, 6),
|
|
|
|
|
on_change_with=['rates']), 'get_rate')
|
2008-05-09 17:26:14 +02:00
|
|
|
rates = fields.One2Many('currency.currency.rate', 'currency', 'Rates')
|
2008-09-22 13:30:07 +02:00
|
|
|
rounding = fields.Numeric('Rounding factor', digits=(12, 6), required=True)
|
2012-03-23 18:38:55 +01:00
|
|
|
digits = fields.Integer('Display Digits', required=True)
|
2008-05-09 17:26:14 +02:00
|
|
|
active = fields.Boolean('Active')
|
|
|
|
|
|
2008-08-27 17:35:00 +02:00
|
|
|
# monetary formatting
|
2008-09-22 20:42:00 +02:00
|
|
|
mon_grouping = fields.Char('Grouping', required=True)
|
|
|
|
|
mon_decimal_point = fields.Char('Decimal Separator', required=True)
|
|
|
|
|
mon_thousands_sep = fields.Char('Thousands Separator')
|
2012-03-23 18:38:55 +01:00
|
|
|
p_sign_posn = fields.Integer('Positive Sign Position', required=True)
|
|
|
|
|
n_sign_posn = fields.Integer('Negative Sign Position', required=True)
|
2008-09-22 20:42:00 +02:00
|
|
|
positive_sign = fields.Char('Positive Sign')
|
|
|
|
|
negative_sign = fields.Char('Negative Sign')
|
2009-01-22 12:16:15 +01:00
|
|
|
p_cs_precedes = fields.Boolean('Positive Currency Symbol Precedes')
|
|
|
|
|
n_cs_precedes = fields.Boolean('Negative Currency Symbol Precedes')
|
|
|
|
|
p_sep_by_space = fields.Boolean('Positive Separate by Space')
|
|
|
|
|
n_sep_by_space = fields.Boolean('Negative Separate by Space')
|
2008-08-27 17:35:00 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def __setup__(cls):
|
|
|
|
|
super(Currency, cls).__setup__()
|
|
|
|
|
cls._order.insert(0, ('code', 'ASC'))
|
|
|
|
|
cls._error_messages.update({
|
2013-03-07 16:54:52 +01:00
|
|
|
'no_rate': ('No rate found for currency "%(currency)s" on '
|
|
|
|
|
'"%(date)s"'),
|
|
|
|
|
'invalid_mon_grouping': ('Invalid grouping "%(grouping)s" on '
|
|
|
|
|
' currency "%(currency)s".'),
|
2012-09-24 22:17:53 +02:00
|
|
|
})
|
|
|
|
|
cls.__rpc__.update({
|
|
|
|
|
'compute': RPC(),
|
|
|
|
|
})
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_active():
|
2008-05-09 17:26:14 +02:00
|
|
|
return True
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_rounding():
|
2008-11-11 22:37:05 +01:00
|
|
|
return Decimal('0.01')
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_digits():
|
2008-05-09 17:26:14 +02:00
|
|
|
return 2
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_mon_grouping():
|
2008-08-27 17:35:00 +02:00
|
|
|
return '[]'
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_mon_thousands_sep():
|
2008-08-27 17:35:00 +02:00
|
|
|
return ','
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_mon_decimal_point():
|
2008-08-27 17:35:00 +02:00
|
|
|
return '.'
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_p_sign_posn():
|
2008-08-27 17:35:00 +02:00
|
|
|
return 1
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_n_sign_posn():
|
2008-08-27 17:35:00 +02:00
|
|
|
return 1
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_negative_sign():
|
2008-08-27 17:35:00 +02:00
|
|
|
return '-'
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_positive_sign():
|
2008-08-27 17:35:00 +02:00
|
|
|
return ''
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_p_cs_precedes():
|
2008-08-27 17:35:00 +02:00
|
|
|
return True
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_n_cs_precedes():
|
2008-08-27 17:35:00 +02:00
|
|
|
return True
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_p_sep_by_space():
|
2008-08-27 17:35:00 +02:00
|
|
|
return False
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_n_sep_by_space():
|
2008-08-27 17:35:00 +02:00
|
|
|
return False
|
|
|
|
|
|
2013-03-07 16:54:52 +01:00
|
|
|
@classmethod
|
|
|
|
|
def validate(cls, currencies):
|
|
|
|
|
super(Currency, cls).validate(currencies)
|
|
|
|
|
for currency in currencies:
|
|
|
|
|
currency.check_mon_grouping()
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
def check_mon_grouping(self):
|
2008-08-27 17:35:00 +02:00
|
|
|
'''
|
|
|
|
|
Check if mon_grouping is list of numbers
|
|
|
|
|
'''
|
2012-09-24 22:17:53 +02:00
|
|
|
try:
|
|
|
|
|
grouping = safe_eval(self.mon_grouping)
|
|
|
|
|
for i in grouping:
|
|
|
|
|
if not isinstance(i, int):
|
2013-03-07 16:54:52 +01:00
|
|
|
raise ValueError
|
2012-09-24 22:17:53 +02:00
|
|
|
except Exception:
|
2013-03-07 16:54:52 +01:00
|
|
|
self.raise_user_error('invalid_mon_grouping', {
|
2013-03-25 21:11:12 +00:00
|
|
|
'grouping': self.mon_grouping,
|
|
|
|
|
'currency': self.rec_name,
|
2013-03-07 16:54:52 +01:00
|
|
|
})
|
2008-08-27 17:35:00 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def check_xml_record(cls, records, values):
|
2009-04-02 20:00:11 +02:00
|
|
|
return True
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def search_rec_name(cls, name, clause):
|
|
|
|
|
currencies = None
|
2010-03-23 11:24:42 +01:00
|
|
|
field = None
|
|
|
|
|
for field in ('code', 'numeric_code'):
|
2013-08-08 01:45:17 +02:00
|
|
|
currencies = cls.search([(field,) + tuple(clause[1:])], limit=1)
|
2012-09-24 22:17:53 +02:00
|
|
|
if currencies:
|
2010-03-23 11:24:42 +01:00
|
|
|
break
|
2012-09-24 22:17:53 +02:00
|
|
|
if currencies:
|
2013-08-08 01:45:17 +02:00
|
|
|
return [(field,) + tuple(clause[1:])]
|
|
|
|
|
return [(cls._rec_name,) + tuple(clause[1:])]
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
def on_change_with_rate(self):
|
2009-06-22 14:44:44 +02:00
|
|
|
now = datetime.date.today()
|
|
|
|
|
closer = datetime.date.min
|
|
|
|
|
res = Decimal('0.0')
|
2012-09-24 22:17:53 +02:00
|
|
|
for rate in self.rates or []:
|
2009-06-22 14:44:44 +02:00
|
|
|
if 'date' not in rate or 'rate' not in rate:
|
|
|
|
|
continue
|
|
|
|
|
if rate['date'] <= now and rate['date'] > closer:
|
|
|
|
|
res = rate['rate']
|
|
|
|
|
closer = rate['date']
|
|
|
|
|
return res
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def get_rate(currencies, name):
|
2008-05-09 17:26:14 +02:00
|
|
|
'''
|
|
|
|
|
Return the rate at the date from the context or the current date
|
|
|
|
|
'''
|
2012-09-24 22:17:53 +02:00
|
|
|
Rate = Pool().get('currency.currency.rate')
|
|
|
|
|
Date = Pool().get('ir.date')
|
2008-10-24 00:34:00 +02:00
|
|
|
|
|
|
|
|
res = {}
|
2012-09-24 22:17:53 +02:00
|
|
|
date = Transaction().context.get('date', Date.today())
|
|
|
|
|
for currency in currencies:
|
|
|
|
|
rates = Rate.search([
|
|
|
|
|
('currency', '=', currency.id),
|
|
|
|
|
('date', '<=', date),
|
|
|
|
|
], limit=1, order=[('date', 'DESC')])
|
|
|
|
|
if rates:
|
|
|
|
|
res[currency.id] = rates[0].id
|
2008-05-09 17:26:14 +02:00
|
|
|
else:
|
2013-07-29 10:31:28 +02:00
|
|
|
res[currency.id] = 0
|
2008-05-09 17:26:14 +02:00
|
|
|
rate_ids = [x for x in res.values() if x]
|
2012-09-24 22:17:53 +02:00
|
|
|
rates = Rate.browse(rate_ids)
|
2008-05-09 17:26:14 +02:00
|
|
|
id2rate = {}
|
|
|
|
|
for rate in rates:
|
|
|
|
|
id2rate[rate.id] = rate
|
|
|
|
|
for currency_id in res.keys():
|
|
|
|
|
if res[currency_id]:
|
|
|
|
|
res[currency_id] = id2rate[res[currency_id]].rate
|
|
|
|
|
return res
|
|
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
def round(self, amount, rounding=ROUND_HALF_EVEN):
|
2009-09-29 12:08:51 +02:00
|
|
|
'''
|
|
|
|
|
Round the amount depending of the currency
|
|
|
|
|
|
|
|
|
|
:param cursor: the database cursor
|
|
|
|
|
:param user: the user id
|
|
|
|
|
:param currency: a BrowseRecord of currency.currency
|
|
|
|
|
:param amout: a Decimal
|
|
|
|
|
:param rounding: the rounding option
|
|
|
|
|
:return: a Decimal
|
|
|
|
|
'''
|
2012-09-24 22:17:53 +02:00
|
|
|
return (amount / self.rounding).quantize(Decimal('1.'),
|
|
|
|
|
rounding=rounding) * self.rounding
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
def is_zero(self, amount):
|
2008-05-09 17:26:14 +02:00
|
|
|
'Return True if the amount can be considered as zero for the currency'
|
2012-09-24 22:17:53 +02:00
|
|
|
return abs(self.round(amount)) < self.rounding
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def compute(cls, from_currency, amount, to_currency, round=True):
|
2008-05-09 17:26:14 +02:00
|
|
|
'''
|
|
|
|
|
Take a currency and an amount
|
|
|
|
|
Return the amount to the new currency
|
2013-01-16 21:20:58 +01:00
|
|
|
Use the rate of the date of the context or the current date
|
2008-05-09 17:26:14 +02:00
|
|
|
'''
|
2012-09-24 22:17:53 +02:00
|
|
|
Date = Pool().get('ir.date')
|
|
|
|
|
Lang = Pool().get('ir.lang')
|
|
|
|
|
from_currency = cls(from_currency.id)
|
|
|
|
|
to_currency = cls(to_currency.id)
|
2008-10-24 00:34:00 +02:00
|
|
|
|
2008-07-10 11:25:52 +02:00
|
|
|
if to_currency == from_currency:
|
|
|
|
|
if round:
|
2012-09-24 22:17:53 +02:00
|
|
|
return to_currency.round(amount)
|
2008-07-10 11:25:52 +02:00
|
|
|
else:
|
|
|
|
|
return amount
|
2008-05-09 17:26:14 +02:00
|
|
|
if (not from_currency.rate) or (not to_currency.rate):
|
2012-09-24 22:17:53 +02:00
|
|
|
date = Transaction().context.get('date', Date.today())
|
2008-05-09 17:26:14 +02:00
|
|
|
if not from_currency.rate:
|
|
|
|
|
name = from_currency.name
|
|
|
|
|
else:
|
|
|
|
|
name = to_currency.name
|
2008-10-24 00:34:00 +02:00
|
|
|
|
2013-03-07 16:54:52 +01:00
|
|
|
languages = Lang.search([
|
2012-01-27 21:14:19 +01:00
|
|
|
('code', '=', Transaction().language),
|
2010-08-10 11:58:58 -04:00
|
|
|
])
|
2013-03-07 16:54:52 +01:00
|
|
|
cls.raise_user_error('no_rate', {
|
|
|
|
|
'currency': name,
|
|
|
|
|
'date': datetime_strftime(date, str(languages[0].date))
|
|
|
|
|
})
|
2008-07-10 11:25:52 +02:00
|
|
|
if round:
|
2012-09-24 22:17:53 +02:00
|
|
|
return to_currency.round(
|
|
|
|
|
amount * to_currency.rate / from_currency.rate)
|
2008-05-09 17:26:14 +02:00
|
|
|
else:
|
2008-07-10 11:25:52 +02:00
|
|
|
return amount * to_currency.rate / from_currency.rate
|
2008-05-09 17:26:14 +02:00
|
|
|
|
|
|
|
|
|
2009-02-28 16:59:47 +01:00
|
|
|
class Rate(ModelSQL, ModelView):
|
2008-05-09 17:26:14 +02:00
|
|
|
'Rate'
|
2012-09-24 22:17:53 +02:00
|
|
|
__name__ = 'currency.currency.rate'
|
2008-05-09 17:26:14 +02:00
|
|
|
_rec_name = 'date'
|
2012-03-05 10:53:02 +01:00
|
|
|
date = fields.Date('Date', required=True, select=True)
|
2008-05-09 17:26:14 +02:00
|
|
|
rate = fields.Numeric('Rate', digits=(12, 6), required=1)
|
2009-09-03 10:20:30 +02:00
|
|
|
currency = fields.Many2One('currency.currency', 'Currency',
|
|
|
|
|
ondelete='CASCADE',)
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def __setup__(cls):
|
|
|
|
|
super(Rate, cls).__setup__()
|
|
|
|
|
cls._sql_constraints = [
|
2008-05-09 17:26:14 +02:00
|
|
|
('date_currency_uniq', 'UNIQUE(date, currency)',
|
2013-03-07 16:54:52 +01:00
|
|
|
'A currency can only have one rate by date.'),
|
2012-03-23 18:38:55 +01:00
|
|
|
('check_currency_rate', 'CHECK(rate >= 0)',
|
|
|
|
|
'The currency rate must greater than or equal to 0'),
|
2012-09-24 22:17:53 +02:00
|
|
|
]
|
|
|
|
|
cls._order.insert(0, ('date', 'DESC'))
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def default_date():
|
|
|
|
|
Date = Pool().get('ir.date')
|
|
|
|
|
return Date.today()
|
2008-05-09 17:26:14 +02:00
|
|
|
|
2012-09-24 22:17:53 +02:00
|
|
|
@classmethod
|
|
|
|
|
def check_xml_record(self, records, values):
|
2008-08-25 11:52:20 +02:00
|
|
|
return True
|