Acumatica创建Move Transaction时批次预分配错误修复求助
Acumatica物料屏幕自定义按钮创建移动交易报错修复方案
问题背景
在Acumatica的Materials屏幕(AM300000)中添加了自定义按钮“Create Move Record”,用于自动创建Move Transaction(AM302000)。当Lot/Serial Number与Parent Lot/Serial Number相同时代码可正常运行,但二者不同时会触发以下错误:
The transaction cannot be created for Production Order RG PP000504 because the 1H4N7820 lot or serial number has not been preassigned.
现有实现代码
namespace PX.Objects.AM { public class MaterialEntry_Extension : PXGraphExtension<PX.Objects.AM.MaterialEntry> { // New action button to create move record after material release public PXAction<AMBatch> CreateMoveRecord; [PXButton(CommitChanges = true)] [PXUIField(DisplayName = "Create Move Record", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Select)] protected void createMoveRecord() { if (Base.batch.Current == null || Base.batch.Current.Status != "R") // R = Released { throw new PXException("Move records can only be created from released material transactions."); } AMBatch moveBatch = CreateMoveTransactionsFromBatch(Base.batch.Current); if (moveBatch != null) { // Redirect to the Move Entry screen with the new batch var moveGraph = PXGraph.CreateInstance<MoveEntry>(); moveGraph.batch.Current = moveGraph.batch.Search<AMBatch.batNbr>(moveBatch.BatNbr); // Automatically release the move batch //try //{ // moveGraph.release.Press(); // } // catch (Exception ex) // { // throw new PXException($"Move Batch {moveBatch.BatNbr} was created but failed to release: {ex.Message}", ex); // } //throw new PXRedirectRequiredException(moveGraph, "Move Batch " + moveBatch.BatNbr + " Created"); // Create redirect exception with WindowMode.New to open in a new tab var redirectException = new PXRedirectRequiredException(moveGraph, "Move Batch " + moveBatch.BatNbr + " Created"); redirectException.Mode = PXBaseRedirectException.WindowMode.New; throw redirectException; } } [PXMergeAttributes(Method = MergeMethod.Merge)] [PXRestrictor( typeof(Where<SubNotLessThanZero<AMProdItemSplit.qty, Add<AMProdItemSplit.qtyComplete, AMProdItemSplit.qtyScrapped>>, NotEqual<decimal0>>), "Remaining Quantity is 0.")] protected virtual void _(Events.CacheAttached<AMMTranSplit.parentLotSerialNbr> e) { } [PXMergeAttributes(Method = MergeMethod.Merge)] [PXRestrictor( typeof(Where<SubNotLessThanZero<AMProdItemSplit.qty, Add<AMProdItemSplit.qtyComplete, AMProdItemSplit.qtyScrapped>>, NotEqual<decimal0>>), "Remaining Quantity is 0.")] protected virtual void _(Events.CacheAttached<AMMTran.parentLotSerialNbr> e) { } protected virtual void _(Events.RowSelected<AMMTran> e) { if (e.Row == null) return; var row = (AMMTran)e.Row; AMBatch batch = Base.batch.Current; // Make LotSerialNbr required PXUIFieldAttribute.SetRequired<AMMTran.lotSerialNbr>(Base.transactions.Cache, true); PXUIFieldAttribute.SetRequired<AMMTran.parentLotSerialNbr>(Base.transactions.Cache, true); // Enable both fields at the cache level first Base.transactions.Cache.AllowUpdate = true; PXUIFieldAttribute.SetEnabled(Base.transactions.Cache, null, true); // Then enable specific fields PXUIFieldAttribute.SetEnabled<AMMTran.lotSerialNbr>(Base.transactions.Cache, null, true); PXUIFieldAttribute.SetEnabled<AMMTran.parentLotSerialNbr>(Base.transactions.Cache, null, true); // Enable for the specific row PXUIFieldAttribute.SetEnabled<AMMTran.lotSerialNbr>(Base.transactions.Cache, row, true); PXUIFieldAttribute.SetEnabled<AMMTran.parentLotSerialNbr>(Base.transactions.Cache, row, true); if (batch != null && !string.IsNullOrWhiteSpace(row.LotSerialNbr) && !string.IsNullOrWhiteSpace(row.ParentLotSerialNbr)) { Base.batch.Cache.SetValue<AMBatch.hold>(batch, false); Base.batch.Update(batch); } // Show/hide the Create Move Record button based on batch status bool isReleased = batch != null && batch.Status == "R"; CreateMoveRecord.SetEnabled(isReleased); } #endregion // Helper method to create move transactions from a material batch private AMBatch CreateMoveTransactionsFromBatch(AMBatch sourceBatch) { try { PXTrace.WriteInformation($"Starting CreateMoveTransactionsFromBatch for source batch: {sourceBatch.BatNbr}"); var moveGraph = PXGraph.CreateInstance<MoveEntry>(); // Create new move batch var moveBatch = moveGraph.batch.Insert(new AMBatch { TranDate = sourceBatch.TranDate, FinPeriodID = sourceBatch.FinPeriodID, Hold = false }); if (moveBatch == null) { PXTrace.WriteError("Failed to insert move batch - batch is null"); throw new PXException("Failed to create move batch."); } PXTrace.WriteInformation($"Created move batch: {moveBatch.BatNbr}"); moveGraph.batch.Current = moveBatch; // Get source transactions var materialTrans = PXSelect<AMMTran, Where<AMMTran.batNbr, Equal<Required<AMMTran.batNbr>>, And<AMMTran.docType, Equal<Required<AMMTran.docType>>>> .Select(moveGraph, sourceBatch.BatNbr, sourceBatch.DocType); PXTrace.WriteInformation($"Found {materialTrans.Count} source transactions to copy"); int transactionCount = 0; foreach (AMMTran mtran in materialTrans) { transactionCount++; PXTrace.WriteInformation($"Processing transaction #{transactionCount}: LineNbr={mtran.LineNbr}, InventoryID={mtran.InventoryID}"); var parentLotSerial = string.IsNullOrEmpty(mtran.ParentLotSerialNbr) ? mtran.LotSerialNbr : mtran.ParentLotSerialNbr; // Create move transaction var moveTran = new AMMTran { OrderType = mtran.OrderType, ProdOrdID = mtran.ProdOrdID, OperationID = mtran.OperationID, SiteID = mtran.SiteID, LocationID = mtran.LocationID, //add UOM and inventory //InventoryID = mtran.InventoryID, //UOM = mtran.UOM }; var insertedTran = moveGraph.transactions.Insert(moveTran); if (insertedTran != null) { //moveGraph.transactions.SetValueExt<AMMTran.prodOrdID>(insertedTran, mtran.ProdOrdID); moveGraph.transactions.SetValueExt<AMMTran.lotSerialNbr>(insertedTran, mtran.LotSerialNbr); moveGraph.transactions.SetValueExt<AMMTran.parentLotSerialNbr>(insertedTran, parentLotSerial); moveGraph.transactions.SetValueExt<AMMTran.qty>(insertedTran, 1m); //moveGraph.transactions.SetValueExt<AMMTran.inventoryID>(insertedTran, mtran.InventoryID); //moveGraph.transactions.SetValueExt<AMMTran.uOM>(insertedTran, mtran.UOM); moveGraph.transactions.Update(insertedTran); } else { PXTrace.WriteError($"Failed to insert move transaction for line {mtran.LineNbr}"); } } // Save all changes moveGraph.Actions.PressSave(); PXTrace.WriteInformation($"Saved {transactionCount} move transactions for batch {moveBatch.BatNbr}"); // Store the batch number for release // string batchNbr = moveBatch.BatNbr; // SIMPLEST RELEASE APPROACH: // Create a fresh instance of MoveEntry and release by batch number // var releaseGraph = PXGraph.CreateInstance<MoveEntry>(); // releaseGraph.batch.Current = releaseGraph.batch.Search<AMBatch.batNbr>(batchNbr); // if (releaseGraph.batch.Current != null) // { // PXTrace.WriteInformation($"Releasing batch {batchNbr}"); // releaseGraph.release.Press(); //} return moveBatch; } catch (Exception ex) { PXTrace.WriteError($"Error in CreateMoveTransactionsFromBatch: {ex}"); throw new PXException($"Error creating move transactions: {ex.Message}"); } } } }
详细错误日志
Error in CreateMoveTransactionsFromBatch: System.Exception: The transaction cannot be created for Production Order RG PP000504 because the 1H4N7820 lot or serial number has not been preassigned. at PX.Objects.AM.MoveEntryBase`1.ValidateLotSerialInformation() in C:\build\code_repo\WebSites\Pure\PX.Objects.AM\AM\MoveEntryBase.cs:line 174 at PX.Objects.AM.MoveEntryBase`1.Persist() in C:\build\code_repo\WebSites\Pure\PX.Objects.AM\AM\MoveEntryBase.cs:line 119 at PX.Data.PXSave`1.<HandlerInternal>g__Persist|3_2() in C:\build\code_repo\NetTools\PX.Data\Descriptor\Action\CommonActions.cs:line 98 at PX.Data.PXSave`1.<HandlerInternal>d__3.MoveNext() in C:\build\code_repo\NetTools\PX.Data\Descriptor\Action\CommonActions.cs:line 81 at PX.Data.PXAction`1.<Press>d__38.MoveNext() in C:\build\code_repo\NetTools\PX.Data\Descriptor\Action\PXAction.cs:line 1301 at PX.Data.PXAction`1.<Press>d__38.MoveNext() in C:\build\code_repo\NetTools\PX.Data\Descriptor\Action\PXAction.cs:line 1235 at PX.Data.PXActionCollection.PressSave(PXAction caller) in C:\build\code_repo\NetTools\PX.Data\Graph\Collection.cs:line 1456 at PX.Objects.AM.MaterialEntry_Extension.CreateMoveTransactionsFromBatch(AMBatch sourceBatch)
修复方案
错误原因
Acumatica的Move Entry在保存时会执行ValidateLotSerialInformation()验证,确保使用的批次/序列号已预分配到目标生产工单。当Parent Lot与Lot不同时,系统会检查Parent Lot是否存在于生产工单的预分配记录中,若不存在则抛出错误。
具体修复步骤
- 恢复关键字段的复制:原代码注释掉了
InventoryID和UOM的设置,这些字段是验证批次预分配的必要条件,必须恢复。 - 调整字段设置顺序:先设置
InventoryID、ProdOrdID等基础字段,再设置批次相关字段,确保系统能正确关联预分配记录。 - 预验证Parent Lot的预分配状态:在创建交易前,检查Parent Lot是否已预分配到生产工单,若未预分配可自动创建预分配记录(需确保业务逻辑允许)。
修改后的核心代码片段
重点修改CreateMoveTransactionsFromBatch方法中的交易创建部分:
// Helper method to create move transactions from a material batch private AMBatch CreateMoveTransactionsFromBatch(AMBatch sourceBatch) { try { PXTrace.WriteInformation($"Starting CreateMoveTransactionsFromBatch for source batch: {sourceBatch.BatNbr}"); var moveGraph = PXGraph.CreateInstance<MoveEntry>(); // Create new move batch var moveBatch = moveGraph.batch.Insert(new AMBatch { TranDate = sourceBatch.TranDate, FinPeriodID = sourceBatch.FinPeriodID, Hold = false }); if (moveBatch == null) { PXTrace.WriteError("Failed to insert move batch - batch is null"); throw new PXException("Failed to create move batch."); } PXTrace.WriteInformation($"Created move batch: {moveBatch.BatNbr}"); moveGraph.batch.Current = moveBatch; // Get source transactions var materialTrans = PXSelect<AMMTran, Where<AMMTran.batNbr, Equal<Required<AMMTran.batNbr>>, And<AMMTran.docType, Equal<Required<AMMTran.docType>>>> .Select(moveGraph, sourceBatch.BatNbr, sourceBatch.DocType); PXTrace.WriteInformation($"Found {materialTrans.Count} source transactions to copy"); int transactionCount = 0; foreach (AMMTran mtran in materialTrans) { transactionCount++; PXTrace.WriteInformation($"Processing transaction #{transactionCount}: LineNbr={mtran.LineNbr}, InventoryID={mtran.InventoryID}"); var parentLotSerial = string.IsNullOrEmpty(mtran.ParentLotSerialNbr) ? mtran.LotSerialNbr : mtran.ParentLotSerialNbr; // 1. 预验证Parent Lot是否已预分配到生产工单 var preAssign = PXSelect<AMProdItemLotSerial, Where<AMProdItemLotSerial.orderType, Equal<Required<AMProdItemLotSerial.orderType>>, And<AMProdItemLotSerial.prodOrdID, Equal<Required<AMProdItemLotSerial.prodOrdID>>, And<AMProdItemLotSerial.inventoryID, Equal<Required<AMProdItemLotSerial.inventoryID>>, And<AMProdItemLotSerial.lotSerialNbr, Equal<Required<AMProdItemLotSerial.lotSerialNbr>>>>>> .Select(moveGraph, mtran.OrderType, mtran.ProdOrdID, mtran.InventoryID, parentLotSerial); if (preAssign == null && !string.IsNullOrEmpty(parentLotSerial)) { // 若未预分配,创建预分配记录(需根据业务场景确认是否允许自动创建) var prodItemLotSerial = new AMProdItemLotSerial { OrderType = mtran.OrderType, ProdOrdID = mtran.ProdOrdID, InventoryID = mtran.InventoryID, LotSerialNbr = parentLotSerial, Qty = mtran.Qty, UOM = mtran.UOM }; PXGraph.CreateInstance<ProductionEntry>().prodItemLotSerial.Insert(prodItemLotSerial); PXTrace.WriteInformation($"Preassigned parent lot {parentLotSerial} to production order {mtran.ProdOrdID}"); } // 2. 创建move transaction,包含所有必要字段 var moveTran = new AMMTran { OrderType = mtran.OrderType, ProdOrdID = mtran.ProdOrdID, OperationID = mtran.OperationID, SiteID = mtran.SiteID, LocationID = mtran.LocationID, InventoryID = mtran.InventoryID, UOM = mtran.UOM }; var insertedTran = moveGraph.transactions.Insert(moveTran); if (insertedTran != null) { // 3. 按正确顺序设置字段:先基础信息,再批次信息 moveGraph.transactions.SetValueExt<AMMTran.qty>(insertedTran, 1m); moveGraph.transactions.SetValueExt<AMMTran.lotSerialNbr>(insertedTran, mtran.LotSerialNbr); moveGraph.transactions.SetValueExt<AMMTran.parentLotSerialNbr>(insertedTran, parentLotSerial); moveGraph.transactions.Update(insertedTran); } else { PXTrace.WriteError($"Failed to insert move transaction for line {mtran.LineNbr}"); } } // Save all changes moveGraph.Actions.PressSave(); PXTrace.WriteInformation($"Saved {transactionCount} move transactions for batch {moveBatch.BatNbr}"); return moveBatch;
相关产品推荐
相关产品推荐

