【问题标题】:Adding Standard Approvals to Custom Acumatica ERP Module将标准批准添加到自定义 Acumatica ERP 模块
【发布时间】:2018-10-02 23:35:46
【问题描述】:

我有一个需要批准的 Acumatica ERP 自定义模块。我想利用 Acumatica ERP 中的标准审批机制,所以我按照本指南...

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

我认为一切都已就绪,但是当我取消保留记录时,什么也没有发生,只是我编写的事件按我的意图将状态切换为 Pending Approval。 (也许我应该让审批流程处理这部分?)我希望我的自动化步骤应该很简单,因为我的状态总是如下:

Hold -> Pending Approval -> Approved -or- Rejected

我没有收到任何错误,所以我不确定我的自动化步骤是否定义不正确,或者我是否应该在 Brendan 回答的步骤 3 中所说的 XXApprovalAutomation 类中编写特定的代码上面的帖子。

我对 Acumatica 还是很陌生,所以很多内部工作仍然有点神秘,需要我不断地挖掘 CodeRepository。我不确定我的自定义 XXApprovalAutomation 是否应该在 GetAssignedMaps 覆盖中有特定的内容,但除非有人可以指导我,否则我会继续挖掘和应用试验和错误。

有人可以解释一下审批系统如何工作的基本流程以及我需要的未在注释帖子中显示的任何相关示例代码(比如 GetAssignedMaps 的内容以点击我定义的审批图)?或者更好的是,指出我可以在哪里阅读更多内容?

【问题讨论】:

  • 审批图在产品最终用户文档中有一个部分:help.acumatica.com/…
  • 谢谢,但我已经能够使用它来定义已经设置为使用它们的模块的批准。我正在努力解决的是如何将批准编程到一个新模块中。我怀疑我需要编程如何将 XXSetupApproval 表连接到将我的状态从保留切换到待批准的事件。只是不确定该怎么做,或者我是否走在正确的轨道上。 Brendan 在他的帖子中说过,您必须编写自己的 XXApprovalAutomation 类并覆盖 GetAssignedMaps - 似乎是我需要在代码中修复的地方,但我找不到要遵循的示例代码。
  • 是的,我提供的链接是针对“最终用户”文档的,对于“请解释审批系统如何工作的基本流程”应该足够了。但是,我没有任何相关的示例代码,因此我将其作为评论而不是答案发布。
  • 一个更接近的设置...link 显示覆盖 EPApprovalAutomation 虽然不是 GetAssignedMaps。
  • 似乎stackoverflow.com/questions/32400353/… 与 18R1/18R2 之间的主要区别需要扩展 EPApprovalMapMaint 和 EPAssignmentMapMaint 并覆盖 GetEntityTypeScreens 以添加您的屏幕

标签: acumatica


【解决方案1】:

自定义批准似乎有几种方法,但这将坚持自动化步骤。在继续之前,您应该熟悉:

自动化定义 - Acumatica ERP 2018r1 的此区域允许您在一个位置查看系统给定区域的所有自动化步骤。例如,选择 PO 默认定义 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 是启用审批时在自定义首选项屏幕中定义的审批图的视图。 (这会将 Approval Maps 连接到 Notifications,以便定义的人员获得指定的批准通知。) 批准是神奇的……这是一些显然不为公众理解的超级秘密特殊调味料。但是,传入的参数定义了 (1) 什么 DAC 描述了您想要批准的数据记录,(2) DAC 中的哪些字段与 Approved、Rejected 和 Hold 相关,以及 (3) 这个魔法框应该在哪里查找批准地图和通知 - 即 XXSetupApproval DAC 的名称。

未经证实的事实...正是我观察到的... 这个名为 EPApprovalAutomation 的超级秘密特殊魔法框似乎可以查看 Hold 字段的状态,当未选中时,使用 XXSetupApproval 数据导航 Approval Map。 When the Actions of Approve and Reject are selected, this same magic box seems to apply the appropriate values to the approved and rejected fields.自动化步骤监控字段的更改(无论您如何定义它)并应用您的自动化,例如将状态字段设置为已批准或已拒绝。 EPApprovalAutomation 似乎要做的最后一件事是将“批准者”用户、日期和状态应用于您应该在自定义屏幕上的“批准详细信息”选项卡中显示的 EPApproval 记录。

现在,从投机回到我所做的事情,以进一步前进......

在需要自定义批准的数据的 DAC 中,请务必按如下方式定义 Status、Hold、Approved 和 Rejected。注意它在数据库中和不在数据库中的区别。

    #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

注意 Rejected 是 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。我还没有完成,但这让我克服了我所问的障碍。完全公开 - 当我拒绝批准时,我的批准详细信息不会更新以显示谁/何时/状态,但我的数据记录已正确更新。但这并不是我在这里真正要问的,所以这应该涵盖预期的原始问题。

【讨论】:

  • 好帖子。从 Gabriel 和我发布 5.3 版的答案到现在的 2018R1 的当前版本,您可能会遇到一些差异。自从迁移到 2017R2 后,我必须开始使用的相同自定义功能没有任何代码更改。自动化步骤设置确实有点愚蠢和不利 - 它们无法通过自定义包轻松导出/导入。
【解决方案2】:

最终使用标准 Acumatica 代码和自动化步骤完成了整个流程。作为 Acumatica 的新手,这对我来说尤其痛苦,当我学习系统和工具的其他部分时,我不得不从中休息很多次。对于那些想避免我忍受无数小时的反复试验的人,这里是您在 2018R1 中自定义的相关代码。 (我知道有些东西可能需要为 R2 重写,所以如果您无法正常工作,请注意您的版本。)

在您深入研究之前,请注意我留下了原始答案,因为它是我学习曲线的一部分,并且可以帮助您从我开始的位置(可能是您所在的位置)到我最终使用它的位置想要的。

我的图表:

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 记录。您需要设置一个 Approval Map 以了解要遵循的批准树,但 Brendan 在他的帖子中再次出色地解释了这些设置。

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

必须以一种方式编写自动化步骤,以便在满足正确条件时操作状态等字段。在我的例子中,当响应 Approve 按钮的其余代码运行后,approved 标志仍然设置时,就会发生批准。由于我不保存到数据库中,因此我可以查找我的 DAC 中的字段何时仍设置为 true,然后将我的状态切换为 Approved。在拒绝时,只需继续设置拒绝以触发拒绝批准,然后在我的文档上将状态权限设置为拒绝。

我对 Acumatica 还很陌生,所以可能有更好的方法来做这件事。如果您碰巧更有经验并且有一些见解可以帮助我们提高我们的技能,请发表评论。

【讨论】:

  • 为避免丢失 acumatica 在未来版本或其他扩展中添加的屏幕,您应该将屏幕添加到返回的基本调用... [PXOverride] public virtual IEnumerable GetEntityTypeScreens(Func> 方法) { var list = method?.Invoke().ToList(); if (list != null) { list.Add("XX000000"); } 返回列表; }
  • 同意。上次我在那个部分时,我做了类似的事情来清理我的错误代码。
  • 我们正在为 19R1 中的 2 个新屏幕再次进行审批,并遇到了类似的问题。我们对将 18R1/18R2 更改适应定制批准感到同样痛苦。与在每个批准/分配屏幕上覆盖该调用相比,最好设置屏幕 ID 的定义位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-27
  • 2017-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
相关资源
最近更新 更多