最终使用标准 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 还很陌生,所以可能有更好的方法来做这件事。如果您碰巧更有经验并且有一些见解可以帮助我们提高我们的技能,请发表评论。