[AX]AX2012 Number sequence framework :(一)概览与原理浅析

从AX的老版本开始都有Number sequence,用来对纪录的自动编号,比如销售订单号、工单号等,这一功能在AX2012得到了进一步的加强,先来看看与Number sequence设置有关的内容及基本原理。

系统中用到的所有Number sequence在Organization administration/Common/Number sequences/Number sequences下都能找到,相关的数据保存在表NumberSequenceTable,打开一个具体的Number sequence,可以看到它的以下信息:

  • Number sequence code/Name:编号序列的代号和名称,用来标识一个编号序列,分别对应表NumberSequenceTable的NumberSequence和Txt字段,注意NumberSequence字段并不是表NumberSequenceTable的主键,它和NumberSequenceScope字段一起构成一个Unique索引,而NumberSequenceScope字段保存表NumberSequenceScope纪录的recId,NumberSequenceScope和下面要讲的Scope parameters是相关的,表示编号序列的应用范围,它和编号序列的代号一起唯一确定一个编号序列,所以如果你在Number sequence list page中看到同名的编号序列不要奇怪,它们的应用范围是不一样的。NumberSequenceScope表包括DataArea、FiscalCalendarPeriod、LegalEntity、OperatingUnit几个主要字段,由公司、年度日历期间、法律实体、运营单元确定一个Scope。比较难理解的是FiscalCalendarPeriod,它对应表FiscalCalendarPeriod定义的总账期间,比如财务的某月记账期间,可以认为编号序列可以随记账期间的不同而不同,但是我想很少有这样的应用实例吧。
  • Scope parameters:这组下的数据其实是可变的,在NumberSequenceDetails form上有个Scope的下拉框,它的可选项目来自于NumberSeqScope::getValidScopeTypes()方法返回的Scope选项列表,都是定义在类NumberSeqScope中通过本地宏定义的整数:
// Define the scope type values for the valid parameter type combinations.
    #Define.scopeTypeGlobal(0)
    #Define.scopeTypeDataArea(1)
    #Define.scopeTypeLegalEntity(2)
    #Define.scopeTypeOperatingUnit(4)
    #Define.scopeTypeFiscalCalendarPeriod(8)
    #Define.scopeTypeDataAreaFiscalCalendarPeriod(#scopeTypeDataArea + #scopeTypeFiscalCalendarPeriod)
    #Define.scopeTypeLegalEntityFiscalCalendarPeriod(#scopeTypeLegalEntity + #scopeTypeFiscalCalendarPeriod)

这组数字相当于二进制构成的Flag,通过某位来表示不同的Scope类型,包括全局范围(0)、公司(1)、法律实体(2)、运营单位(4)及年度日历期间(8)等,注意后面两个是前几个的或运算的BOOL值。将这些数字转换成具体的字符串标签是在NumberSeqScope::getScopeTypeLabel()通过检索NumberSeqParameterType枚举的各子项标签完成,NumberSeqParameterType枚举只有DataArea、LegalEntity、OperatingUnit、FiscalCalendarPeriod四个选项,和NumberSequenceScope的四个字段对应,前面Scope类型列出的后两种组合类型的字符串标签就是通过连接两种基本类型得到的,比如#scopeTypeDataAreaFiscalCalendarPeriod显示的是“Company and Fiscal calendar period”。通过选择这个下拉框的某个选项,form上会出现相应的公司选择、法律实体选择、期间选择等内容,这样你就设定了编号序列的应用范围,有点复杂,简单的说你可以在NumberSequenceScope表的几个字段输入不同内容来标记一个编码序列应用范围,但并不是任何组合都是有效的,有效的组合是通过上面scopeTypeXXX来定义及相关代码来控制的。

在NumberSequenceDetails form界面上你可能发现有的Number sequence是不允许编辑Scope的,这是因为一旦编码序列被引用(下面要讲到的reference)就不能再编辑了。

  • Segments:在这节你可以定义编码序列所用的格式,可以用公司名称、字符串常量、自动产生的数字来分段组合编码格式。在前面选择Scope的时候如果选择了公司类型的Scope,会在Segments中自动添加公司名称的分段。可以添加多组自动数字的分段,但它们是统一增长的,比如你的编码序列从1开始到99最大,编码的格式为“CEE-#-#”,CEE为公司名称,通过字符串常量“-”分割了两个自动数字分段“#”。产生的序列从“CEE-0-1”开始,到了“CEE-0-9”,往下一个就是“CEE-1-0”了。
  • References:这里定义编码序列被什么地方引用。首先要从各个模块Setup下的Module parameters设置说起,几乎每个模块的参数设置界面都有一个Number sequence的TAB,这里列出了该模块某个功能所用到的所有编码序列,可以在这里直接设置某项功能的编码序列,但不推荐这样做,更好的方法使用后面要讲到的“Set up number sequence” Wizard。首先这些模块下的Numer sequence reference是从哪里来的呢?它们来自于表NumberSequenceReference,它的三个字段NumberSequenceDatatype、NumberSequenceId、NumberSequenceScope分别保存来自于表NumberSequenceDatatype、NumberSequenceTable、NumberSequenceScope纪录的RecId,后面两个表已经讲过了,来看表NumberSequenceDatatype纪录的是什么内容呢?首先AX的Number sequence是基于某种EDT类型的,比如客户代码CustAccount,编码序列产生的值就是用来填充该EDT类型的字段,在后续接受如何添加自定义模块Number sequence的时候我们会看到这些信息是在NumberSeqApplicationModule的扩展类中通过NumberSeqDatatype类创建的,在NumberSeqDatatype类的一个实例中指定EDT的类型ID、帮助说明文字、Configuration key、Module Enum、是否连续、是否手工、连同它的Wizard生成编码序列时的最大最小值、应用的Scope type(注意这里保存的不是NumberSequenceScope表纪录的recid,而是前面说到的scope type的几种类型的整数数值)等信息,这些内容保存在表NumberSequenceDatatype。

