You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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是否存在于生产工单的预分配记录中,若不存在则抛出错误。

具体修复步骤

  1. 恢复关键字段的复制:原代码注释掉了InventoryID和UOM的设置,这些字段是验证批次预分配的必要条件,必须恢复。
  2. 调整字段设置顺序:先设置InventoryID、ProdOrdID等基础字段,再设置批次相关字段,确保系统能正确关联预分配记录。
  3. 预验证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;
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 10:51:43