2010-03-04 09:03:42 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2010-01-28 13:26:53 +00:00
#
2010-03-04 09:05:56 +00:00
# OpenERP, Open Source Management Solution
2010-12-08 14:17:07 +00:00
# Copyright (C) 2004-TODAY OpenERP S.A. <http://www.openerp.com>
2008-06-16 11:00:21 +00:00
#
2008-11-03 18:27:16 +00:00
# This program is free software: you can redistribute it and/or modify
2010-12-08 14:17:07 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2006-12-07 13:41:40 +00:00
#
2008-11-03 18:27:16 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2010-12-08 14:17:07 +00:00
# GNU Affero General Public License for more details.
2006-12-07 13:41:40 +00:00
#
2010-12-08 14:17:07 +00:00
# You should have received a copy of the GNU Affero General Public License
2010-01-28 13:26:53 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
2011-09-29 12:27:53 +00:00
import logging
2006-12-07 13:41:40 +00:00
import time
2011-09-27 18:38:30 +00:00
import openerp
2012-12-10 15:27:23 +00:00
from openerp . osv import osv
from openerp . tools . translate import _
2011-09-27 18:38:30 +00:00
2012-01-24 11:47:30 +00:00
_logger = logging . getLogger ( __name__ )
2011-09-29 12:27:53 +00:00
2011-09-27 18:38:30 +00:00
class ir_sequence_type ( openerp . osv . osv . osv ) :
2008-07-22 14:24:36 +00:00
_name = ' ir.sequence.type '
2010-12-10 22:42:58 +00:00
_order = ' name '
2008-07-22 14:24:36 +00:00
_columns = {
2011-09-27 18:38:30 +00:00
' name ' : openerp . osv . fields . char ( ' Name ' , size = 64 , required = True ) ,
' code ' : openerp . osv . fields . char ( ' Code ' , size = 32 , required = True ) ,
2008-07-22 14:24:36 +00:00
}
2006-12-07 13:41:40 +00:00
2011-09-27 21:36:12 +00:00
_sql_constraints = [
( ' code_unique ' , ' unique(code) ' , ' `code` must be unique. ' ) ,
]
2011-11-07 15:19:49 +00:00
def _code_get ( self , cr , uid , context = None ) :
2008-07-22 14:24:36 +00:00
cr . execute ( ' select code, name from ir_sequence_type ' )
return cr . fetchall ( )
2006-12-07 13:41:40 +00:00
2011-09-27 18:38:30 +00:00
class ir_sequence ( openerp . osv . osv . osv ) :
""" Sequence model.
The sequence model allows to define and use so - called sequence objects .
Such objects are used to generate unique identifiers in a transaction - safe
way .
"""
2008-07-22 14:24:36 +00:00
_name = ' ir.sequence '
2010-12-10 22:42:58 +00:00
_order = ' name '
2013-05-17 12:43:28 +00:00
def _get_number_next_actual ( self , cr , user , ids , field_name , arg , context = None ) :
''' Return number from ir_sequence row when no_gap implementation,
and number from postgres sequence when standard implementation . '''
res = dict . fromkeys ( ids )
for element in self . browse ( cr , user , ids , context = context ) :
if element . implementation != ' standard ' :
res [ element . id ] = element . number_next
else :
# get number from postgres sequence. Cannot use
# currval, because that might give an error when
# not having used nextval before.
statement = (
" SELECT last_value, increment_by, is_called "
" FROM ir_sequence_ %03d "
% element . id )
cr . execute ( statement )
( last_value , increment_by , is_called ) = cr . fetchone ( )
if is_called :
res [ element . id ] = last_value + increment_by
else :
res [ element . id ] = last_value
return res
def _set_number_next_actual ( self , cr , uid , id , name , value , args = None , context = None ) :
return self . write ( cr , uid , id , { ' number_next ' : value or 0 } , context = context )
2008-07-22 14:24:36 +00:00
_columns = {
2011-09-27 18:38:30 +00:00
' name ' : openerp . osv . fields . char ( ' Name ' , size = 64 , required = True ) ,
2011-10-01 00:56:18 +00:00
' code ' : openerp . osv . fields . selection ( _code_get , ' Code ' , size = 64 ) ,
2011-09-27 21:36:12 +00:00
' implementation ' : openerp . osv . fields . selection ( # TODO update the view
2011-09-29 12:27:53 +00:00
[ ( ' standard ' , ' Standard ' ) , ( ' no_gap ' , ' No gap ' ) ] ,
' Implementation ' , required = True ,
2011-09-27 21:36:12 +00:00
help = " Two sequence object implementations are offered: Standard "
" and ' No gap ' . The later is slower than the former but forbids any "
" gap in the sequence (while they are possible in the former). " ) ,
2011-09-27 18:38:30 +00:00
' active ' : openerp . osv . fields . boolean ( ' Active ' ) ,
' prefix ' : openerp . osv . fields . char ( ' Prefix ' , size = 64 , help = " Prefix value of the record for the sequence " ) ,
' suffix ' : openerp . osv . fields . char ( ' Suffix ' , size = 64 , help = " Suffix value of the record for the sequence " ) ,
' number_next ' : openerp . osv . fields . integer ( ' Next Number ' , required = True , help = " Next number of this sequence " ) ,
2013-05-17 17:35:13 +00:00
' number_next_actual ' : openerp . osv . fields . function ( _get_number_next_actual , fnct_inv = _set_number_next_actual , type = ' integer ' , required = True , string = ' Next Number ' , help = ' Next number that will be used. This number can be incremented frequently so the displayed value might already be obsolete ' ) ,
2011-09-27 18:38:30 +00:00
' number_increment ' : openerp . osv . fields . integer ( ' Increment Number ' , required = True , help = " The next number of the sequence will be incremented by this number " ) ,
2011-09-28 13:31:01 +00:00
' 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. " ) ,
2011-09-27 18:38:30 +00:00
' company_id ' : openerp . osv . fields . many2one ( ' res.company ' , ' Company ' ) ,
2008-07-22 14:24:36 +00:00
}
_defaults = {
2011-09-27 21:36:12 +00:00
' implementation ' : ' standard ' ,
2011-09-27 18:38:30 +00:00
' active ' : True ,
2010-02-02 22:27:30 +00:00
' company_id ' : lambda s , cr , uid , c : s . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' ir.sequence ' , context = c ) ,
2011-09-27 18:38:30 +00:00
' number_increment ' : 1 ,
' number_next ' : 1 ,
2013-05-17 18:15:14 +00:00
' number_next_actual ' : 1 ,
2011-09-27 18:38:30 +00:00
' padding ' : 0 ,
2008-07-22 14:24:36 +00:00
}
2006-12-07 13:41:40 +00:00
2011-09-29 13:47:58 +00:00
def init ( self , cr ) :
2011-09-30 15:23:45 +00:00
return # Don't do the following index yet.
2011-09-29 13:47:58 +00:00
# CONSTRAINT/UNIQUE INDEX on (code, company_id)
# /!\ The unique constraint 'unique_name_company_id' is not sufficient, because SQL92
# only support field names in constraint definitions, and we need a function here:
# we need to special-case company_id to treat all NULL company_id as equal, otherwise
# we would allow duplicate (code, NULL) ir_sequences.
cr . execute ( """
SELECT indexname FROM pg_indexes WHERE indexname =
' ir_sequence_unique_code_company_id_idx ' """ )
if not cr . fetchone ( ) :
cr . execute ( """
CREATE UNIQUE INDEX ir_sequence_unique_code_company_id_idx
ON ir_sequence ( code , ( COALESCE ( company_id , - 1 ) ) ) """ )
2011-09-30 18:38:06 +00:00
def _create_sequence ( self , cr , id , number_increment , number_next ) :
""" Create a PostreSQL sequence.
There is no access rights check .
"""
2012-11-23 10:10:28 +00:00
if number_increment == 0 :
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( " Increment number must not be zero. " ) )
2011-09-30 18:38:06 +00:00
assert isinstance ( id , ( int , long ) )
sql = " CREATE SEQUENCE ir_sequence_ %03d INCREMENT BY %% s START WITH %% s " % id
cr . execute ( sql , ( number_increment , number_next ) )
def _drop_sequence ( self , cr , ids ) :
""" Drop the PostreSQL sequence if it exists.
There is no access rights check .
"""
ids = ids if isinstance ( ids , ( list , tuple ) ) else [ ids ]
assert all ( isinstance ( i , ( int , long ) ) for i in ids ) , \
" Only ids in (int, long) allowed. "
names = ' , ' . join ( ' ir_sequence_ %03d ' % i for i in ids )
2011-09-27 21:36:12 +00:00
2011-09-30 18:38:06 +00:00
# RESTRICT is the default; it prevents dropping the sequence if an
# object depends on it.
cr . execute ( " DROP SEQUENCE IF EXISTS %s RESTRICT " % names )
2013-05-17 15:45:44 +00:00
def _alter_sequence ( self , cr , id , number_increment , number_next = None ) :
2011-09-30 18:38:06 +00:00
""" Alter a PostreSQL sequence.
There is no access rights check .
"""
2012-11-26 09:43:37 +00:00
if number_increment == 0 :
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( " Increment number must not be zero. " ) )
2011-09-30 18:38:06 +00:00
assert isinstance ( id , ( int , long ) )
2013-05-17 18:15:14 +00:00
seq_name = ' ir_sequence_ %03d ' % ( id , )
cr . execute ( " SELECT relname FROM pg_class WHERE relkind = %s AND relname= %s " , ( ' S ' , seq_name ) )
if not cr . fetchone ( ) :
# sequence is not created yet, we're inside create() so ignore it, will be set later
return
statement = " ALTER SEQUENCE %s INCREMENT BY %d " % ( seq_name , number_increment )
2013-05-17 15:45:44 +00:00
if number_next is not None :
2013-05-17 12:43:28 +00:00
statement + = " RESTART WITH %d " % ( number_next , )
cr . execute ( statement )
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
def create ( self , cr , uid , values , context = None ) :
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
2011-09-28 09:13:09 +00:00
"""
2011-09-30 18:38:06 +00:00
values = self . _add_missing_default_values ( cr , uid , values , context )
values [ ' id ' ] = super ( ir_sequence , self ) . create ( cr , uid , values , context )
if values [ ' implementation ' ] == ' standard ' :
2012-12-14 13:25:33 +00:00
self . _create_sequence ( cr , values [ ' id ' ] , values [ ' number_increment ' ] , values [ ' number_next ' ] )
2011-09-30 18:38:06 +00:00
return values [ ' id ' ]
2011-09-28 09:13:09 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
super ( ir_sequence , self ) . unlink ( cr , uid , ids , context )
self . _drop_sequence ( cr , ids )
2011-09-28 13:05:48 +00:00
return True
2011-09-28 09:13:09 +00:00
def write ( self , cr , uid , ids , values , context = None ) :
2011-09-30 18:38:06 +00:00
if not isinstance ( ids , ( list , tuple ) ) :
ids = [ ids ]
2011-09-28 09:13:09 +00:00
new_implementation = values . get ( ' implementation ' )
2011-09-30 18:38:06 +00:00
rows = self . read ( cr , uid , ids , [ ' implementation ' , ' number_increment ' , ' number_next ' ] , context )
2011-09-28 09:13:09 +00:00
super ( ir_sequence , self ) . write ( cr , uid , ids , values , context )
2011-09-30 18:38:06 +00:00
2011-09-28 09:13:09 +00:00
for row in rows :
# 4 cases: we test the previous impl. against the new one.
2011-11-18 08:54:39 +00:00
i = values . get ( ' number_increment ' , row [ ' number_increment ' ] )
n = values . get ( ' number_next ' , row [ ' number_next ' ] )
2011-09-28 09:13:09 +00:00
if row [ ' implementation ' ] == ' standard ' :
if new_implementation in ( ' standard ' , None ) :
2013-05-17 12:43:28 +00:00
# Implementation has NOT changed.
# Only change sequence if really requested.
if row [ ' number_next ' ] != n :
self . _alter_sequence ( cr , row [ ' id ' ] , i , n )
else :
# Just in case only increment changed
self . _alter_sequence ( cr , row [ ' id ' ] , i )
2011-09-28 09:13:09 +00:00
else :
self . _drop_sequence ( cr , row [ ' id ' ] )
else :
if new_implementation in ( ' no_gap ' , None ) :
pass
else :
self . _create_sequence ( cr , row [ ' id ' ] , i , n )
return True
2011-09-27 21:36:12 +00:00
def _interpolate ( self , s , d ) :
2011-09-30 18:38:06 +00:00
if s :
return s % d
return ' '
2011-09-27 21:36:12 +00:00
def _interpolation_dict ( self ) :
t = time . localtime ( ) # Actually, the server is always in UTC.
return {
' year ' : time . strftime ( ' % Y ' , t ) ,
' month ' : time . strftime ( ' % m ' , t ) ,
' day ' : time . strftime ( ' %d ' , t ) ,
' y ' : time . strftime ( ' % y ' , t ) ,
' doy ' : time . strftime ( ' % j ' , t ) ,
' woy ' : time . strftime ( ' % W ' , t ) ,
' weekday ' : time . strftime ( ' % w ' , t ) ,
' h24 ' : time . strftime ( ' % H ' , t ) ,
' h12 ' : time . strftime ( ' % I ' , t ) ,
' min ' : time . strftime ( ' % M ' , t ) ,
' sec ' : time . strftime ( ' % S ' , t ) ,
2008-11-22 00:05:33 +00:00
}
2006-12-07 13:41:40 +00:00
2011-09-30 23:24:39 +00:00
def _next ( self , cr , uid , seq_ids , context = None ) :
if not seq_ids :
2011-09-28 09:13:09 +00:00
return False
2011-11-14 11:04:47 +00:00
if context is None :
context = { }
force_company = context . get ( ' force_company ' )
2011-12-15 15:26:35 +00:00
if not force_company :
2011-11-14 11:04:47 +00:00
force_company = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ) . company_id . id
2013-05-17 09:04:21 +00:00
sequences = self . read ( cr , uid , seq_ids , [ ' name ' , ' company_id ' , ' implementation ' , ' number_next ' , ' prefix ' , ' suffix ' , ' padding ' ] )
2011-11-14 11:04:47 +00:00
preferred_sequences = [ s for s in sequences if s [ ' company_id ' ] and s [ ' company_id ' ] [ 0 ] == force_company ]
seq = preferred_sequences [ 0 ] if preferred_sequences else sequences [ 0 ]
2011-09-30 23:24:39 +00:00
if seq [ ' implementation ' ] == ' standard ' :
cr . execute ( " SELECT nextval( ' ir_sequence_ %03d ' ) " % seq [ ' id ' ] )
seq [ ' number_next ' ] = cr . fetchone ( )
2011-09-28 09:13:09 +00:00
else :
2011-09-30 23:24:39 +00:00
cr . execute ( " SELECT number_next FROM ir_sequence WHERE id= %s FOR UPDATE NOWAIT " , ( seq [ ' id ' ] , ) )
cr . execute ( " UPDATE ir_sequence SET number_next=number_next+number_increment WHERE id= %s " , ( seq [ ' id ' ] , ) )
2011-09-28 09:13:09 +00:00
d = self . _interpolation_dict ( )
2013-05-17 09:04:21 +00:00
try :
interpolated_prefix = self . _interpolate ( seq [ ' prefix ' ] , d )
interpolated_suffix = self . _interpolate ( seq [ ' suffix ' ] , d )
except ValueError :
raise osv . except_osv ( _ ( ' Warning ' ) , _ ( ' Invalid prefix or suffix for sequence \' %s \' ' ) % ( seq . get ( ' name ' ) ) )
2011-09-30 23:24:39 +00:00
return interpolated_prefix + ' %% 0 %s d ' % seq [ ' padding ' ] % seq [ ' number_next ' ] + interpolated_suffix
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
def next_by_id ( self , cr , uid , sequence_id , context = None ) :
""" Draw an interpolated string using the specified sequence. """
2012-08-22 11:16:13 +00:00
self . check_access_rights ( cr , uid , ' read ' )
2013-04-19 15:49:20 +00:00
company_ids = self . pool . get ( ' res.company ' ) . search ( cr , uid , [ ] , context = context ) + [ False ]
2011-09-30 23:24:39 +00:00
ids = self . search ( cr , uid , [ ' & ' , ( ' id ' , ' = ' , sequence_id ) , ( ' company_id ' , ' in ' , company_ids ) ] )
return self . _next ( cr , uid , ids , context )
2011-09-27 21:36:12 +00:00
2011-09-30 18:38:06 +00:00
def next_by_code ( self , cr , uid , sequence_code , context = None ) :
2011-12-15 15:26:35 +00:00
""" Draw an interpolated string using a sequence with the requested code.
If several sequences with the correct code are available to the user
( multi - company cases ) , the one from the user ' s current company will
be used .
: param dict context : context dictionary may contain a
` ` force_company ` ` key with the ID of the company to
use instead of the user ' s current company for the
sequence selection . A matching sequence for that
specific company will get higher priority .
"""
2012-08-22 11:16:13 +00:00
self . check_access_rights ( cr , uid , ' read ' )
2013-04-19 15:49:20 +00:00
company_ids = self . pool . get ( ' res.company ' ) . search ( cr , uid , [ ] , context = context ) + [ False ]
ids = self . search ( cr , uid , [ ' & ' , ( ' code ' , ' = ' , sequence_code ) , ( ' company_id ' , ' in ' , company_ids ) ] )
2011-09-30 23:24:39 +00:00
return self . _next ( cr , uid , ids , context )
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
def get_id ( self , cr , uid , sequence_code_or_id , code_or_id = ' id ' , context = None ) :
""" Draw an interpolated string using the specified sequence.
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
The sequence to use is specified by the ` ` sequence_code_or_id ` `
argument , which can be a code or an id ( as controlled by the
` ` code_or_id ` ` argument . This method is deprecated .
2011-09-28 09:13:09 +00:00
"""
2011-10-07 14:54:06 +00:00
# TODO: bump up to warning after 6.1 release
_logger . debug ( " ir_sequence.get() and ir_sequence.get_id() are deprecated. "
2011-09-30 18:38:06 +00:00
" Please use ir_sequence.next_by_code() or ir_sequence.next_by_id(). " )
if code_or_id == ' id ' :
return self . next_by_id ( cr , uid , sequence_code_or_id , context )
else :
return self . next_by_code ( cr , uid , sequence_code_or_id , context )
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
def get ( self , cr , uid , code , context = None ) :
""" Draw an interpolated string using the specified sequence.
2011-09-28 09:13:09 +00:00
2011-09-30 18:38:06 +00:00
The sequence to use is specified by its code . This method is
deprecated .
2011-09-28 09:13:09 +00:00
"""
2011-09-30 18:38:06 +00:00
return self . get_id ( cr , uid , code , ' code ' , context )
2006-12-07 13:41:40 +00:00
2008-07-23 15:01:27 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: