[ADD] ir.translation#translate()
[odoo/odoo.git] / openerp / addons / base / ir / ir_sequence.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
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
22 import logging
23 import time
24
25 import openerp
26
27 _logger = logging.getLogger(__name__)
28
29 class ir_sequence_type(openerp.osv.osv.osv):
30     _name = 'ir.sequence.type'
31     _order = 'name'
32     _columns = {
33         'name': openerp.osv.fields.char('Name', size=64, required=True),
34         'code': openerp.osv.fields.char('Code', size=32, required=True),
35     }
36
37     _sql_constraints = [
38         ('code_unique', 'unique(code)', '`code` must be unique.'),
39     ]
40
41 def _code_get(self, cr, uid, context=None):
42     cr.execute('select code, name from ir_sequence_type')
43     return cr.fetchall()
44
45 class ir_sequence(openerp.osv.osv.osv):
46     """ Sequence model.
47
48     The sequence model allows to define and use so-called sequence objects.
49     Such objects are used to generate unique identifiers in a transaction-safe
50     way.
51
52     """
53     _name = 'ir.sequence'
54     _order = 'name'
55     _columns = {
56         'name': openerp.osv.fields.char('Name', size=64, required=True),
57         'code': openerp.osv.fields.selection(_code_get, 'Code', size=64),
58         'implementation': openerp.osv.fields.selection( # TODO update the view
59             [('standard', 'Standard'), ('no_gap', 'No gap')],
60             'Implementation', required=True,
61             help="Two sequence object implementations are offered: Standard "
62             "and 'No gap'. The later is slower than the former but forbids any"
63             " gap in the sequence (while they are possible in the former)."),
64         'active': openerp.osv.fields.boolean('Active'),
65         'prefix': openerp.osv.fields.char('Prefix', size=64, help="Prefix value of the record for the sequence"),
66         'suffix': openerp.osv.fields.char('Suffix', size=64, help="Suffix value of the record for the sequence"),
67         'number_next': openerp.osv.fields.integer('Next Number', required=True, help="Next number of this sequence"),
68         'number_increment': openerp.osv.fields.integer('Increment Number', required=True, help="The next number of the sequence will be incremented by this number"),
69         'padding' : openerp.osv.fields.integer('Number Padding', required=True, help="OpenERP will automatically adds some '0' on the left of the 'Next Number' to get the required padding size."),
70         'company_id': openerp.osv.fields.many2one('res.company', 'Company'),
71     }
72     _defaults = {
73         'implementation': 'standard',
74         'active': True,
75         'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'ir.sequence', context=c),
76         'number_increment': 1,
77         'number_next': 1,
78         'padding' : 0,
79     }
80
81     def init(self, cr):
82         return # Don't do the following index yet.
83         # CONSTRAINT/UNIQUE INDEX on (code, company_id) 
84         # /!\ The unique constraint 'unique_name_company_id' is not sufficient, because SQL92
85         # only support field names in constraint definitions, and we need a function here:
86         # we need to special-case company_id to treat all NULL company_id as equal, otherwise
87         # we would allow duplicate (code, NULL) ir_sequences.
88         cr.execute("""
89             SELECT indexname FROM pg_indexes WHERE indexname =
90             'ir_sequence_unique_code_company_id_idx'""")
91         if not cr.fetchone():
92             cr.execute("""
93                 CREATE UNIQUE INDEX ir_sequence_unique_code_company_id_idx
94                 ON ir_sequence (code, (COALESCE(company_id,-1)))""")
95
96     def _create_sequence(self, cr, id, number_increment, number_next):
97         """ Create a PostreSQL sequence.
98
99         There is no access rights check.
100         """
101         assert isinstance(id, (int, long))
102         sql = "CREATE SEQUENCE ir_sequence_%03d INCREMENT BY %%s START WITH %%s" % id
103         cr.execute(sql, (number_increment, number_next))
104
105     def _drop_sequence(self, cr, ids):
106         """ Drop the PostreSQL sequence if it exists.
107
108         There is no access rights check.
109         """
110
111         ids = ids if isinstance(ids, (list, tuple)) else [ids]
112         assert all(isinstance(i, (int, long)) for i in ids), \
113             "Only ids in (int, long) allowed."
114         names = ','.join('ir_sequence_%03d' % i for i in ids)
115
116         # RESTRICT is the default; it prevents dropping the sequence if an
117         # object depends on it.
118         cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
119
120     def _alter_sequence(self, cr, id, number_increment, number_next):
121         """ Alter a PostreSQL sequence.
122
123         There is no access rights check.
124         """
125         assert isinstance(id, (int, long))
126         cr.execute("""
127             ALTER SEQUENCE ir_sequence_%03d INCREMENT BY %%s RESTART WITH %%s
128             """ % id, (number_increment, number_next))
129
130     def create(self, cr, uid, values, context=None):
131         """ Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
132         """
133         values = self._add_missing_default_values(cr, uid, values, context)
134         values['id'] = super(ir_sequence, self).create(cr, uid, values, context)
135         if values['implementation'] == 'standard':
136             f = self._create_sequence(cr, values['id'], values['number_increment'], values['number_next'])
137         return values['id']
138
139     def unlink(self, cr, uid, ids, context=None):
140         super(ir_sequence, self).unlink(cr, uid, ids, context)
141         self._drop_sequence(cr, ids)
142         return True
143
144     def write(self, cr, uid, ids, values, context=None):
145         if not isinstance(ids, (list, tuple)):
146             ids = [ids]
147         new_implementation = values.get('implementation')
148         rows = self.read(cr, uid, ids, ['implementation', 'number_increment', 'number_next'], context)
149         super(ir_sequence, self).write(cr, uid, ids, values, context)
150
151         for row in rows:
152             # 4 cases: we test the previous impl. against the new one.
153             i = values.get('number_increment', row['number_increment'])
154             n = values.get('number_next', row['number_next'])
155             if row['implementation'] == 'standard':
156                 if new_implementation in ('standard', None):
157                     self._alter_sequence(cr, row['id'], i, n)
158                 else:
159                     self._drop_sequence(cr, row['id'])
160             else:
161                 if new_implementation in ('no_gap', None):
162                     pass
163                 else:
164                     self._create_sequence(cr, row['id'], i, n)
165
166         return True
167
168     def _interpolate(self, s, d):
169         if s:
170             return s % d
171         return  ''
172
173     def _interpolation_dict(self):
174         t = time.localtime() # Actually, the server is always in UTC.
175         return {
176             'year': time.strftime('%Y', t),
177             'month': time.strftime('%m', t),
178             'day': time.strftime('%d', t),
179             'y': time.strftime('%y', t),
180             'doy': time.strftime('%j', t),
181             'woy': time.strftime('%W', t),
182             'weekday': time.strftime('%w', t),
183             'h24': time.strftime('%H', t),
184             'h12': time.strftime('%I', t),
185             'min': time.strftime('%M', t),
186             'sec': time.strftime('%S', t),
187         }
188
189     def _next(self, cr, uid, seq_ids, context=None):
190         if not seq_ids:
191             return False
192         if context is None:
193             context = {}
194         force_company = context.get('force_company')
195         if not force_company:
196             force_company = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
197         sequences = self.read(cr, uid, seq_ids, ['company_id','implementation','number_next','prefix','suffix','padding'])
198         preferred_sequences = [s for s in sequences if s['company_id'] and s['company_id'][0] == force_company ]
199         seq = preferred_sequences[0] if preferred_sequences else sequences[0]
200         if seq['implementation'] == 'standard':
201             cr.execute("SELECT nextval('ir_sequence_%03d')" % seq['id'])
202             seq['number_next'] = cr.fetchone()
203         else:
204             cr.execute("SELECT number_next FROM ir_sequence WHERE id=%s FOR UPDATE NOWAIT", (seq['id'],))
205             cr.execute("UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id=%s ", (seq['id'],))
206         d = self._interpolation_dict()
207         interpolated_prefix = self._interpolate(seq['prefix'], d)
208         interpolated_suffix = self._interpolate(seq['suffix'], d)
209         return interpolated_prefix + '%%0%sd' % seq['padding'] % seq['number_next'] + interpolated_suffix
210
211     def next_by_id(self, cr, uid, sequence_id, context=None):
212         """ Draw an interpolated string using the specified sequence."""
213         self.check_read(cr, uid)
214         company_ids = self.pool.get('res.company').search(cr, uid, [], order='company_id', context=context) + [False]
215         ids = self.search(cr, uid, ['&',('id','=', sequence_id),('company_id','in',company_ids)])
216         return self._next(cr, uid, ids, context)
217
218     def next_by_code(self, cr, uid, sequence_code, context=None):
219         """ Draw an interpolated string using a sequence with the requested code.
220             If several sequences with the correct code are available to the user
221             (multi-company cases), the one from the user's current company will
222             be used.
223
224             :param dict context: context dictionary may contain a
225                 ``force_company`` key with the ID of the company to
226                 use instead of the user's current company for the
227                 sequence selection. A matching sequence for that
228                 specific company will get higher priority. 
229         """
230         self.check_read(cr, uid)
231         company_ids = self.pool.get('res.company').search(cr, uid, [], order='company_id', context=context) + [False]
232         ids = self.search(cr, uid, ['&',('code','=', sequence_code),('company_id','in',company_ids)])
233         return self._next(cr, uid, ids, context)
234
235     def get_id(self, cr, uid, sequence_code_or_id, code_or_id='id', context=None):
236         """ Draw an interpolated string using the specified sequence.
237
238         The sequence to use is specified by the ``sequence_code_or_id``
239         argument, which can be a code or an id (as controlled by the
240         ``code_or_id`` argument. This method is deprecated.
241         """
242         # TODO: bump up to warning after 6.1 release
243         _logger.debug("ir_sequence.get() and ir_sequence.get_id() are deprecated. "
244             "Please use ir_sequence.next_by_code() or ir_sequence.next_by_id().")
245         if code_or_id == 'id':
246             return self.next_by_id(cr, uid, sequence_code_or_id, context)
247         else:
248             return self.next_by_code(cr, uid, sequence_code_or_id, context)
249
250     def get(self, cr, uid, code, context=None):
251         """ Draw an interpolated string using the specified sequence.
252
253         The sequence to use is specified by its code. This method is
254         deprecated.
255         """
256         return self.get_id(cr, uid, code, 'code', context)
257
258
259 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: