I need to make usage Assignment and Approval Maps. What is template for usage of maps which are constructed at page EP205000? I made research in file coderepository.xml of Acumatica, and found there EPApprovalAutomation class. I wanted to use it, but it requires among arguments usage of class which implements IAssignedMap interface. It gives another problem, because IAssignedMap interface is internal, which gives another riddle, how to use IAssignedMap interface? What are alternatives?
This answer might be coming a bit late, but I'm sure it can be useful to others so I will share it.
New tables and fields for the database
XXSetupApproval
Add new setup table for approval settings in your module (XXSetupApproval further). You can see all required fields below, but you may need to add any additional parameters if you want to split approval for different types of entities.
CREATE TABLE XXSetupApproval
(
CompanyID int NOT NULL,
ApprovalID int NOT NULL identity,
AssignmentMapID int NOT NULL,
AssignmentNotificationID int NULL,
CreatedByID uniqueidentifier NOT NULL,
CreatedByScreenID char(8) NOT NULL,
CreatedDateTime datetime NOT NULL,
LastModifiedByID uniqueidentifier NOT NULL,
LastModifiedByScreenID char(8) NOT NULL,
LastModifiedDateTime datetime NOT NULL,
Tstamp timestamp NULL,
IsActive bit NOT NULL
)
GO
ALTER TABLE XXSetupApproval
ADD CONSTRAINT XXSetupApproval_PK PRIMARY KEY CLUSTERED (CompanyID, ApprovalID)
GO
XXRegister
Modify table of entity for which you need to implement approval mechanism (XXRegister further). Your entity should include three required fields that you can see below.
OwnerID uniqueidentifier NULL,
WorkGroupID int NULL,
Approved bit NOT NULL
XXSetup
Modify table of main setup in your module (XXSetup further). Your setup should include one required field that you can see below. Name this flag according with your entity name, because it will be indicate whether approval mechanism enabled or not (XXRequestApproval further).
XXRequestApproval bit NULL
New update scripts for the database
Update XXRegister table and set Approved flag equal to 1 for all existing records according with your conditions. Use your own expressions instead of three dots.
EXEC sp_executesql N'UPDATE XXRegister SET Approved = 1 WHERE ...'
New tables and fields for the code
AssignmentMapTypeXX
Add your entity to the AssignmentMapType class (AssignmentMapTypeXX further). This type should be used for select the assignment maps of needed types only.
public static class AssignmentMapType
{
...
public class AssignmentMapTypeXX : Constant<string>
{
public AssignmentMapTypeXX() : base(typeof(XXRegister).FullName) { }
}
...
}
XXSetup
Add new properties and classes to the XXSetup DAC according with new fields in the database. Use any other attributes if needed.
#region XXRequestApproval
public abstract class xXRequestApproval : PX.Data.IBqlField { }
[EPRequireApproval]
[PXDefault(false, PersistingCheck = PXPersistingCheck.Null)]
[PXUIField(DisplayName = "Require Approval")]
public virtual bool? XXRequestApproval { get; set; }
#endregion
XXSetupApproval
Add new DAC to the code according with XXSetupApproval table in the database. XXSetupApproval DAC should realize IAssignedMap interface (AssignmentMapID, AssignmentNotificationID, IsActive fields). Use any other attributes if needed.
[Serializable]
public partial class XXSetupApproval : IBqlTable, IAssignedMap
{
#region ApprovalID
public abstract class approvalID : IBqlField { }
[PXDBIdentity(IsKey = true)]
public virtual int? ApprovalID { get; set; }
#endregion
#region AssignmentMapID
public abstract class assignmentMapID : IBqlField { }
[PXDefault]
[PXDBInt]
[PXSelector(typeof(Search<EPAssignmentMap.assignmentMapID, Where<EPAssignmentMap.entityType, Equal<AssignmentMapType.AssignmentMapTypeXX>>>),
DescriptionField = typeof(EPAssignmentMap.name))]
[PXUIField(DisplayName = "Approval Map")]
public virtual int? AssignmentMapID { get; set; }
#endregion
#region AssignmentNotificationID
public abstract class assignmentNotificationID : IBqlField { }
[PXDBInt]
[PXSelector(typeof(PX.SM.Notification.notificationID), SubstituteKey = typeof(PX.SM.Notification.name))]
[PXUIField(DisplayName = "Pending Approval Notification")]
public virtual int? AssignmentNotificationID { get; set; }
#endregion
#region tstamp
public abstract class Tstamp : IBqlField { }
[PXDBTimestamp()]
public virtual byte[] tstamp { get; set; }
#endregion
#region CreatedByID
public abstract class createdByID : IBqlField { }
[PXDBCreatedByID()]
public virtual Guid? CreatedByID { get; set; }
#endregion
#region CreatedByScreenID
public abstract class createdByScreenID : IBqlField { }
[PXDBCreatedByScreenID()]
public virtual string CreatedByScreenID { get; set; }
#endregion
#region CreatedDateTime
public abstract class createdDateTime : IBqlField { }
[PXDBCreatedDateTime()]
public virtual DateTime? CreatedDateTime { get; set; }
#endregion
#region LastModifiedByID
public abstract class lastModifiedByID : IBqlField { }
[PXDBLastModifiedByID()]
public virtual Guid? LastModifiedByID { get; set; }
#endregion
#region LastModifiedByScreenID
public abstract class lastModifiedByScreenID : IBqlField { }
[PXDBLastModifiedByScreenID()]
public virtual string LastModifiedByScreenID { get; set; }
#endregion
#region LastModifiedDateTime
public abstract class lastModifiedDateTime : IBqlField { }
[PXDBLastModifiedDateTime()]
public virtual DateTime? LastModifiedDateTime { get; set; }
#endregion
#region IsActive
public abstract class isActive : IBqlField { }
[PXDBBool()]
[PXDefault(typeof(Search<XXSetup.xXRequestApproval>), PersistingCheck = PXPersistingCheck.Nothing)]
public virtual bool? IsActive { get; set; }
#endregion
}
XXRegister
Add PXEmailSource attribute to the XXRegister DAC, it is required for "Assignment and Approvals Maps" tree selector.
[PXEMailSource]
public partial class XXRegister : IBqlTable, EP.IAssign ...
Add new properties and classes to the XXRegister DAC according with new fields in the database. XXRegister DAC should realize IAssign interface (OwnerID, WorkgroupID fields). Use any other attributes if needed. Use your own expressions instead of three dots.
#region OwnerID
public abstract class ownerID : IBqlField { }
[PXDBGuid()]
[PXDefault(typeof(...), PersistingCheck = PXPersistingCheck.Nothing)]
[PX.TM.PXOwnerSelector()]
[PXUIField(DisplayName = "Owner")]
public virtual Guid? OwnerID { get; set; }
#endregion
#region WorkgroupID
public abstract class workgroupID : IBqlField { }
[PXDBInt]
[PXDefault(typeof(...), PersistingCheck = PXPersistingCheck.Nothing)]
[PX.TM.PXCompanyTreeSelector]
[PXUIField(DisplayName = "Workgroup", Enabled = false)]
public virtual int? WorkgroupID { get; set; }
#endregion
#region Approved
public abstract class approved : IBqlField { }
[PXDBBool]
[PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Approved", Visibility = PXUIVisibility.Visible, Enabled = false)]
public virtual bool? Approved { get; set; }
#endregion
#region Rejected
public abstract class rejected : IBqlField { }
[PXBool]
[PXDefault(false, PersistingCheck = PXPersistingCheck.Nothing)]
public bool? Rejected { get; set; }
#endregion
Statuses
Add new approval statuses for the entity and use them in the list. Use other letters if “P” and “R” already in use.
public const string PendingApproval = "P";
public const string Rejected = "R";
public class ListAttribute : PXStringListAttribute
{
public ListAttribute() : base(
new string[] { ..., PendingApproval, Rejected, ... },
new string[] { ..., EP.Messages.PendingApproval, EP.Messages.Rejected, ... }) { ; }
}
New code for the graph of the entity
Implement EPApprovalAutomation helper in the graph which is manipulating with your entity. Use your own parameters instead of three dots.
public EPApprovalAutomation<...> Approval;
Add view for the XXSetupApproval DAC. Use your own expressions instead of three dots.
public PXSelect<XXSetupApproval, Where<...>> SetupApproval;
New code for the graph of the main setup
Add view for the XXSetupApproval DAC.
public PXSelect<APSetupApproval> SetupApproval;
Update each XXSetupApproval row according with new value of the XXRequestApproval field.
protected virtual void XXSetup_XXRequestApproval_FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
PXCache cache = this.Caches[typeof(XXSetupApproval)];
foreach (XXSetupApproval setup in PXSelect<XXSetupApproval>.Select(this))
{
setup.IsActive = (bool?)e.NewValue;
cache.Update(setup);
}
}
New tables and fields for the web pages
XXSetup.aspx
Add new tab with approval settings to the main web page of the setup (XXSetup.aspx further). Use any other parameters if needed.
<px:PXTabItem Text="Approval">
<Template>
<px:PXPanel ID="panelApproval" runat="server" >
<px:PXLayoutRule runat="server" LabelsWidth="S" ControlSize="XM" />
<px:PXCheckBox ID="chkXXRequestApproval" runat="server" AlignLeft="True" Checked="True" DataField="XXRequestApproval" CommitChanges="True" />
</px:PXPanel>
<px:PXGrid ID="gridApproval" runat="server" DataSourceID="ds" SkinID="Details" Width="100%" >
<AutoSize Enabled="True" />
<Levels>
<px:PXGridLevel DataMember="SetupApproval" >
<RowTemplate>
<px:PXLayoutRule runat="server" StartColumn="True" LabelsWidth="M" ControlSize="XM" />
<px:PXSelector ID="edAssignmentMapID" runat="server" DataField="AssignmentMapID" AllowEdit="True" CommitChanges="True" />
<px:PXSelector ID="edAssignmentNotificationID" runat="server" DataField="AssignmentNotificationID" AllowEdit="True" />
</RowTemplate>
<Columns>
<px:PXGridColumn DataField="AssignmentMapID" Width="250px" RenderEditorText="True" TextField="AssignmentMapID_EPAssignmentMap_Name" />
<px:PXGridColumn DataField="AssignmentNotificationID" Width="250px" RenderEditorText="True" />
</Columns>
</px:PXGridLevel>
</Levels>
</px:PXGrid>
</Template>
</px:PXTabItem>
XXRegister.aspx
Add new field “Approved” to the main web page of the entity (XXRegister.aspx further). Use any other parameters if needed.
<px:PXCheckBox ID="chkApproved" runat="server" DataField="Approved" CommitChanges="True" Enabled="False" />
Add new tab with approval information to the XXRegister.aspx web page. Use any other parameters if needed.
<px:PXTabItem Text="Approval Details" BindingContext="form" RepaintOnDemand="false">
<Template>
<px:PXGrid ID="gridApproval" runat="server" DataSourceID="ds" Width="100%" SkinID="DetailsInTab" NoteIndicator="True" Style="left: 0px; top: 0px;">
<AutoSize Enabled="True" />
<Mode AllowAddNew="False" AllowDelete="False" AllowUpdate="False" />
<Levels>
<px:PXGridLevel DataMember="Approval">
<Columns>
<px:PXGridColumn DataField="ApproverEmployee__AcctCD" Width="160px" />
<px:PXGridColumn DataField="ApproverEmployee__AcctName" Width="160px" />
<px:PXGridColumn DataField="ApprovedByEmployee__AcctCD" Width="100px" />
<px:PXGridColumn DataField="ApprovedByEmployee__AcctName" Width="160px" />
<px:PXGridColumn DataField="ApproveDate" Width="90px" />
<px:PXGridColumn DataField="Status" AllowNull="False" AllowUpdate="False" RenderEditorText="True"/>
<px:PXGridColumn DataField="WorkgroupID" Width="150px" />
</Columns>
</px:PXGridLevel>
</Levels>
</px:PXGrid>
</Template>
</px:PXTabItem>
New Automation steps for the Automation definition
This is the most difficult part during approval implementation because a few new automation steps should be created in the scope of current behavior of the entity. For example, the entity has next statuses and automation steps accordingly: "Hold" -> "Open". And we should implement approval mechanism between those two steps. Then three new automation steps should be created: "Hold-Open" (if we don’t need to approve the document), "Hold-Pending Approval" (if we need to approve the document), "Pending Approval" (step from which the entity should be approved or rejected). New life cycle will be looks like this: "Hold" -> "Hold-Open" OR "Hold-Pending Approval" -> "Open" OR "Pending Approval". So "Hold-Open" and "Hold-Pending Approval" automation steps are only switches, that determine, which automation step should be used after "Hold".
<Step StepID="Hold-Open" Description="Hold-Open" GraphName="…" ViewName="Document" TimeStampName="tstamp">
<Filter FieldName="Status" Condition="Equals" Value="H" Operator="And" />
<Filter FieldName="Hold" Condition="Equals" Value="False" Value2="False" Operator="And" />
<Filter FieldName="Approved" Condition="Equals" Value="True" Value2="False" Operator="And" />
<Action ActionName="*" IsDefault="1">
<Fill FieldName="Status" Value="N" />
</Action>
</Step>
<Step StepID="Hold-Pending Approval" Description="Hold-Pending Approval" GraphName="…" ViewName="Document" TimeStampName="tstamp">
<Filter OpenBrackets="1" FieldName="Status" Condition="Equals" Value="H" Operator="Or" />
<Filter FieldName="Status" Condition="Equals" Value="N" CloseBrackets="1" Operator="And" />
<Filter FieldName="Hold" Condition="Equals" Value="False" Value2="False" Operator="And" />
<Filter FieldName="Approved" Condition="Equals" Value="False" Value2="False" Operator="And" />
<Action ActionName="*" IsDefault="1">
<Fill FieldName="Status" Value="P" />
</Action>
</Step>
Thanks to Evgeny Kralko from the Acumatica dev team for his work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With