注意NumberSequenceReference表中的纪录不是在这个时候添加的,它是在打开xxxParameters form中由 \Classes\NumberSeqApplicationModule\createReferencesForScope()根据模块下的Number sequence data type动态添加的,每次打开这参数form都会自动更新一次,如果已经针对某个data type相应scope(会根据当前公司、scope type在NumberSequenceScope表中查找或者新建纪录)已经在表NumberSequenceReference有了对应的纪录,也就不会反复添加了。需要注意到NumberSequenceReference表的SaveDataPerCompany设成了No,那么如果一个Number sequence reference是按公司分scope的,那么怎么找到相应公司针对某个data type的number sequence reference引用呢?其实上针对这种情况会在NumberSequenceReference生成多条纪录(在每个公司下打开一次xxxParameters表单时生成当前公司的、或者在使用set up number sequence wizard时生成所有公司的),每条纪录对应一条NumberSequenceScope(这个表SaveDataPercomapny也是NO)纪录,在这里会纪录具体是哪个公司。

那么什么又是Number sequence的模块呢?其实就是枚举NumberSeqModule所定义各子项,如果我们要添加自己的序列模块,在这个enum下添加一个子项就是了。

回到Number sequence detail form下的references一节,这里可以看到当前number sequence已经被应用到哪些模块的number sequence reference,你也可以添加或者删除多个引用,也就是说不同data type可以使用同一个number sequence来生成数据,这里的修改自然也反映到 xxx Parameters表单的Number sequence设置中。在References下添加的引用可以设置“Reuse numbers”和“use same number as”两个选项,前者对应NumberSequenceReference表的“AllowSameAs”字段,后者是使用NumberSeqDatatype类的parmDatatypeSameAsId()方法指定这个Data type可以重用另外一个Data type的编号,在用NumberSeq::newGetNumAndVoucher(seq1,voucherseq).numAndVoucher()一次调用得到普通编号和voucher编号的时候,会检查voucherseq对应reference的“AllowSameAs”字段,如果这个字段为true即勾选了“Reuse nubmers”,得到的voucher号码和前一个序列的号码是一样的。其主要的用途是保证voucher的号码和业务数据编号一致,比如AR下你可以勾选“Customer invoice voucher”重用“Customer invoice”的号码,这样Invoice产生的Voucher和Invoice的号码相同,方便查找。

  • General:这里主要是编号序列的是否启用、是否手工、是否连续、可以手工往上往下调整、最大最小值、当前值等信息,编辑模式下可以修改相关内容。
  • Automatic cleanup:如果编号序列设成了连续,为了重用那些已经分配但是因为操作退出或者失败而废弃的编号,需要对这些废弃编号回收重用,这就是Clean up。这里可以设置启用自动回收、及自动回收的间隔(小时为单位)。连续编号的分配是在 \Classes\NumberSeq\getNumInternal()完成,最终调用是在NumberSequence::getNextNumber(),这是一个系统类,代码没有公开(使用了某种专利算法??)。需要注意的是连续编号的分配必须放在ttsbegin...ttscommit,否则会得到“System Does not support setup continuous of number sequence”这样的错误,放在tts事务中的原因是因为要使用application.ttsNotifyCommit()机制调用\Classes\NumberSeqGlobal\ttsNotifyCommit()进而调用\Classes\NumberSeqGlobal\runAutoClean()来实现废弃编号的回收。如果出现错误 \Classes\Application\ttsNotifyAbort()也会调用Classes\NumberSeqGlobal\ttsNotifyAbort()开始一个废弃编号的检查回收。
  • Performance:对非连续的编号序列可以启用预分配,这里设置每次分配的数目N,启用预分配后,系统一次从数据库分配N个编号保存在系统内存,后续的申请不需要再到数据库计算,对于频繁的编号分配可以启用这个选项。内部实现上针对非连续的序列会构建一个NumberSeq的子类NumberSeq_Fast,这个子类重载getNumInternal(),由它先在 appl.numberSeqGlobal().numberSeqNumCache()中查找已经分配在缓存中的编号,如果缓存中的编号已经用尽,就启动一个UserConnection的数据库连接从numberSequenceTable表分配一批编号填充到缓存(appl.numberSeqGlobal().numberSeqNumCache().fillCache()函数),再从这批新填充的缓存编号中取出一个。由于是缓存在appl的成员变量中的,所以关闭了AX 的client,已经分配的这批缓冲就丢掉了,但是NumberSequenceTable所纪录的next已经是这批后的下一批开始编号,这样就造成了编号的不连续,所以对于非连续的编号序列才可以启用预分配。

