diff --git a/CHANGELOG b/CHANGELOG
index 07929d45ae..7b7015bf40 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Add timestamp sequence
* New transaction management
* Make _timestamp numeric instead of datetime to work with XML-RPC and JSON-RPC
* Add ir.trigger to trigger ModelStorage change
diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py
index f75aa0d0ba..aa13ef7d91 100644
--- a/trytond/ir/sequence.py
+++ b/trytond/ir/sequence.py
@@ -3,9 +3,10 @@
from __future__ import with_statement
from string import Template
import datetime
+import time
from trytond.model import ModelView, ModelSQL, fields
from trytond.tools import datetime_strftime
-from trytond.pyson import In, Eval
+from trytond.pyson import In, Eval, Not
from trytond.transaction import Transaction
@@ -24,30 +25,64 @@ class Sequence(ModelSQL, ModelView):
_name = 'ir.sequence'
_description = __doc__
name = fields.Char('Sequence Name', required=True, translate=True)
- code = fields.Selection('code_get', 'Sequence Type', required=True,
+ code = fields.Selection('code_get', 'Sequence Code', required=True,
states={
'readonly': In('code', Eval('context', {})),
})
active = fields.Boolean('Active')
prefix = fields.Char('Prefix')
suffix = fields.Char('Suffix')
- number_next = fields.Integer('Next Number')
- number_increment = fields.Integer('Increment Number')
- padding = fields.Integer('Number padding')
+ type = fields.Selection([
+ ('incremental', 'Incremental'),
+ ('decimal timestamp', 'Decimal Timestamp'),
+ ('hexadecimal timestamp', 'Hexadecimal Timestamp'),
+ ], 'Type')
+ number_next = fields.Integer('Next Number',
+ states={
+ 'invisible': Not(In(Eval('type'), ['incremental'])),
+ }, depends=['type'])
+ number_increment = fields.Integer('Increment Number',
+ states={
+ 'invisible': Not(In(Eval('type'), ['incremental'])),
+ }, depends=['type'])
+ padding = fields.Integer('Number padding',
+ states={
+ 'invisible': Not(In(Eval('type'), ['incremental'])),
+ }, depends=['type'])
+ timestamp_rounding = fields.Integer('Timestamp Rounding', required=True,
+ states={
+ 'invisible': Not(In(Eval('type'),
+ ['decimal timestamp', 'hexadecimal timestamp'])),
+ }, depends=['type'])
+ timestamp_offset = fields.Float('Timestamp Offset',
+ states={
+ 'invisible': Not(In(Eval('type'),
+ ['decimal timestamp', 'hexadecimal timestamp'])),
+ }, depends=['type'])
+ last_timestamp = fields.Integer('Last Timestamp',
+ states={
+ 'invisible': Not(In(Eval('type'),
+ ['decimal timestamp', 'hexadecimal timestamp'])),
+ }, depends=['type'])
def __init__(self):
super(Sequence, self).__init__()
self._constraints += [
('check_prefix_suffix', 'invalid_prefix_suffix'),
+ ('check_last_timestamp', 'future_last_timestamp'),
]
self._error_messages.update({
'missing': 'Missing sequence!',
'invalid_prefix_suffix': 'Invalid prefix/suffix!',
+ 'future_last_timestamp': 'Last Timestamp could not be in future!',
})
def default_active(self):
return True
+ def default_type(self):
+ return 'incremental'
+
def default_number_increment(self):
return 1
@@ -57,6 +92,15 @@ class Sequence(ModelSQL, ModelView):
def default_padding(self):
return 0
+ def default_timestamp_rounding(self):
+ return 1.0
+
+ def default_timestamp_offset(self):
+ return 946681200.0 # Offset for 2000-01-01
+
+ def default_last_timestamp(self):
+ return 0.0
+
def default_code(self):
return Transaction().context.get('code', False)
@@ -77,6 +121,15 @@ class Sequence(ModelSQL, ModelView):
return False
return True
+ def check_last_timestamp(self, ids):
+ "Check last_timestamp"
+
+ for sequence in self.browse(ids):
+ next_timestamp = self._timestamp(sequence)
+ if sequence.last_timestamp > next_timestamp:
+ return False
+ return True
+
def _process(self, string, date=None):
date_obj = self.pool.get('ir.date')
if not date:
@@ -90,6 +143,35 @@ class Sequence(ModelSQL, ModelView):
day=day,
)
+ def _timestamp(self, sequence):
+ return int((time.time() - sequence.timestamp_offset)
+ / sequence.timestamp_rounding)
+
+ def _get_sequence(self, sequence):
+ if sequence.type == 'incremental':
+ #Pre-fetch number_next
+ number_next = sequence.number_next or 0
+
+ with Transaction().set_user(0):
+ self.write(sequence.id, {
+ 'number_next': number_next + sequence.number_increment,
+ })
+ return '%%0%sd' % sequence.padding % number_next
+ elif sequence.type in ('decimal timestamp', 'hexadecimal timestamp'):
+ timestamp = sequence.last_timestamp
+ while timestamp == sequence.last_timestamp:
+ timestamp = self._timestamp(sequence)
+ with Transaction().set_user(0):
+ self.write(sequence.id, {
+ 'last_timestamp': timestamp,
+ })
+ if sequence.type == 'decimal timestamp':
+ return '%d' % timestamp
+ else:
+ return hex(timestamp)[2:].upper()
+ return ''
+
+
def get_id(self, domain):
'''
Return sequence value for the domain
@@ -108,21 +190,11 @@ class Sequence(ModelSQL, ModelView):
if sequence_ids:
with Transaction().set_user(0):
sequence = self.browse(sequence_ids[0])
- #Pre-fetch number_next
- number_next = sequence.number_next
-
- self.write(sequence.id, {
- 'number_next': (number_next +
- sequence.number_increment),
- })
-
- if number_next:
- return (self._process(sequence.prefix, date=date) +
- '%%0%sd' % sequence.padding % number_next +
- self._process(sequence.suffix, date=date))
- else:
- return (self._process(sequence.prefix, date=date) +
- self._process(sequence.suffix, date=date))
+ return '%s%s%s' % (
+ self._process(sequence.prefix, date=date),
+ self._get_sequence(sequence),
+ self._process(sequence.suffix, date=date),
+ )
self.raise_user_error('missing')
def get(self, code):
diff --git a/trytond/ir/sequence.xml b/trytond/ir/sequence.xml
index 6b4f9ea033..df63ac675c 100644
--- a/trytond/ir/sequence.xml
+++ b/trytond/ir/sequence.xml
@@ -22,12 +22,25 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -48,7 +61,8 @@ this repository contains the full copyright notices and license terms. -->
-
+
+
]]>
diff --git a/trytond/test/__tryton__.py b/trytond/test/__tryton__.py
index 6b3b6303ba..d854433951 100644
--- a/trytond/test/__tryton__.py
+++ b/trytond/test/__tryton__.py
@@ -8,6 +8,7 @@
'depends': ['ir', 'res'],
'xml': [
'import_data.xml',
+ 'sequence.xml',
],
'translation': [
],
diff --git a/trytond/test/sequence.xml b/trytond/test/sequence.xml
new file mode 100644
index 0000000000..4be2b449c3
--- /dev/null
+++ b/trytond/test/sequence.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ Test
+ test
+
+
+
+
+
diff --git a/trytond/tests/test_sequence.py b/trytond/tests/test_sequence.py
new file mode 100644
index 0000000000..834a74f5ee
--- /dev/null
+++ b/trytond/tests/test_sequence.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#This file is part of Tryton. The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+from __future__ import with_statement
+import unittest
+import datetime
+from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, \
+ install_module
+from trytond.transaction import Transaction
+
+
+class SequenceTestCase(unittest.TestCase):
+ '''
+ Test Sequence
+ '''
+
+ def setUp(self):
+ install_module('test')
+ self.sequence = POOL.get('ir.sequence')
+
+ def test0010incremental(self):
+ '''
+ Test incremental
+ '''
+ with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+ sequence_id = self.sequence.create({
+ 'name': 'Test incremental',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'incremental',
+ })
+ self.assertEqual(self.sequence.get_id(sequence_id), '1')
+
+ self.sequence.write(sequence_id, {
+ 'number_increment': 10,
+ })
+ self.assertEqual(self.sequence.get_id(sequence_id), '2')
+ self.assertEqual(self.sequence.get_id(sequence_id), '12')
+
+ self.sequence.write(sequence_id, {
+ 'padding': 3,
+ })
+ self.assertEqual(self.sequence.get_id(sequence_id), '022')
+
+ transaction.cursor.rollback()
+
+ def test0020decimal_timestamp(self):
+ '''
+ Test Decimal Timestamp
+ '''
+ with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+ sequence_id = self.sequence.create({
+ 'name': 'Test decimal timestamp',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'decimal timestamp',
+ })
+ timestamp = self.sequence.get_id(sequence_id)
+ sequence = self.sequence.read(sequence_id, ['last_timestamp'])
+ self.assertEqual(timestamp, str(sequence['last_timestamp']))
+
+ self.assertNotEqual(self.sequence.get_id(sequence_id), timestamp)
+
+ sequence = self.sequence.browse(sequence_id)
+ next_timestamp = self.sequence._timestamp(sequence)
+ self.assertRaises(Exception, self.sequence.write, sequence_id, {
+ 'last_timestamp': next_timestamp + 100,
+ })
+
+ transaction.cursor.rollback()
+
+ def test0030hexadecimal_timestamp(self):
+ '''
+ Test Hexadecimal Timestamp
+ '''
+ with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+ sequence_id = self.sequence.create({
+ 'name': 'Test hexadecimal timestamp',
+ 'code': 'test',
+ 'prefix': '',
+ 'suffix': '',
+ 'type': 'hexadecimal timestamp',
+ })
+ timestamp = self.sequence.get_id(sequence_id)
+ sequence = self.sequence.read(sequence_id, ['last_timestamp'])
+ self.assertEqual(timestamp, hex(sequence['last_timestamp'])[2:].upper())
+
+ self.assertNotEqual(self.sequence.get_id(sequence_id), timestamp)
+
+ sequence = self.sequence.browse(sequence_id)
+ next_timestamp = self.sequence._timestamp(sequence)
+ self.assertRaises(Exception, self.sequence.write, sequence_id, {
+ 'last_timestamp': next_timestamp + 100,
+ })
+
+ transaction.cursor.rollback()
+
+ def test0040prefix_suffix(self):
+ '''
+ Test prefix/suffix
+ '''
+ with Transaction().start(DB_NAME, USER, CONTEXT) as transaction:
+ sequence_id = self.sequence.create({
+ 'name': 'Test incremental',
+ 'code': 'test',
+ 'prefix': 'prefix/',
+ 'suffix': '/suffix',
+ 'type': 'incremental',
+ })
+ self.assertEqual(self.sequence.get_id(sequence_id),
+ 'prefix/1/suffix')
+
+ self.sequence.write(sequence_id, {
+ 'prefix': '${year}-${month}-${day}/',
+ 'suffix': '/${day}.${month}.${year}',
+ })
+ context = CONTEXT.copy()
+ with Transaction().set_context(date=datetime.date(2010, 8, 15)):
+ self.assertEqual(self.sequence.get_id(sequence_id),
+ '2010-08-15/2/15.08.2010')
+
+def suite():
+ return unittest.TestLoader().loadTestsFromTestCase(SequenceTestCase)
+
+if __name__ == '__main__':
+ suite = suite()
+ unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py
index 254dddf29f..ea334b4481 100644
--- a/trytond/tests/test_tryton.py
+++ b/trytond/tests/test_tryton.py
@@ -184,6 +184,8 @@ def all_suite():
suite_.addTests(test_trigger.suite())
import trytond.tests.test_protocols_datatype as test_protocols_datatype
suite_.addTests(test_protocols_datatype.suite())
+ import trytond.tests.test_sequence as test_sequence
+ suite_.addTests(test_sequence.suite())
return suite_
def modules_suite():