瞎子摸象---库存篇
本文描述库存模块中最重要的两个类InventMovement和InventUpdate,当然这两个类还有相应的子类.
毋庸置疑,AX中库存模块至关重要,我的理解是与财务模块并列两大基础模块,其他模块都依赖于这两个模块,当然从独立性的角度来看,库存模块也依赖于财务模块,因为库存的变动要在财务上有所体现,其他模块只要涉及到库存的变动都由库存模块去实现,具体的实现就是由InventMovement和InventUpdate去做的.
一.类设计
首先按照我的理解分析一下AX为什么要用InventMovement和InventUpdate两个类来做处理库存的变动.
先来看一下InventMovement和InventUpdate与其子类的静态类图:
图一:InventMovement与其子类的关系图
图二:InventUpdate与其子类的关系图.
从上面的关系图可以看出InventMovement的子类是按照业务类型来分的,一个业务类型对应一个具体子类,比如销售对应InventMov_Sales,采购对应InventMov_Purch,生产对应InventMov_Prod.
而InventUpdate的子类是按照库存的状态来设计其子类的,比如InventUpd_Estimated这个类是对应库存初始状态的,对于出库就是在单,对于入库就是已订购,InventUpd_Physical对应库存的物理更新状态,对于出库就是已扣除,对于入库就是已收到.
从数据结构看,InventUpdate操作的表主要是InventTrans,而InventMovement操作的是各个业务模块对应的表,而这些业务模块对应的表都会对应一条或多条InventTrans,比如对于InventMov_Sales就是操作的表SalesLine,该表通过InventTransId对应InventTrans的一条或多条记录,,InventMov_Purch操作的是PurchLine,该表通过InventTransId对应InventTrans的一条或多条记录,等等.
分成InventMovement与Inventupdate两个类,这样分工很明确,与各个业务模块有关的操作交给InventMovement去处理,而具体的库存操作交给InventUpdate类去处理,这样将来业务发生了变更或者出现了新的业务类型,只需要更改或者新增一个新的InventMovement子类就可以了,而不需要更改InventUpdate类的实现.
按照开发四的说法,InventMovement类是Data Carrier,负责存储InventUpdate所需要的数据.这样说蛮对,其实看一下InventMovement及其子类的方法可以看出,它实际上就是对各个业务表的封装,比如InventMov_Sales这个类,这个类就是封装了表SalesLine的值,比如该SalesLine对应的产品的库存模型组信息,维度信息,过账科目信息,以及SalesLine的数量信息等等,当然InventMovement也不是吃闲饭的,当个饭桶而已,它还是做点了一些实事的,一件事情是在物理过账或者财务过账的时候(也就是PackingSlip或者Invoice的时候)要生成凭证,这个动作是有InventMovement做的,另一方面在InventUpdate更新完成后,需要更改原来业务表的数据,这个也是有InventMovement来完成的.这两个事情由InventMovement去做很容易理解,过账到那个科目以及要更新哪些业务表是各个业务模块了解的,不能交给InventUpdate,要不然InventUpdate的负担就重了,不单纯了.
InventMovement是InventUpdate的成员变量,在实例化InventUpdate的时候必须使用InventMovement,也就是说实例化InventUpdate之前必须首先实例化InventMovement,这两个是成双对出现的.因为InventUpdate实际上是对InventTrans表的操作,而必须有人告诉它要操作该表中的哪些记录,也就是InventTransId字段的值是多少,这个值封装到了InventMovement中.当然还有其他的事情,在使用InventUpdate操作的过程中,做完某些动作需要对业务表进行操作,或者需要生成凭证在财务上有所反应,这些通过调用其成员变量InventMovement的方法去实现.
二.使用
光说不练假把式,各个模块到底是怎么使用这两个类的那?我们分析几个例子:
1.销售订单:
在创建或修改销售订单行的时候,SalesLine的Insert或者update方法会通过InventUpd_Estimated创建或修改SalesLine对应的表InventTrans中的记录,具体的代码在
SalesLine->Insert->SalesLineType->Insert
{
estimated = InventUpd_Estimated::newInventMovement(InventMovement::construct(salesLine, InventMovSubType::None, childBuffer));
estimated.updateNow();
}
接下来对销售订单做各种过账动作的时候是通过SalesFormLetter的子类针来处理的,并不是所有的操作都要对库存有所影响,比如过账里确认单并不会影响库存,在过账操作的五个动作中只有装箱单和发票这两个动作会更改库存状态(当然领料单登记也会更改库存状态为Picked,销售的Pick和采购的Registered实际上是库存操作,这时库存状态的更改不是通过SalesFormLetter和PurchFormLetter及其子类实现的,后面会介绍一下),我们看一下销售订单在做装箱单时是如何更新库存状态的:
SalesFormLetter_PackingSlip->UpdateNow
{
InventMovement inventMovement;
SalesLineType salesLineType;
TransactionTxt transactionTxt;
boolean qtyReduced = false;
SalesId oldSalesId;
;
while (salesParmLine)
{
inventMovement = InventMovement::construct(salesLine);
this.updateInventory(inventMovement);
return true;
}
再看一下updateInventory方法的实现:
{
InventUpd_Physical inventUpd_Physical;
;
inventUpd_Physical = InventUpd_Physical::newSalesPackingSlip(_inventMovement,
salesParmLine,
number,
salesParmUpdate.ReduceOnHand);
inventUpd_Physical.updateNow(ledgerVoucher);
updateNow = -inventUpd_Physical.updPhysicalUnit();
updateNowInvent = -inventUpd_Physical.updPhysical();
}
过账发票的时候代码是一样的,只不过updateInventory实例化的InventUpdate的子类不同.
{
InventUpd_Financial inventUpd_Financial;
;
inventUpd_Financial = InventUpd_Financial::newSalesInvoice(_inventMovement,
ledgerVoucher,
number,
salesParmLine,
salesParmUpdate.ReduceOnHand);
inventUpd_Financial.updateNow();
updateNow = -inventUpd_Financial.updFinancialUnit();
updateNowInvent = -inventUpd_Financial.updFinancial();
invoiceUpdatedOnly = -inventUpd_Financial.updPhysicalUnit();
}
采购跟销售的代码是完全一样的,这里就不再赘述.
销售和采购的FormLetter是一个很迷人的系列,实际上FormLetter是个管家婆或者说协调者,它用了很多的资源来实现销售和采购的功能,这里只是介绍了它怎么去使唤库存的类去实现库存的变动的,还有很多仆人供它使用,后面会用一篇文章看一下这个管家婆是如何协调各种资源为我所用的.
AX中还有一种类型的,可以称之为日志(Journal)系列.实际上库存日志和生产过程的领料和完工入库都是通过日志系列来完成的,日志的内容比FormLetter系列更庞杂,嫡系众多,这里不展开论述了,只交待一下在什么地方调用本章介绍的类去更新库存.
创建业务表对应的InventTrans对应的记录跟销售采购订单一样都是通过在业务表的insert方法中通过调用类InventUpd_Estimated的方法来实现的.物理和财务过账是在Journal系列类的最底层的子类通过重载postTransLedger方法实现的.比如库存日志是通过InventJournalCheckPost_Movement这个类是通过postTransLedger去实现的:
JournalTransData _journalTransData,
LedgerVoucher _ledgerVoucher)
{
super(_journalTransData, _ledgerVoucher);
movement = InventMovement::construct(inventJournalTrans);
financial = InventUpd_Financial::newCheckPostInventJournalTrans(movement, _ledgerVoucher, abs(inventJournalTrans.CostAmount) * movement.transSign());
movement.journalPostTrans(_ledgerVoucher, financial);
if (inventJournalTrans.inventJournalTable().JournalType == InventJournalType::Transfer)
{
movement_Transfer = InventMovement::construct(inventJournalTrans,InventMovSubType::TransferReceipt);
movement_Transfer.parmUpdCostAmountphysical(-financial.updCostAmountPhysical());
financial = InventUpd_Financial::newCheckPostInventJournalTrans(movement_Transfer, _ledgerVoucher, -financial.updCostAmountInvent());
movement_Transfer.journalPostTrans(_ledgerVoucher, financial);
}
}
再比如生产的完工入库,是通过ProdJournalCheckPostProd的postTransLedger方法实现的:
JournalTransData _journalTransData,
LedgerVoucher _ledgerVoucher
)
{
..
physical= InventUpd_Physical::newCheckPostProdJournalProd(prodTable,prodJournalProd);
physical.updateNow(_ledgerVoucher);
..
}
我们知道对于库存日志没有一个中间物理过账的过程,而是直接将其财务过账了.而生产过程不同,领料或者完工入库只是将库存记录的状态变成了已扣减和已收到而已,也就是将其物理过账,而生产过程的最后一步结束动作才会将其财务过账,这一步并没有纳入日志系列,而是通过类ProdUpdHistoricalCost的updateBOMConsumption方法和updateProduction方法实现的:
BOM:
{
ProdBOM prodBOM;
InventUpd_Financial financial;
setprefix(strfmt("@SYS28536"));
while select forupdate prodBOM
index hint NumIdx
where prodBOM.ProdId == prodTableJour.ProdId &&
prodBOM.RemainBOMFinancial != 0
{
financial = InventUpd_Financial::newProdBOMHistoricalCost(prodBOM,_ledgerVoucher, new InventTransIdSum(prodBOM.InventTransId));
financial.updateNow();
prodTableJour.AmountFinancial-= financial.updCostAmountInvent();
this.updateCalcBOM(prodBOM,financial);
}
}
Production:
{
..
if (prodTableJour.QtyGood != 0)
{
financial = InventUpd_Financial::newProdTableHistoricalCost(prodTable,_ledgerVoucher,transIdSum,prodTableJour);
financial.updateNow();
..
}
}
我们前面只是说了物理和财务过账的使用,还有预留,领料和登记没有介绍,AX中手动预留,领料和登记在各个模块都用了同样的一个窗体,我们可以看一下它是怎么玩的,领料和登记差不多,是通过类InventTransWMS_Pick和InventTransWMS_Register方法的updateInvent方法实现的,我们看一下那两个窗体对应的过账按钮的代码:
void clicked()
{
if (inventTranspick)
{
InventTransWMS_Pick::updateInvent(inventTranspick, tmpInventTransWMS);
tmpInventTransWMS_DS.executeQuery();
element.doResearch();
}
super();
}
再看一下InventTransWMS_Pick的静态方法updateInvent方法:
{
parmMovement = _inventTransPick.parmMovement();
if (parmMovement.childBuffer())
{
movement = InventMovement::construct(parmMovement.childBuffer());
}
else
movement = InventMovement::construct(parmMovement.buffer(),parmMovement.inventMovSubType());
try
{
ttsbegin;
while select forupdate tmp
order by InventQty
{
inventDim = InventDim::find(tmp.InventDimId);
inventDimParm.initPhysicalUpdate(movement.dimGroupId());
setprefix(inventDim.preFix());
picked = InventUpd_Picked::newParameters(movement,inventDim,inventDimParm,inventDim,inventDimParm,-tmp.InventQty);
picked.parmCompleteQtyUnit(UnitConvert::qty(_inventTransPick.pickQty(),
movement.inventTableInvent().UnitId,
movement.transUnitId(),
movement.itemId()));
picked.updateNow();
}
ttscommit;
}
对于自动预留,会在业务表的Insert或者update方法中调用InventUpd_Estimated的updateReservation方法去实现,我们可以看一下该方法的代码:
{
InventUpd_Reservation reservation;
InventQty reservQty;
if (movement.canBeReserved() && movement.mustBeAutoReserved() )
{
if (! movement.updateReqExplodeReservation())
{
if (movement_Orig)
{
if (allowReserveReversed && (movement_Orig.transDate() != movement.transDate() || preEstimated * estimated < 0))
{
reservation = InventUpd_Reservation::newParameters(movement,inventDimCriteria,inventDimParm,0,-movement.transIdSum().reserved(),true);
reservation.updateNow();
}
}
reservQty = movement.reserveQty() - movement.transIdSum().reserved();
if (reservQty)
{
reservation = InventUpd_Reservation::newMovement(movement,reservQty,true);
reservation.updateNow();
}
}
}
}
手动预留实际上是使用了inventOnhandReserve这个类的方法,而最终是通过类InventUpd_Reservation的静态方法实现的,最终免不了要实例化InventMovement,再实例化InventUpd_Reservation,调用updateNow方法.
OK,差不多就这么多了,本文简略地描述了InventMovement和InventUpdate的设计和系统现有代码对它们的使用,由于水平有限,错误在所难免,还望不吝赐教.