你可以在Organization administration/Common/Number sequences/Number sequences界面上新建一个Number sequence,在各模块的参数form上来选择使用它,但是更为方便的是使用CEE/Organization administration/Common/Number sequences/Number sequence界面或者Number sequence detail界面的“Generate”按钮启动Wizard来为各模块未设置的编号序列引用自动创建number sequence,它自动根据前面提到的Number sequence data type搜索尚未配置的序列引用,一路NEXT就可以为这些引用创建并分配相应的编号序列,这就是使用Number sequence framework的好处之一吧。如果所有的序列引用已经正确配置,再次使用这个Wizard你会得到“The application already has the required number sequence”的提示。需要注意的是如果某个data type是FiscalCalendarPeriod的scope类型,wizard会为每一个期间创建一个单独的number sequence,这会是一个庞大的数字,谨慎选择吧。另外还需要注意的是包含FiscalCalendarPeriod的Data type的引用,不会出现在xxx parameters的number sequence列表中。

另一个和Number sequence有关的配置是在Organization administration/Common/Number sequences/Segment configuration,这里列出所有的Number sequence module,选择Module后会显示出相应Module的所有Reference(模块的NumberSequenceDatatype纪录),在内容面板上会显示你可以配置的Number sequence分段信息,比如“Company”旁边带个checkbox。这些checkbox可能经常是灰化的,一是如果这个reference已经配置了number sequence就不能再修改了,二是如果NumberSequenceDatatype在添加时没有指定scope为可配置(NumberSeqDatatype.addParameterType()第三个参数为false,true表示可配置),这两种情况下都不允许修改相应的segment(其实是data type的scope type)。假设我们对一个默认scope为DataArea的data type把前面所说的“Company”勾去掉了,它的直接影响是使用“Set up number sequence wizard”配置创建序列时不会再为每个公司创建一个单独的Number sequence,而是所有公司都共享这样一个编号序列。可以对一个NumberSequenceDatatype可用调用NumberSeqDatatype.addParameterType()添加不同的scope类型,这个configuration界面上会出现相应的多个scope类型的checkbox,你可以选择关闭其中的一个或多个类型,比如前面讲到的scopeTypeDataAreaFiscalCalendarPeriod,你可以关闭掉针对财务期间的分配,仅保留针对公司的分配。前面有说到包含包含FiscalCalendarPeriod的Data type的引用,不会出现在xxx parameters的number sequence列表中,实际上并不全对,如果你在configuration中关闭了scopeTypeDataAreaFiscalCalendarPeriod scope type的“Fiscal calendar period”选择,它就会出现在xxx parameters表单的number sequence中了。

我们还需要关注表NumberSequenceList,它纪录连续编号序列已分配的编号使用状态,当前与它关联的form是NumberSequenceList,在Number sequence detail表单上的按钮“Status list”按钮打开的就是这个form。以创建工单为例,在打开“Create production order”窗口时,系统自动为工单分配一个工单号(具体\Classes\NumberSeq\getNumFromList()),这时候观察表NumberSequenceList中与工单号编号序列有关的纪录,你会发现一天当前工单号“Status”为“Active”的纪录;如果我们取消了工单的创建,NumberSequenceList表中的这条纪录仍会存在,但是状态变成了“Free”,这个号在下次创建工单时被重用;而如果是成功创建了工单,NumberSequenceList的相应纪录被删除。所以这个表的作用就是用来跟踪那些已经分配但尚未成功使用的连续编号,要注意的是如果工单顺利创建后被删除,这个被删除工单的工单号是不会被重用的,系统认为这个编号已经被分配使用,所以连续编号指的仅仅是编号分配时的连续,而不是我们业务数据纪录编号的连续。连续编号的分配都是在\Classes\NumberSeq\getNumFromList()调用NumberSequence::getNextNumber()完成的,注意到NumberSeq类还有个叫做getNumFromTable()方法,它是从NumberSequenceList表分配编号,但是在我测试中从未发现它被调用过。

如果我们对一个NumberSequence做了修改,系统会自动纪录这些修改历史到表NumberSequenceHistory,对应从Number sequence detail窗口“History”打开的NumberSequenceHistory窗口,在这里能看到Number sequence的创建以及每次修改的历史数据。

在AOT中还有一些其他NumSeq开始的表和类,它们一起组成构建了Number sequence framework,这里不一一分析,我们的浅析到此为止,大家可以参看MSDN的相关内容(http://msdn.microsoft.com/EN-US/library/aa608474)以及一份“Using the Enhanced Number Sequence Framework in Microsoft Dynamics AX 2012”的白皮书。

原文地址:https://www.cnblogs.com/duanshuiliu/p/2701117.html