Skip to content

Instantly share code, notes, and snippets.

@anesvijskij
Last active June 16, 2022 19:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anesvijskij/7b671f4a21ec6c8f564162a94231dbca to your computer and use it in GitHub Desktop.
Save anesvijskij/7b671f4a21ec6c8f564162a94231dbca to your computer and use it in GitHub Desktop.
Code sample for ARInvoiceEntry workflow customization in 2022R1
// - Modify order of existing composite/parent steps (pending email, pending print)
// - Add additional state (pending review)
// - Trigger events by workflow on field changes (subscribe on changing amount field – trigger back to pending review state)
using System;
using PX.Common;
using PX.Data;
using PX.Data.WorkflowAPI;
using PX.Objects.AR;
namespace MyProject
{
using static BoundedTo<ARInvoiceEntry, ARInvoice>;
/// <summary>
/// The DAC extension with the new fields
/// </summary>
public class ARInvoice_ReviewExtension : PXCacheExtension<ARInvoice>
{
#region UsrReviewed
/// <summary>
/// The additional DAC field that indicates whether the invoice is reviewed
/// </summary>
public abstract class usrReviewed : PX.Data.BQL.BqlBool.Field<usrReviewed>
{
}
[PXDBBool]
[PXDefault(false)]
[PXUIField(DisplayName = "Reviewed")]
public virtual Boolean? UsrReviewed { get; set; }
#endregion
// The new document status
#region PendingReview Status
public const string PendingReview = "I"; // 'R' is already used for CreditHold
public class pendingReview : PX.Data.BQL.BqlString.Constant<pendingReview>
{
public pendingReview() : base(PendingReview)
{
}
}
#endregion
}
/// <summary>
/// A class that represents a union of two DAC properties
/// An event will be fired if any of the two properties (CuryOrigDocAmt 'Detail Total' OR CuryOrigDiscAmt 'Cash Discount') are changed
/// </summary>
public class OnUpdateCuryOrig : TypeArrayOf<IBqlField>
.FilledWith<ARInvoice.curyOrigDocAmt, ARInvoice.curyOrigDiscAmt>
{
}
// ReSharper disable once InconsistentNaming
// ReSharper disable once UnusedMember.Global
public class ARInvoiceEntry_ReviewWorkflowExtension : PXGraphExtension<ARInvoiceEntry_Workflow, ARInvoiceEntry>
{
/// <summary>
/// The action that marks the document as reviewed
/// </summary>
public PXAction<ARInvoice> MarkReviewed;
[PXButton]
[PXUIField(DisplayName = "Mark as Reviewed")]
public virtual void markReviewed()
{
}
/// <summary>
/// The additional condition that will be used in workflow
/// </summary>
public class Conditions : Condition.Pack
{
/// <summary>
/// The condition is true if the UsrReviewed property equals True
/// </summary>
public Condition IsInspected => GetOrCreate(b => b.FromBql<
ARInvoice_ReviewExtension.usrReviewed.IsEqual<True>>());
}
/// <summary>
/// Override default screen configuration
/// </summary>
/// <param name="config"></param>
public override void Configure(PXScreenConfiguration config)
{
Configure(config.GetScreenConfigurationContext<ARInvoiceEntry, ARInvoice>());
}
private void Configure(WorkflowContext<ARInvoiceEntry, ARInvoice> context)
{
// Get the 'Process' category to add the new action to it
var processingCategory = context.Categories.Get(ARInvoiceEntry_Workflow.CategoryID.Processing);
// Get the conditions pack to use it in the workflow configuration
var conditions = context.Conditions.GetPack<Conditions>();
// Include the MarkReviewed action into the screen configuration
var markReviewedAction = context.ActionDefinitions.CreateExisting<ARInvoiceEntry_ReviewWorkflowExtension>(
extension => extension.MarkReviewed, act => act
// Add the MarkReviewed action to the 'Process' category
.WithCategory(processingCategory)
// Update the usrReviewed field when the MarkReviewed action is executed
.WithFieldAssignments(fields =>
fields.Add<ARInvoice_ReviewExtension.usrReviewed>(v =>
v.SetFromValue(true))));
// Create a new workflow event handler named OnUpdateCuryOrigHandler that will be subscribed to change
// of the CuryOrigDocAmt OR CuryOrigDiscAmt fields
var onUpdateCuryOrigHandler = context.WorkflowEventHandlers.Create(config =>
config.WithTargetOf<ARInvoice>().OfFieldsUpdated<OnUpdateCuryOrig>()
.IsNew("OnUpdateCuryOrigHandler")
.UsesTargetAsPrimaryEntity()
// when one of the amount fields changes, the usrReviewed field is assigned FALSE
.WithFieldAssignments(fields => fields.Add<ARInvoice_ReviewExtension.usrReviewed>(v =>
v.SetFromValue(false))));
// Update the screen configuration of the ARInvoiceEntry graph
context.UpdateScreenConfigurationFor(screen =>
// Update the default ARInvoiceEntry workflow
screen.UpdateDefaultFlow(flow =>
// Modify workflow states
flow.WithFlowStates(states =>
// Update the main sequence with the name 'HoldToBalance'
states.UpdateSequence<ARDocStatus.HoldToBalance>(sequence =>
// Modify the sequence states
sequence.WithStates(sequenceStates =>
{
// Modify the 'pendingEmail' state by moving it forward, after the 'creditHold' state
sequenceStates.Update<ARDocStatus.pendingEmail>(state =>
state.PlaceAfter<ARDocStatus.creditHold>());
// Add the new 'pendingReview' state
sequenceStates.Add<ARInvoice_ReviewExtension.pendingReview>(
flowState =>
{
return flowState
// Make the new state 'skippable' when the document has already been inspected
.IsSkippedWhen(conditions.IsInspected)
// Place the new state after the 'pendingPrint' state
.PlaceAfter<ARDocStatus.pendingPrint>()
// Make the 'putOnHold' and 'markReviewed' actions available in this state
.WithActions(actions =>
{
actions.Add(g => g.putOnHold);
actions.Add(markReviewedAction,
config => config
.IsDuplicatedInToolbar()
.WithConnotation(ActionConnotation.Primary));
});
});
})
// Make the onUpdateCuryOrigHandler event handler is active in all child states of the sequence
.WithEventHandlers(handlers => handlers.Add(onUpdateCuryOrigHandler)))
)
// Modify transitions for the new state
.WithTransitions(transitions =>
{
// Add a transition from the 'pendingInspection' state to the next state in sequence when the 'MarkReviewed' action is selected
transitions.Add(source =>
source.From<ARInvoice_ReviewExtension.pendingReview>().ToNext() // in fact, next state is 'Balanced'
.IsTriggeredOn(markReviewedAction));
// Add a transition from the sequence (and this means that for all child states of the sequence) to the
// beginning of the sequence on fields changing to start the process of "searching" of the valid state again
transitions.Add(source =>
source.From<ARDocStatus.HoldToBalance>().To<ARDocStatus.HoldToBalance>()
.IsTriggeredOn(onUpdateCuryOrigHandler));
}))
// Add the new action to the screen configuration
.WithActions(actions => actions.Add(markReviewedAction))
// Modify field states in the screen configuration
.WithFieldStates(fields =>
{
// Add the new 'PendingInspection' value to the Status field
fields.Add<ARInvoice.status>(field =>
field.SetComboValue(ARInvoice_ReviewExtension.PendingReview, "On Review"));
// Make the new 'usrReviewed' field always disabled
fields.Add<ARInvoice_ReviewExtension.usrReviewed>(field => field.IsDisabledAlways());
})
// Add the new workflow event handler to the screen configuration
.WithHandlers(handlers => handlers.Add(onUpdateCuryOrigHandler)));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment