向自定义Acumatica ERP模块添加标准批准

时间:2018-09-06 19:22:42

标签: acumatica

我有一个Acumatica ERP的自定义模块,需要获得批准。我想利用Acumatica ERP中的标准批准机制,因此我遵循了本指南...

How to work with Assignment and Approval Maps in Acumatica via Automation Steps? - Answers from Gabriel and Brendan

我认为一切都准备就绪,但是当我取消记录时什么都不会发生,除了我写的事件将按预期将状态切换为“待批准”。 (也许我应该让审批流程来处理那部分?)我希望自动化步骤应该很简单,因为我的状态始终为:

保留->待批准->批准-或-拒绝

我没有收到任何错误,所以不确定不确定自动化步骤定义是否正确,还是应该将特定的内容编码到Brendan在其答案的第3步中说要构建的XXApprovalAutomation类中以上帖子。

我对Acumatica还是很陌生,因此很多内部工作仍然是个谜,这要求我不断地研究CodeRepository。我不确定我的自定义XXApprovalAutomation是否应该在GetAssignedMaps覆盖中具有特定的内容,但除非有人在这里指导,否则我将继续挖掘和尝试尝试。

有人可以解释批准系统如何工作的基本流程,以及我需要的,未在我所注明的帖子中显示的任何相关示例代码(例如GetAssignedMaps的内容可以打到我定义的批准图上)吗?甚至更好,将我指向可以在其中阅读更多内容的地方?

2 个答案:

答案 0 :(得分:1)

出现自定义批准有两种方法,但这将遵循“自动化步骤”。在继续之前,您应该熟悉以下内容:

自动化定义-Acumatica ERP 2018r1的这一区域使您可以在一处查看系统给定区域的所有自动化步骤。例如,选择“采购订单默认定义ID”,然后选择“采购订单”屏幕,可以通过单击“显示填充的”按钮来查看以XML格式定义的所有自动化步骤。该工具对我来说至关重要,因为它指出了我的自动化步骤中遗漏的重要步骤。该系统区域还允许您导出迁移所需的自动化步骤,并将其与为在下一个系统上部署而发送的代码捆绑在一起(例如,TST-> QA-> PRD)。

自动化步骤-系统的这一区域使您可以定义“发生这种情况时,执行此操作”类型的操作。例如,我的自动化步骤之一是,当取消选中保留按钮并且表单当前处于保留状态时,将状态切换为“待批准”。请务必探索并了解“操作”标签,并使用“用值填充”(用于设置字段值),以及在网格中使用“操作名称”来定义“操作”菜单上的按钮,方法是选择“操作”,然后命名菜单文本下的按钮。这是我定义“批准”和“拒绝”按钮的地方。

批准和分配图-正如HB_Acumatica指出的那样,该区域有说明如何使用它的文档。阅读相关内容,并尝试使用PO进行类似操作,以确保在尝试进行自己的定制之前,您了解使用批准的应用程序方面。

尽管我仍在努力掌握使其工作所需的扎实经验,但以下因素似乎是关键因素:

在图形中要批准的位置定义它们。就像Brendan在他的帖子中所建议的那样,自定义EPApprovalAutomation似乎最终没有必要,至少对我而言。可能是这样做的原因,但我从未发现要自定义的内容或原因。

[PXViewName("My DAC Name")]
public PXSelect<XXRegister, Where<XXRegister.branchID, Equal<Current<AccessInfo.branchID>>>> MyView;

public PXSetup<XXSetup> Setup;

public PXSelect<XXSetupApproval> SetupApproval;

[PXViewName(Messages.Approval)]
public EPApprovalAutomation<XXRegister, XXRegister.approved, XXRegister.rejected, XXRegister.hold, XXSetupApproval> Approval;

MyView可以是您想调用的任何视图。它将包含您的自定义数据。设置是首选项DAC的视图,您可以在其中通知系统为自定义数据启用批准。 SetupApproval是启用批准时在自定义首选项屏幕中定义的批准图的视图。 (这会将批准地图与通知相关联,以便定义的人可以获取指定的批准通知。)批准就是魔术……这是一些超级秘密的特殊调味料,显然不适合普通大众理解。但是,传入的参数定义(1)哪个DAC描述您要批准的数据记录;(2)DAC中的哪个字段与“已批准”,“已拒绝”和“保持”相关;以及(3)此魔术框应在哪里查找批准图和通知-即XXSetupApproval DAC的名称。

