1. 应用场景
ERP系统中需要产生序列号的地方很多,最常见的是各种单据的单号、商品的编码、条码等等。这些号码都需要具有唯一性和连续递增性,且根据具体情况,需要有一定的规则(比如前缀中含有单据类型码、操作员码、日期等)。
2. 手工生成序号
单据创建时,将会调用模型父类的create方法。我们可以在模型中覆写父类的create方法,并计算当前单据类型的最新序号。比如下面这段代码:
@api.model
def create(self,values):
self.env.cr.execute('select * from xxx_model order by order_code desc limit 1')
x=self.env.cr.dictfetchall()
if len(x)==0:
values['order_code']='0000001'
else:
xx=int(x[0]['order_code'])
xx+=1
xxx=str(xx)
z=xxx
for i in range(6-len(xxx)):
z='0'+z
values['order_code']=z
return super().create(values)
self.env.cr.execute()函数可以直接执行sql语句,但这是一种惰性查询,真正触发数据库查询动作的是x=self.env.cr.dictfetchall(),这条语句将查询结果赋值给变量x。这条sql语句的意思是:查找xxx_model中order_code最大的一条记录。如果没有找到该记录,说明当前是首张单据,则给其赋值‘0000001’。若找到记录,则将该记录的order_code转换成int类型,加1后再按规则首位补0,补齐7位。
由于odoo ORM实现了serializable级别的事务隔离,因此这种生成序列号的方式是没有问题的,否则需要通过加锁的方式确保多用户同时创建单据时数据库一致性问题。
3. ir.sequence创建序列号
odoo为我们提供了一个很好用的序列号生成工具ir.sequence,使用该工具只需要在数据文件中注册一条ir.sequence记录,在模型代码中就可以调用该序列。
3.1 注册ir.sequence记录
在需要生成序列号的模型视图文件中,添加一条ir.sequence记录,并可定义序列的前缀规则,如下:
Purchase Order
sequence.purchase_order
%(year)s%(month)s%(day)s
5
prefix字段定义了前缀,前缀的写法是一段格式化字符串,如下是ir.sequence模型中处理前缀的方法,可以看到,%(year)s会被解析为%Y,并通过date的strftime函数解析出四位数的年份,month、day...的处理方法都是如此。因此,这里可以写的参数包括year、month、day、y、doy(day of year)、woy(week of year)、weekday、h23、h12、min、sec这10种的组合。还可以添加一些字符串前缀,比如SN,用来标识单据类别。
def _get_prefix_suffix(self, date=None, date_range=None):
def _interpolate(s, d):
return (s % d) if s else ''
def _interpolation_dict():
now = range_date = effective_date = datetime.now(pytz.timezone(self._context.get('tz') or 'UTC'))
if date or self._context.get('ir_sequence_date'):
effective_date = fields.Datetime.from_string(date or self._context.get('ir_sequence_date'))
if date_range or self._context.get('ir_sequence_date_range'):
range_date = fields.Datetime.from_string(date_range or self._context.get('ir_sequence_date_range'))
sequences = {
'year': '%Y', 'month': '%m', 'day': '%d', 'y': '%y', 'doy': '%j', 'woy': '%W',
'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S'
}
res = {}
for key, format in sequences.items():
res[key] = effective_date.strftime(format)
res['range_' + key] = range_date.strftime(format)
res['current_' + key] = now.strftime(format)
return res
d = _interpolation_dict()
try:
interpolated_prefix = _interpolate(self.prefix, d)
interpolated_suffix = _interpolate(self.suffix, d)
except ValueError:
raise UserError(_('Invalid prefix or suffix for sequence \'%s\'') % (self.get('name')))
return interpolated_prefix, interpolated_suffix
除了prefix,也可以有suffix(后缀)、padding、number_next等重要字段。
padding表示序列号数字部分的位数,比如padding=5,则序列号为[prefix]00001这样的类型。
number_next表示序列号的递增数,比如number_next=1,则下一个序列号比上一个序列号增1。
3.2 模型中使用序列号
注册了序列号后,就可以在模型中使用。需要产生序列号的地方一般在模型的create函数中。如下这段代码:
@api.model
def create(self,values):
try:
values['order_code']=self.env['ir.sequence'].next_by_code('sequence.purchase_order')
stock=self.env['sunrise.u.stock']
if 'order_items' in values.keys():
for item in values['order_items']:
stock_commodity=stock.search([('commodity','=',item[2]['commodity'])])
stock_commodity.amount+=item[2]['amount']
return super().create(values)
except:
raise Exception('b_purchase_order.py:create()')
try中的第一行则使用了上文注册的序列号,通过self.env['ir.sequence'].next_by_code('sequence.purchase_order'),可获取注册id="seqence_b_purchase_order"这条ir.sequence对象的next_by_code方法,获取到最新序号。