Acumatica生产订单维护:父订单生产数量同步至子订单
需求说明
- 在Acumatica生产订单维护界面,为生产数量为10.00的父生产订单点击「Generate Orders for Subassemblies(生成子装配件订单)」按钮时,需将生成的子生产订单的**Qty. to Produce(生产数量)**同步设置为10.00,当前系统默认设置为1.00。
已尝试的实现方案及问题
方案一:通过RowInserted/RowUpdated事件更新子订单
通过监听AMProdItem的RowInserted和RowUpdated事件,在子订单创建后更新其生产数量:
namespace PX.Objects.AM { public class ProdMaint_Extension : PXGraphExtension<ProdMaint.ItemLineSplittingExtension,PX.Objects.AM.ProdMaint> { //ADD ROWINSERTED LOGIC protected virtual void _(Events.RowInserted<AMProdItem> e) { UpdateChildOrder(e.Cache, e.Row); } protected virtual void _(Events.RowUpdated<AMProdItem> e) { UpdateChildOrder(e.Cache, e.Row); } private void UpdateChildOrder(PXCache cache, AMProdItem row) { if (row == null || cache == null) return; // Ensure this is a child order with a valid parent if (string.IsNullOrEmpty(row.ParentOrdID)) return; // Query the parent order using PK.Find var parentOrder = AMProdItem.PK.Find(Base, row.ParentOrderType, row.ParentOrdID); if (parentOrder == null) return; // Update the child order fields using parent order values cache.SetValueExt<AMProdItem.qtytoProd>(row, parentOrder.QtytoProd); if (!string.IsNullOrEmpty(parentOrder.BOMRevisionID)) { cache.SetValueExt<AMProdItem.bOMRevisionID>(row, parentOrder.BOMRevisionID); } PXTrace.WriteInformation($"Child order updated: {row.ProdOrdID}, Parent: {parentOrder.ProdOrdID}, QtyToProd: {row.QtytoProd}"); } //END OF ROW INSERTED LOGIC } }
问题:不确定该逻辑是否正确覆盖子订单生成的场景,可能存在子订单创建时事件未触发或执行顺序问题。
方案二:重写CreateLinkedOrders方法
通过PXOverride重写CreateLinkedOrders方法,在子订单生成后批量更新生产数量:
namespace PX.Objects.AM { public class ProdMaint_Extension : PXGraphExtension<ProdMaint.ItemLineSplittingExtension,PX.Objects.AM.ProdMaint> { public delegate IEnumerable CreateLinkedOrdersDelegate(PXAdapter adapter); [PXOverride] public IEnumerable CreateLinkedOrders(PXAdapter adapter, CreateLinkedOrdersDelegate baseMethod) { var result = baseMethod(adapter); // Call the original logic to create child orders // Ensure the current production order is loaded if (Base.ProdMaintRecords.Current == null) return result; // Get the parent production order's QtyToProduce decimal? parentQtyToProduce = Base.ProdMaintRecords.Current.QtytoProd; string parentBOMRevisionID = Base.ProdMaintRecords.Current.BOMRevisionID; // Start with the first child orders string currentParentID = Base.ProdMaintRecords.Current.ProdOrdID; // Continue finding and updating children until no more are found while (!string.IsNullOrEmpty(currentParentID)) { // Find child order for current parent var childOrder = (AMProdItem)PXSelect<AMProdItem, Where<AMProdItem.parentOrdID, Equal<Required<AMProdItem.parentOrdID>>>> .Select(Base, currentParentID) .FirstOrDefault(); if (childOrder != null && parentQtyToProduce.HasValue) { // Update quantity for this child childOrder.QtytoProd = parentQtyToProduce; if (!string.IsNullOrEmpty(parentBOMRevisionID)) childOrder.BOMRevisionID = parentBOMRevisionID; Base.ProdMaintRecords.Update(childOrder); // Move to next parent in the chain currentParentID = childOrder.ProdOrdID; } else { // No more children found, exit the loop break; } } Base.Actions.PressSave(); // Save all changes to the database return result; } } }
问题:触发NullReferenceException异常,错误栈如下:
Exception Type: System.NullReferenceException Message: Object reference not set to an instance of an object. Stack Trace: at PX.Data.PXFirstChanceExceptionLogger.ProfilerFirstChanceException(Object o, FirstChanceExceptionEventArgs args) at PX.Objects.AM.GLAccountHelper.GetAcctID[Field](PXGraph graph, String AcctDefault, InventoryItem inventoryItem, INSite site, INPostClass postClass, AMOrderType amOrderType) at PX.Objects.AM.GLAccountHelper.GetAccountDefaults[Field](PXGraph graph, InventoryItem inventoryItem, INSite site, INPostClass postclass, AMOrderType amOrderType) at PX.Objects.AM.ProdMaint.SetAccountDefaults(PXCache cache) at PX.Objects.AM.ProdMaint.AMProdItem_InventoryID_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e) at PX.Data.PXFieldUpdated.Invoke(PXCache sender, PXFieldUpdatedEventArgs args) at PX.Objects.AM.ProdMaint_Extension.AMProdItem_InventoryID_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated baseHandler) at PX.Data.PXCache.OnFieldUpdated(String name, Object row, Object oldValue, Boolean externalCall) at PX.Data.PXCache`1.FillWithValues(TNode item, TNode copy, TNode newitem) at PX.Data.PXCache`1.Update(Object data, Boolean bypassinterceptor) at PX.Data.PXSelectBase`1.Update(Table item) at PX.Objects.AM.CriticalMaterialsInq.CreateNewProdItem(AMProdItem parentAMProdItem, Decimal qtyToProduce, AMProdMatl prodMatl, CreateProdFilter filter, Numbering numbering, ProdMaint prodMaintGraph) at PX.Objects.AM.CriticalMaterialsInq.CreateProductionOrder(AMProdItem prodCreatedFrom, Decimal qtyToProduce, AMProdMatl prodMatl, CreateProdFilter filter, Numbering numbering, ProdMaint prodMaintGraph, Int32 level, List`1 ordersList, Nullable`1 splitPlanID) at PX.Objects.AM.CriticalMaterialsInq.CreateLinkedProductionOrders(AMProdItem prodCreatedFrom, CreateProdFilter filter, ProdMaint prodMaintGraph, Numbering numbering, Int32 level, List`1 ordersList) at PX.Objects.AM.CriticalMaterialsInq.CreateLinkedProductionOrders(AMProdItem prodCreatedFrom, List`1 ordersList) at PX.Objects.AM.ProdMaint.<>c__DisplayClass107_0.<CreateLinkedOrders>b__0() at PX.Concurrency.CancellationIgnorantExtensions.RunWithCancellationViaThreadAbort(Action method, CancellationToken cancellationToken) at PX.Concurrency.CancellationIgnorantExtensions.<>c__DisplayClass1_0.<ToCancellationViaThreadAbort>b__0(CancellationToken cancellationToken) at PX.Concurrency.Internal.PXLongOperationPars.PopAndRunDelegate(CancellationToken cancellationToken) at PX.Concurrency.Internal.RuntimeLongOperationManager.PerformOperation(PXLongOperationPars p) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at PX.Concurrency.Internal.PXThreadPool.RunItem(RequestItem item) at PX.Concurrency.Internal.PXThreadPool.Run() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()
正确实现方案
最佳方式是在子订单创建的源头修改生产数量,避免事后更新触发的字段事件异常。通过重写CriticalMaterialsInq中的CreateNewProdItem方法,直接传递父订单的生产数量:
namespace PX.Objects.AM { public class CriticalMaterialsInq_Extension : PXGraphExtension<CriticalMaterialsInq> { public delegate AMProdItem CreateNewProdItemDelegate(AMProdItem parentAMProdItem, decimal qtyToProduce, AMProdMatl prodMatl, CreateProdFilter filter, Numbering numbering, ProdMaint prodMaintGraph); [PXOverride] public AMProdItem CreateNewProdItem(AMProdItem parentAMProdItem, decimal qtyToProduce, AMProdMatl prodMatl, CreateProdFilter filter, Numbering numbering, ProdMaint prodMaintGraph, CreateNewProdItemDelegate baseMethod) { // 替换默认的1.00为父订单的生产数量 qtyToProduce = parentAMProdItem.QtytoProd ?? qtyToProduce; // 调用原方法创建子订单 var childOrder = baseMethod(parentAMProdItem, qtyToProduce, prodMatl, filter, numbering, prodMaintGraph); // 同步父订单的BOM版本号(如果需要) if (!string.IsNullOrEmpty(parentAMProdItem.BOMRevisionID)) { prodMaintGraph.ProdMaintRecords.Cache.SetValueExt<AMProdItem.bOMRevisionID>(childOrder, parentAMProdItem.BOMRevisionID); } return childOrder; } } }
说明:
- 该方案直接在子订单创建时使用父订单的生产数量,无需事后更新,避免了缓存状态不一致和字段事件异常。
- 同步BOM版本号的逻辑可选,根据实际需求保留。
异常原因分析(方案二)
- 字段更新事件触发空引用:调用
Base.ProdMaintRecords.Update(childOrder)时,子订单的关联数据(如INSite、AMOrderType的GL账户配置)未完全加载,导致AMProdItem_InventoryID_FieldUpdated事件触发时,GLAccountHelper.GetAcctID方法中出现空对象引用。 - 循环逻辑缺陷:代码中每次仅查询第一个子订单,无法处理一个父订单对应多个子订单的场景,且递归更新的逻辑不符合子订单层级关系。
- 手动保存冲突:原
CreateLinkedOrders方法内部已处理保存操作,额外调用Base.Actions.PressSave()会导致重复保存,引发缓存状态冲突。
内容的提问来源于stack exchange,提问作者Tharidhi P
相关产品推荐
相关产品推荐