未经证实的事实...就是我所观察到的... 名为EPApprovalAutomation的超级机密特殊魔术框似乎在查看“保留”字段的状态,如果未选中,则使用XXSetupApproval数据导航“批准图”。当选择批准和拒绝操作时,该魔术框似乎会将适当的值应用于批准和拒绝的字段。自动化步骤监视字段的更改(无论您如何定义)并应用自动化,例如将状态字段设置为“已批准”或“已拒绝”。 EPApprovalAutomation似乎要做的最后一件事是将“批准人”用户,日期和状态应用于应该显示在自定义屏幕的“批准细节”选项卡中的EPApproval记录。

现在,从推测开始,回到我所做的事情,以进一步发展……

在需要自定义批准的数据的DAC中,请确保按以下方式定义“状态”,“保留”,“已批准”和“拒绝”。注意它何时在数据库中和何时不在数据库中。

    #region Hold
    [PXDBBool()]
    [PXUIField(DisplayName = "Hold", Visibility = PXUIVisibility.Visible)]
    [PXDefault(true)]
    //[PXNoUpdate] <- Saw this in the PO code, but had to remove so user could save stat of the Hold checkbox
    public virtual bool? Hold { get; set; }
    public abstract class hold : IBqlField { }
    #endregion

    #region Approved
    [PXDBBool()]
    [PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
    [PXUIField(DisplayName = "Approved", Visibility = PXUIVisibility.Visible, Enabled = false)]
    public virtual bool? Approved { get; set; }
    public abstract class approved : IBqlField { }
    #endregion

    #region Rejected
    [PXBool]
    [PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
    public abstract class rejected : IBqlField { }
    #endregion

    #region Status
    [PXDBString(1)]
    [PXDefault(XXRegister.Statuses.Hold)]
    [PXUIField(DisplayName = "Status", Visibility = PXUIVisibility.SelectorVisible, Enabled = false)]
    [Statuses.List]
    public virtual string Status { get; set; }
    public abstract class status : IBqlField { }
    #endregion

请注意,已拒绝是PXBool,而不是PXDBBool-因此,请勿将其添加到数据库的表中。

在图中,添加事件处理程序。我希望使用此选项来设置当前记录的分支,因为我希望最终用户仅查看其分支中的记录。

    #region XXRegister_RowInserting
    protected void XXRegister_RowInserting(PXCache sender, PXRowInsertingEventArgs e)
    {
        XXRegister row = (XXRegister)e.Row;
        row.BranchID = PXAccess.GetBranchID();
    }
    #endregion

尽管Acumatica ERP在“自动化步骤”中已对此进行了处理,但我还是用它来以编程方式切换状态和各个字段-在我当前的知识水平上,这样做更容易。

    #region XXRegister _RowUpdating
    protected void XXRegister _RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
    {
        XXRegister row = (XXRegister )e.Row;
        XXRegister newRow = (XXRegister )e.NewRow;

        if (row.Hold != newRow.Hold)
        {
            if (newRow.Hold.Equals(true))
            {
                newRow.Status = XXRegister.Statuses.Hold;
                newRow.Approved = false;
            }
            else if (row.Status.Equals(XXRegister.Statuses.Hold))
            {
                newRow.Status = XXRegister.Statuses.PendingApproval;
            }
        }
    }
    #endregion

剩下的就是试图获得自动化步骤来模仿PO或SO。我还没有完成,但这使我摆脱了我一直在问的障碍。完全公开-当我拒绝批准时,我的批准详细信息不会更新为显示谁/何时/状态,但是我的数据记录已正确更新。但这不是我在这里问的真正问题,因此这应该涵盖了原来的预期问题。

答案 1 :(得分:0)

最后使用标准Acumatica代码和自动化步骤完成了整个过程。这对于Acumatica来说是新手,这让我特别痛苦,在学习系统和工具的其他部分时,我不得不从中休息很多。对于那些想要避免我忍受无数小时的反复试验的人,以下是您在2018R1中进行自定义的相关代码。 (我了解R2可能需要重写某些内容,因此如果您无法使它正常工作,请注意您的版本。)

在您潜水之前,请注意,我离开了原始答案,因为它是我学习曲线的一部分,可以帮助您从我的起点(可能是您所在的地方)联系到最终的工作方向希望的。

MyGraph:

using PX.Data;
using PX.Objects.AP;
using PX.Objects.AR;
using PX.Objects.CR;
using PX.Objects.EP;
using PX.Objects.IN;
using System.Collections;
using System.Collections.Generic;

namespace MyNamespace
{

    public class MyGraph : PXGraph<MyGraph, XXDocument>
    {

        [PXViewName(Messages.MyGraph)]
        public PXSelect<XXDocument, Where<XXDocument.branchID, Equal<Current<AccessInfo.branchID>>>> MyView;

        public PXSetup<XXSetup> MySetup;

        public PXSelect<XXSetupApproval> SetupApproval;

        // THIS WILL USE THE STANDARD APPROVAL CODE AND SUPPORT THE STANDARD APPROVAL SCREEN
        [PXViewName(Messages.Approval)]
        public EPApprovalAutomation<XXDocument, XXDocument.approved, XXDocument.rejected, XXDocument.hold, XXSetupApproval> Approval;


        // RESET REQUESTAPPROVAL FIELD FROM THE SETUP SCREEN SETTING
        protected virtual void XXDocument_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
        {
            XXDocument doc = e.Row as XXDocument;

            if (doc == null)
            {
                return;
            }
            doc.RequestApproval = MySetup.Current.XXRequestApproval;
        }


        public MyGraph()
        {
            XXSetup setup = MySetup.Current;
        }

        // SETS UP THE ACTIONS MENU INCLUDING @actionID = Persist and @refresh FOR AUTOMATION STEPS
        public PXAction<XXDocument> action;
        [PXUIField(DisplayName = "Actions", MapEnableRights = PXCacheRights.Select)]
        [PXButton]
        protected virtual IEnumerable Action(PXAdapter adapter,
            [PXInt] [PXIntList(new int[] { 1, 2 }, new string[] { "Persist", "Update" })] int? actionID,
            [PXBool] bool refresh,
            [PXString] string actionName
        )
        {
            List<XXDocument> result = new List<XXDocument>();
            if (actionName != null)
            {
                PXAction a = this.Actions[actionName];
                if (a != null)
                    foreach (PXResult<XXDocument> e in a.Press(adapter))
                        result.Add(e);
            }
            else
                foreach (XXDocument e in adapter.Get<XXDocument>())
                    result.Add(e);

            if (refresh)
            {
                foreach (XXDocument MyView in result)
                    MyView.Search<XXDocument.refNbr>(MyView.RefNbr);
            }
            switch (actionID)
            {
                case 1:
                    Save.Press();
                    break;
                case 2:
                    break;
            }
            return result;
        }

        public PXAction<XXDocument> hold;

        // QUICK DEFAULT BASED ON WETHER APPROVAL SETUPS ARE DEFINED PROPERLY
        protected virtual void XXDocument_Approved_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
        {
            e.NewValue = MySetup.Current == null || MySetup.Current.XXRequestApproval != true;
        }

        // THESE (EPApproval_XX_CacheAttached) WILL SET VALUES IN THE GRID FOR THE STANDARD APPROVAL PROCESSING SCREEN
        #region EPApproval Cache Attached
        [PXDBDate()]
        [PXDefault(typeof(XXDocument.docDate), PersistingCheck = PXPersistingCheck.Nothing)]
        protected virtual void EPApproval_DocDate_CacheAttached(PXCache sender)
        {
        }

        [PXDBInt()]
        [PXDefault(typeof(XXDocument.bAccountID), PersistingCheck = PXPersistingCheck.Nothing)]
        protected virtual void EPApproval_BAccountID_CacheAttached(PXCache sender)
        {
        }

        [PXDBString(60, IsUnicode = true)]
        [PXDefault(typeof(XXDocument.description), PersistingCheck = PXPersistingCheck.Nothing)]
        protected virtual void EPApproval_Descr_CacheAttached(PXCache sender)
        {
        }

        [PXDBLong()]
        [CurrencyInfo(typeof(XXDocument.curyInfoID))]
        protected virtual void EPApproval_CuryInfoID_CacheAttached(PXCache sender)
        {
        }

        [PXDBDecimal(4)]
        [PXDefault(typeof(XXDocument.curyTotalAmount), PersistingCheck = PXPersistingCheck.Nothing)]
        protected virtual void EPApproval_CuryTotalAmount_CacheAttached(PXCache sender)
        {
        }

        [PXDBDecimal(4)]
        [PXDefault(typeof(XXDocument.totalAmount), PersistingCheck = PXPersistingCheck.Nothing)]
        protected virtual void EPApproval_TotalAmount_CacheAttached(PXCache sender)
        {
        }
        #endregion

    }
}

我的文档DAC:

using PX.Data;
using PX.Data.EP;
using PX.Objects.CS;
using PX.Objects.EP;
using PX.Objects.SM;
using PX.SM;
using PX.TM;
using System;

namespace MyNamespace
{
    [PXEMailSource]

    [Serializable]
    [PXPrimaryGraph(typeof(MyGraph))]
    [PXCacheName(Messages.XXDocument)]
    public partial class XXDocument : IBqlTable, IAssign
    {
        #region Selected
        [PXBool()]
        [PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
        [PXUIField(DisplayName = "Selected")]
        public virtual bool? Selected { get; set; }
        public abstract class selected : IBqlField { }
        #endregion

        #region BranchID
        [PXDBInt()]
        [PXUIField(DisplayName = "Branch ID")]
        public virtual int? BranchID { get; set; }
        public abstract class branchID : IBqlField { }
        #endregion

        #region DocumentID
        [PXDBIdentity]
        public virtual int? DocumentID { get; set; }
        public abstract class documentID : IBqlField { }
        #endregion

        #region RefNbr
        [PXDBString(15, IsKey = true, IsUnicode = true, InputMask = "")]
        [PXUIField(DisplayName = "Ref Nbr", Visibility = PXUIVisibility.SelectorVisible)]
        [AutoNumber(typeof(XXSetup.numberingID), typeof(AccessInfo.businessDate))]
        [PXSelector(typeof(XXDocument.refNbr),
           typeof(XXDocument.refNbr),
           typeof(XXDocument.createdDateTime)
           )]
        public virtual string RefNbr { get; set; }
        public abstract class refNbr : IBqlField { }
        #endregion

        #region Hold
        [PXDBBool()]
        [PXUIField(DisplayName = "Hold", Visibility = PXUIVisibility.Visible)]
        [PXDefault(true)]
        public virtual bool? Hold { get; set; }
        public abstract class hold : IBqlField { }
        #endregion

        #region Approved
        // MAKE THIS PXDBBOOL IF YOU WANT TO SAVE THIS IN THE DATABASE LIKE POORDER.APPROVED FIELD
        // NOT NECESSARY IN MY CODE BUT CAN AFFECT HOW YOU DEFINE AUTOMATION STEPS
        [PXBool()]
        [PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
        // REMEMBER PXUIFIELD IF YOU WANT TO DISPPLAY ON THE SCREEN - I DID NOT WANT THIS ON MY SCREEN
        //[PXUIField(DisplayName = "Approved", Visibility = PXUIVisibility.Visible, Enabled = false)]
        public virtual Boolean? Approved { get; set; }
        public abstract class approved : IBqlField { }
        #endregion

        #region Rejected
        [PXBool]
        [PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
        public bool? Rejected { get; set; }
        public abstract class rejected : IBqlField { }
        #endregion

        #region RequestApproval
        [PXBool()]
        [PXUIField(DisplayName = "Request Approval", Visible = false)]
        public virtual bool? RequestApproval { get; set; }
        public abstract class requestApproval : IBqlField { }
        #endregion

        #region Status
        [PXDBString(1)]
        [PXDefault(XXDocument.Statuses.Hold)]
        [PXUIField(DisplayName = "Status", Visibility = PXUIVisibility.SelectorVisible, Enabled = false)]
        [Statuses.List]
        public virtual string Status { get; set; }
        public abstract class status : IBqlField { }
        #endregion

        #region Description
        [PXDBString(255, IsUnicode = true, InputMask = "")]
        [PXUIField(DisplayName = "Description")]
        public virtual string Description { get; set; }
        public abstract class description : IBqlField { }
        #endregion

        // ADD A VERSION OF AMOUNT FOR CURRENCY AND ALSO A FIELD FOR CURRENCY ID IF YOU WANT IN YOUR APPROVAL SCREEN
        #region Amount
        [PXDBDecimal(2)]
        [PXDefault(TypeCode.Decimal, "0.0")]
        [PXUIField(DisplayName = "Amount", Enabled = false)]
        public virtual decimal? Amount { get; set; }
        public abstract class amount : IBqlField { }
        #endregion

        #region DocDate
        [PXDBDate()]
        [PXUIField(DisplayName = "Date")]
        [PXDefault(typeof(AccessInfo.businessDate))]
        public virtual DateTime? DocDate { get; set; }
        public abstract class docDate : IBqlField { }
        #endregion

        #region BAccountID
        /// <summary>
        /// The ID of the workgroup which was assigned to approve the transaction.
        /// </summary>
        [PXInt]
        [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
        public virtual int? BAccountID { get; set; }
        public abstract class bAccountID : IBqlField { }
        #endregion

        #region OwnerID
        [PXDBGuid()]
        [PXDefault(typeof(Search<EPEmployee.userID, Where<EPEmployee.userID, Equal<Current<AccessInfo.userID>>>>), PersistingCheck = PXPersistingCheck.Nothing)]
        [PX.TM.PXOwnerSelector()]
        [PXUIField(DisplayName = "Owner")]
        public virtual Guid? OwnerID { get; set; }
        public abstract class ownerID : IBqlField { }
        #endregion

        #region WorkgroupID
        /// <summary>
        /// The ID of the workgroup which was assigned to approve the transaction.
        /// </summary>
        [PXInt]
        [PXSelector(typeof(Search<EPCompanyTree.workGroupID>), SubstituteKey = typeof(EPCompanyTree.description))]
        [PXUIField(DisplayName = "Approval Workgroup ID", Enabled = false)]
        public virtual int? WorkgroupID { get; set; }
        public abstract class workgroupID : IBqlField { }
        #endregion


        #region CreatedByID
        [PXDBCreatedByID()]
        public virtual Guid? CreatedByID { get; set; }
        public abstract class createdByID : IBqlField { }
        #endregion

        #region CreatedByScreenID
        [PXDBCreatedByScreenID()]
        public virtual string CreatedByScreenID { get; set; }
        public abstract class createdByScreenID : IBqlField { }
        #endregion

        #region CreatedDateTime
        [PXDBCreatedDateTime()]
        [PXUIField(DisplayName = "Created Date Time")]
        public virtual DateTime? CreatedDateTime { get; set; }
        public abstract class createdDateTime : IBqlField { }
        #endregion

        #region LastModifiedByID
        [PXDBLastModifiedByID()]
        public virtual Guid? LastModifiedByID { get; set; }
        public abstract class lastModifiedByID : IBqlField { }
        #endregion

        #region LastModifiedByScreenID
        [PXDBLastModifiedByScreenID()]
        public virtual string LastModifiedByScreenID { get; set; }
        public abstract class lastModifiedByScreenID : IBqlField { }
        #endregion

        #region LastModifiedDateTime
        [PXDBLastModifiedDateTime()]
        [PXUIField(DisplayName = "Last Modified Date Time")]
        public virtual DateTime? LastModifiedDateTime { get; set; }
        public abstract class lastModifiedDateTime : IBqlField { }
        #endregion

        #region Tstamp
        [PXDBTimestamp()]
        [PXUIField(DisplayName = "Tstamp")]
        public virtual byte[] Tstamp { get; set; }
        public abstract class tstamp : IBqlField { }
        #endregion

        #region NoteID  
        [PXSearchable(INSERT YOUR SEARCHABLE CODE HERE OR REMOVE THIS LINE TO NOT BE SEARCHABLE)]
        [PXNote]
        public virtual Guid? NoteID { get; set; }
        public abstract class noteID : IBqlField { }
        #endregion

        #region DeletedDatabaseRecord
        [PXDBBool()]
        [PXDefault(false)]
        [PXUIField(DisplayName = "Deleted Database Record")]
        public virtual bool? DeletedDatabaseRecord { get; set; }
        public abstract class deletedDatabaseRecord : IBqlField { }
        #endregion

        #region IAssign Members
        int? PX.Data.EP.IAssign.WorkgroupID
        {
            get { return WorkgroupID; }
            set { WorkgroupID = value; }
        }

        Guid? PX.Data.EP.IAssign.OwnerID
        {
            get { return OwnerID; }
            set { OwnerID = value; }
        }
        #endregion


        public static class Statuses
        {
            public class ListAttribute : PXStringListAttribute
            {
                public ListAttribute() : base(
                    new[]
                    {
                    Pair(Hold, PX.Objects.EP.Messages.Hold),
                    Pair(PendingApproval, PX.Objects.EP.Messages.PendingApproval),
                    Pair(Approved, PX.Objects.EP.Messages.Approved),
                    Pair(Rejected, PX.Objects.EP.Messages.Rejected),
                    })
                { }
            }

            public const string Hold = "H";
            public const string PendingApproval = "P";
            public const string Approved = "A";
            public const string Rejected = "V";   // V = VOIDED

        }
    }

    public static class AssignmentMapType
    {
        public class AssignmentMapTypeXX : Constant<string>
        {
            public AssignmentMapTypeXX() : base(typeof(XXDocument).FullName) { }
        }
    }
}

覆盖EPApprovalMapMaint屏幕类型列表:

using PX.Data;
using PX.SM;
using PX.TM;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Common;
using PX.Objects;
using PX.Objects.EP;

namespace PX.Objects.EP
{
  public class EPApprovalMapMaint_Extension : PXGraphExtension<EPApprovalMapMaint>
  {
    #region Event Handlers
    public delegate IEnumerable<String> GetEntityTypeScreensDelegate();
    [PXOverride]
    public IEnumerable<String> GetEntityTypeScreens(GetEntityTypeScreensDelegate baseMethod)
    {
      return new string[]
      {
        "AP301000",//Bills and Adjustments
        "AP302000",//Checks and Payments
        "AP304000",//Quick Checks
        "AR302000",//Payments and Applications
        "AR304000",//Cash Sales
        "CA304000",//Cash Transactions
        "EP305000",//Employee Time Card
        "EP308000",//Equipment Time Card
        "EP301000",//Expense Claim
        "EP301020",//Expense Receipt
        "PM301000",//Projects
        "PM307000",//Proforma
        "PM308000",//Change Order
        "PO301000",//Purchase Order
        "RQ301000",//Purchase Request
        "RQ302000",//Purchase Requisition
        "SO301000",//Sales Order
        "CR304500",//Quote
        "XX000000"//My Custom Document Screen
      };

      //return baseMethod();

    }
    #endregion
  }
}

我的自动化步骤。这是从“自动化定义”屏幕生成的XML格式的,但最好使用它来学习阅读此格式并在“自动化步骤”屏幕中复制适当的步骤。就我而言,我利用一个非数据库字段来批准,以帮助触发批准的进展(EPApprovalAutomation),但是我的状态为:保留->待批准-> [已批准|已拒绝],我实际上不需要存储在数据库中“批准”。

<?xml version="1.0" encoding="utf-8"?>
<Screens>
    <Screen ScreenID="XX000000">
        <Menu ActionName="Action">
            <MenuItem Text="Approve" />
            <MenuItem Text="Reject" />
        </Menu>
        <Step StepID="Approved" GraphName="MyNamespace.MyGraph" ViewName="Savings" TimeStampName="Tstamp">
            <Filter FieldName="Approved" Condition="Equals" Value="True" Value2="False" Operator="And" />
            <Filter FieldName="Status" Condition="Equals" Value="P" Operator="And" />
            <Action ActionName="Action" MenuText="Approve" IsDisabled="1" />
            <Action ActionName="Action" MenuText="Reject" IsDisabled="1" />
            <Action ActionName="*" IsDefault="1">
                <Fill FieldName="Status" Value="A" />
            </Action>
        </Step>
        <Step StepID="Hold" GraphName="MyNamespace.MyGraph" ViewName="Savings" TimeStampName="Tstamp">
            <Filter FieldName="Hold" Condition="Equals" Value="True" Value2="False" Operator="And" />
            <Filter FieldName="Status" Condition="Does Not Equal To" Value="H" Operator="And" />
            <Action ActionName="*" IsDefault="1" AutoSave="4">
                <Fill FieldName="Status" Value="H" />
            </Action>
            <Action ActionName="Action" MenuText="Approve" IsDisabled="1" />
            <Action ActionName="Action" MenuText="Reject" IsDisabled="1" />
        </Step>
        <Step StepID="Hold-Pending Approval" GraphName="MyNamespace.MyGraph" ViewName="Savings" TimeStampName="Tstamp">
            <Filter FieldName="Hold" Condition="Equals" Value="False" Value2="False" Operator="And" />
            <Filter FieldName="Status" Condition="Equals" Value="H" Operator="And" />
            <Action ActionName="*" IsDefault="1" AutoSave="4">
                <Fill FieldName="Status" Value="P" />
            </Action>
        </Step>
        <Step StepID="Pending Approval" GraphName="MyNamespace.MyGraph" ViewName="Savings" TimeStampName="Tstamp">
            <Filter FieldName="Status" Condition="Equals" Value="P" Operator="And" />
            <Action ActionName="Action" MenuText="Approve">
                <Fill FieldName="Approved" Value="True" />
                <Fill FieldName="@actionID" Value="1" />
                <Fill FieldName="@refresh" Value="True" />
            </Action>
            <Action ActionName="Action" MenuText="Reject">
                <Fill FieldName="Rejected" Value="True" />
                <Fill FieldName="Status" Value="V" />
                <Fill FieldName="@actionID" Value="1" />
                <Fill FieldName="@refresh" Value="True" />
            </Action>
        </Step>
    </Screen>
</Screens>

代码的其他部分(包括设置屏幕)很容易创建,以遵循原始帖子中提到的Brendan的帖子。这些代码示例应指导您完成我反复尝试的所有步骤,并尝试遵循面包屑进行遍历(许多跟踪仅以元数据结尾,而不是以不完整的成熟代码结尾)实际上在我的CodeRepository中。)

答案就在我眼前,这一切看起来都很简单。但是现在知道系统如何在代码级别处理批准,这对我来说找到方法非常困难。

从根本上来说,请了解EPApprovalAutomation是标准的批准钩子。它管理EPApproval记录,这些记录是实际的批准。您需要为其设置一个批准图,以了解要遵循的批准树,但是Brendan再次出色地解释了他的帖子中的设置。

您告诉EPApprovalAutomation您要批准的文件/记录是什么,并为它提供管理批准所需的相关字段名称。我在自动化步骤中将字段设置为批准或拒绝,并且EPApprovalAutomation注意到将字段设置为true以处理批准步骤。请注意,它似乎仅根据输入来真正管理EPApproval记录。它将查找适当的批准图,并保留批准完成或查找下一步以开始该批准。

必须以某种方式编写自动化步骤,以便在满足正确条件时操纵诸如状态之类的字段。在我的情况下,当其余代码响应“批准”按钮运行后仍设置了“批准”标志时,就会发生批准。由于我没有保存到数据库,因此我只能查找DAC中的字段何时仍设置为true,然后将状态切换为“已批准”。在“拒绝”上,只需继续并设置“拒绝”即可触发拒绝批准,然后在我的文档中将状态权限设置为“拒绝”。

我对Acumatica还是很陌生,因此可能会有更好的方法来执行其中的一些操作。如果您碰巧更有经验并且有一些见识可以帮助我们所有人提高技能,请发表评论。