[MERGE] base: raise an exception if the format of the bank account is wrong
[odoo/odoo.git] / openerp / addons / base / res / res_currency.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21 import re
22 import time
23 import netsvc
24 from osv import fields, osv
25 import tools
26
27 from tools import float_round, float_is_zero, float_compare
28 from tools.translate import _
29
30 CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
31
32 class res_currency(osv.osv):
33     def _current_rate(self, cr, uid, ids, name, arg, context=None):
34         if context is None:
35             context = {}
36         res = {}
37         if 'date' in context:
38             date = context['date']
39         else:
40             date = time.strftime('%Y-%m-%d')
41         date = date or time.strftime('%Y-%m-%d')
42         # Convert False values to None ...
43         currency_rate_type = context.get('currency_rate_type_id') or None
44         # ... and use 'is NULL' instead of '= some-id'.
45         operator = '=' if currency_rate_type else 'is'
46         for id in ids:
47             cr.execute("SELECT currency_id, rate FROM res_currency_rate WHERE currency_id = %s AND name <= %s AND currency_rate_type_id " + operator +" %s ORDER BY name desc LIMIT 1" ,(id, date, currency_rate_type))
48             if cr.rowcount:
49                 id, rate = cr.fetchall()[0]
50                 res[id] = rate
51             else:
52                 res[id] = 0
53         return res
54     _name = "res.currency"
55     _description = "Currency"
56     _columns = {
57         # Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
58         'name': fields.char('Currency', size=32, required=True, help="Currency Code (ISO 4217)"),
59         'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."),
60         'rate': fields.function(_current_rate, string='Current Rate', digits=(12,6),
61             help='The rate of the currency to the currency of rate 1.'),
62         'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'),
63         'accuracy': fields.integer('Computational Accuracy'),
64         'rounding': fields.float('Rounding Factor', digits=(12,6)),
65         'active': fields.boolean('Active'),
66         'company_id':fields.many2one('res.company', 'Company'),
67         'date': fields.date('Date'),
68         'base': fields.boolean('Base'),
69         'position': fields.selection([('after','After Amount'),('before','Before Amount')], 'Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
70     }
71     _defaults = {
72         'active': lambda *a: 1,
73         'position' : 'after',
74         'rounding': 0.01,
75         'accuracy': 4,
76     }
77     _sql_constraints = [
78         # this constraint does not cover all cases due to SQL NULL handling for company_id,
79         # so it is complemented with a unique index (see below). The constraint and index
80         # share the same prefix so that IntegrityError triggered by the index will be caught
81         # and reported to the user with the constraint's error message.
82         ('unique_name_company_id', 'unique (name, company_id)', 'The currency code must be unique per company!'),
83     ]
84     _order = "name"
85
86     def init(self, cr):
87         # CONSTRAINT/UNIQUE INDEX on (name,company_id) 
88         # /!\ The unique constraint 'unique_name_company_id' is not sufficient, because SQL92
89         # only support field names in constraint definitions, and we need a function here:
90         # we need to special-case company_id to treat all NULL company_id as equal, otherwise
91         # we would allow duplicate "global" currencies (all having company_id == NULL) 
92         cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'res_currency_unique_name_company_id_idx'""")
93         if not cr.fetchone():
94             cr.execute("""CREATE UNIQUE INDEX res_currency_unique_name_company_id_idx
95                           ON res_currency
96                           (name, (COALESCE(company_id,-1)))""")
97
98     def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
99         res = super(res_currency, self).read(cr, user, ids, fields, context, load)
100         currency_rate_obj = self.pool.get('res.currency.rate')
101         values = res
102         if not isinstance(values, (list)):
103             values = [values]
104         for r in values:
105             if r.__contains__('rate_ids'):
106                 rates=r['rate_ids']
107                 if rates:
108                     currency_date = currency_rate_obj.read(cr, user, rates[0], ['name'])['name']
109                     r['date'] = currency_date
110         return res
111
112     def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
113         if not args:
114             args = []
115         results = super(res_currency,self)\
116             .name_search(cr, user, name, args, operator=operator, context=context, limit=limit)
117         if not results:
118             name_match = CURRENCY_DISPLAY_PATTERN.match(name)
119             if name_match:
120                 results = super(res_currency,self)\
121                     .name_search(cr, user, name_match.group(1), args, operator=operator, context=context, limit=limit)
122         return results
123
124     def name_get(self, cr, uid, ids, context=None):
125         if not ids:
126             return []
127         if isinstance(ids, (int, long)):
128             ids = [ids]
129         reads = self.read(cr, uid, ids, ['name','symbol'], context=context, load='_classic_write')
130         return [(x['id'], tools.ustr(x['name']) + (x['symbol'] and (' (' + tools.ustr(x['symbol']) + ')') or '')) for x in reads]
131
132     def round(self, cr, uid, currency, amount):
133         """Return ``amount`` rounded  according to ``currency``'s
134            rounding rules.
135
136            :param browse_record currency: currency for which we are rounding
137            :param float amount: the amount to round
138            :return: rounded float
139         """
140         return float_round(amount, precision_rounding=currency.rounding)
141
142     def compare_amounts(self, cr, uid, currency, amount1, amount2):
143         """Compare ``amount1`` and ``amount2`` after rounding them according to the
144            given currency's precision..
145            An amount is considered lower/greater than another amount if their rounded
146            value is different. This is not the same as having a non-zero difference!
147
148            For example 1.432 and 1.431 are equal at 2 digits precision,
149            so this method would return 0.
150            However 0.006 and 0.002 are considered different (returns 1) because
151            they respectively round to 0.01 and 0.0, even though
152            0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
153
154            :param browse_record currency: currency for which we are rounding
155            :param float amount1: first amount to compare
156            :param float amount2: second amount to compare
157            :return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than,
158                     equal to, or greater than ``amount2``, according to
159                     ``currency``'s rounding.
160         """
161         return float_compare(amount1, amount2, precision_rounding=currency.rounding)
162
163     def is_zero(self, cr, uid, currency, amount):
164         """Returns true if ``amount`` is small enough to be treated as
165            zero according to ``currency``'s rounding rules.
166
167            Warning: ``is_zero(amount1-amount2)`` is not always equivalent to 
168            ``compare_amounts(amount1,amount2) == 0``, as the former will round after
169            computing the difference, while the latter will round before, giving
170            different results for e.g. 0.006 and 0.002 at 2 digits precision.
171
172            :param browse_record currency: currency for which we are rounding
173            :param float amount: amount to compare with currency's zero
174         """
175         return float_is_zero(amount, precision_rounding=currency.rounding)
176
177     def _get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None):
178         if context is None:
179             context = {}
180         ctx = context.copy()
181         ctx.update({'currency_rate_type_id': ctx.get('currency_rate_type_from')})
182         from_currency = self.browse(cr, uid, from_currency.id, context=ctx)
183
184         ctx.update({'currency_rate_type_id': ctx.get('currency_rate_type_to')})
185         to_currency = self.browse(cr, uid, to_currency.id, context=ctx)
186
187         if from_currency.rate == 0 or to_currency.rate == 0:
188             date = context.get('date', time.strftime('%Y-%m-%d'))
189             if from_currency.rate == 0:
190                 currency_symbol = from_currency.symbol
191             else:
192                 currency_symbol = to_currency.symbol
193             raise osv.except_osv(_('Error'), _('No rate found \n' \
194                     'for the currency: %s \n' \
195                     'at the date: %s') % (currency_symbol, date))
196         return to_currency.rate/from_currency.rate
197
198     def compute(self, cr, uid, from_currency_id, to_currency_id, from_amount,
199                 round=True, currency_rate_type_from=False, currency_rate_type_to=False, context=None):
200         if not context:
201             context = {}
202         if not from_currency_id:
203             from_currency_id = to_currency_id
204         if not to_currency_id:
205             to_currency_id = from_currency_id
206         xc = self.browse(cr, uid, [from_currency_id,to_currency_id], context=context)
207         from_currency = (xc[0].id == from_currency_id and xc[0]) or xc[1]
208         to_currency = (xc[0].id == to_currency_id and xc[0]) or xc[1]
209         if (to_currency_id == from_currency_id) and (currency_rate_type_from == currency_rate_type_to):
210             if round:
211                 return self.round(cr, uid, to_currency, from_amount)
212             else:
213                 return from_amount
214         else:
215             context.update({'currency_rate_type_from': currency_rate_type_from, 'currency_rate_type_to': currency_rate_type_to})
216             rate = self._get_conversion_rate(cr, uid, from_currency, to_currency, context=context)
217             if round:
218                 return self.round(cr, uid, to_currency, from_amount * rate)
219             else:
220                 return (from_amount * rate)
221
222 res_currency()
223
224 class res_currency_rate_type(osv.osv):
225     _name = "res.currency.rate.type"
226     _description = "Currency Rate Type"
227     _columns = {
228         'name': fields.char('Name', size=64, required=True, translate=True),
229     }
230
231 res_currency_rate_type()
232
233 class res_currency_rate(osv.osv):
234     _name = "res.currency.rate"
235     _description = "Currency Rate"
236
237     _columns = {
238         'name': fields.date('Date', required=True, select=True),
239         'rate': fields.float('Rate', digits=(12,6), help='The rate of the currency to the currency of rate 1'),
240         'currency_id': fields.many2one('res.currency', 'Currency', readonly=True),
241         'currency_rate_type_id': fields.many2one('res.currency.rate.type', 'Currency Rate Type', help="Allow you to define your own currency rate types, like 'Average' or 'Year to Date'. Leave empty if you simply want to use the normal 'spot' rate type"),
242     }
243     _defaults = {
244         'name': lambda *a: time.strftime('%Y-%m-%d'),
245     }
246     _order = "name desc"
247
248 res_currency_rate()
249
250 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
251