最近遇到一个直接交运过账的问题,应该是AX2009系统的bug。
背景:
如果需要把采购的物料直接由供应商交付给客户,需要用到直接交运功能,AX2009的实现方式是采购订单收货的地址直接写客户的地址,在采购订单过账装箱单时自动过账销售订单的装箱单。
问题重现:
环境:
操作系统:Windows 2003
AX2009: SP1+RU7 5.0.1500.4570
数据:使用AX2009的DEMO数据库的CEU公司
1.创建一个销售订单
2.创建销售订单行
选择1000和5008两个物料
3.设置销售订单为直接交运
4.创建直接交运的采购订单,功能->创建直接交运
5.自动产生两个采购订单,分别对应销售订单的一行
6.通过应付账款->期间->过账装箱单,同时过账两个采购订单
7.点击 确定,问题出现如下:
原因分析:
整个过程采用的都是AX2009的标准功能,从整个操作看也没有什么不妥之处,从业务上看,创建销售订单,直接交运,两个供应商同时发货给了客户,于是在系统里选择这两个采购订单过账装箱单,这个在业务上也是合情合理的,但是AX2009为什么给出了这么一个奇怪的报错,超交?让人有点丈二和尚。
1.查看采购订单和销售订单行的过账情况:
采购订单:
销售订单:
从上面两个图看,第一个采购订单000385及其对应的销售订单行已经顺利过账,事务已经提交,问题出在过账第二个采购订单时。从系统的报错看,过账第二个采购订单的时候,它在尝试过账1000这个物料,但是第二个采购订单关联的物料是5008,这个采购订单跟1000物料没有一毛钱的关系。
从这里猜测看应该是在过账第二个采购订单关联的销售订单时错误地选择了销售订单行,把本不应该过账的1000再次过账了,其实只过账5008这个行就可以了。
代码分析
AX2009的代码称这种直接交运的过账为internalPosting,因为这个过账是在同一个公司下,对于不同公司间交易的过账称之为externalPosting。
过账采购订单对应的销售订单的代码在PurchFormLetter_PackingSlip的intercompanyPost,如果采购订单是直接交运的,就会调用InterCompanyPostSales过账相应的销售订单.
接下去看类InterCompanyPost的formletterCollect方法,整个方法执行后的最终结果是找到需要过账的销售订单,查找的逻辑是根据VendPackingSlipTrans的
InventRefTransId找到相应的销售订单行,进而得到销售订单。至于VendPackingSlipTrans 的InventRefTransId是从哪里来的,就要去看PurchFormLetter_PackingSlip的writeJournlLine方法了,该方法中有initFormPurchLine的调用,也就是说这个InventRefTransId是从PurchLine的InventRefTransId来的,而PuchLine的InventrefTransId是创建直接交运的时候产生的,整个值也就是对应的销售订单行的InventTransId.
从这段代码看,只是找到了需要过账的销售订单并将其放到Map中,但并未限定只过账本次过账的采购订单对应的行。
接下去看,是否还有地方可以限定,interCompanyPost最终会调用SalesFormLetter的update方法,这个方法是用来过账销售订单的简单的API,该方法会构造出适当的SalesParmUpdate,SalesParmTable,SalesParmLine等记录,用来过账销售订单。
那么产生这些记录的时候会不会有限定只过账本次采购订单对应的销售订单行的语句呢?限定的语句在SalesFormLetter的updateQueryBuild方法中:
{
mapSalesRecordEnumerator = interCompanyMap.getEnumerator();
while (mapSalesRecordEnumerator.moveNext())
{
localSalesTable = mapSalesRecordEnumerator.currentValue();
chooseLines.query().dataSourceTable(tablenum(SalesTable)).addRange(fieldnum(SalesTable, SalesId)).value(queryValue(localSalesTable.SalesId));
noSelected++;
}
this.callerFormDataSource(null);
if (noSelected > 1)
this.defaultGiroType(NoYes::Yes);
else
this.defaultGiroType(NoYes::No);
}
可以看出这段方法就是把interCompanyPost的formletterCollect得到的SalesId在这里做了限定,也就是让SalesFormLetter只过帐采购订单对应的销售订单。
但是依然没看到过滤销售订单行,让只跟本次过账的采购订单关联的销售订单行过账。
这样错过了第二次机会。
这样就只能依靠在正式过账前的最后一次过滤来限定了,这个过滤发生在创建SalesParmLine时,因为要判断本次销售订单行要过账的数量,如果能在这个时点过滤也是可以的。
创建SalesParmLine的方法是在SalesFormLetter的CreateParmLine方法
&& !this.interCompanyParmSelectFromJournal())
{
localSalesParmLine.Closed = this.interCompanyParmLineClosed(_salesLineOrig);
[localSalesParmLine.DeliverNow, localSalesParmLine.RemainBefore , localSalesParmLine.RemainAfter] = this.qtySales (_salesLineOrig, this.interCompanyParmLineQty(_salesLineOrig));
[localSalesParmLine.InventNow , localSalesParmLine.RemainBeforeInvent, localSalesParmLine.RemainAfterInvent] = this.qtyInvent (_salesLineOrig, this.interCompanyParmLineQty(_salesLineOrig, true));
salesLine.SalesDeliverNow = localSalesParmLine.DeliverNow;
salesLine.setInventDeliverNow();
if (localSalesParmLine.Closed)
{
localSalesParmLine.RemainAfter = 0;
localSalesParmLine.setRemainAfterInvent();
}
}
其中决定本次要过账数量的方法是interCompanyParmLineQty,过账装箱单数量的方法在SalesFormLetter_PackingSlip
where vendPackingSlipTransLocal.InventTransId == salesLineLocal.InventRefTransId
exists join vendPackingSlipJourLocal
where vendPackingSlipJourLocal.PackingSlipId == vendPackingSlipTransLocal.PackingSlipId
&& vendPackingSlipJourLocal.DeliveryDate == vendPackingSlipTransLocal.DeliveryDate
&& vendPackingSlipJourLocal.PurchId == vendPackingSlipTransLocal.PurchId
&& vendPackingSlipJourLocal.InternalPackingSlipId == vendPackingSlipTransLocal.InternalPackingSlipId
&& vendPackingSlipJourLocal.ParmId == interCompanyParmId;
这个语句是查询vendPackingSlipTrans表里的记录,条件是inventTransId等于salesLine的InventRefTransId并且ParmId等于过账采购订单时产生的ParmId。
这个语句可以过滤只过账当前采购订单对应的销售订单行吗?
不能。
因为是同时过账两个采购订单000385和000386,按照AX2009的过账逻辑和数据结构,过账时用的是同一个parmId,这样不论是1000还是5008,在过账第二个采购订单时,上面的语句都会得到过账的数量,这样1000就重复过账了,于是提示超交了。
这样三个地方可以过滤只过账当前过账的采购订单对应的销售订单行的地方都没过滤,于是问题产生了。
解决方法
分析原因后就比较容易解决了,在上述三个地方的任意一个地方进行过滤就可以了。IntercompanyPost类修改比较麻烦,在CreateParmLine的修改属于事后补救,所以感觉在SalesFormLetter的updateQueryBuild这个地方过滤比较好。