Skip to content

Instantly share code, notes, and snippets.

@anesvijskij
Created June 16, 2022 18:31
Show Gist options
  • Save anesvijskij/0f194da32b5a45e9f293437f9a6ad462 to your computer and use it in GitHub Desktop.
Save anesvijskij/0f194da32b5a45e9f293437f9a6ad462 to your computer and use it in GitHub Desktop.
Draft for ARInvoiceEntry workflow customization in 2021R2
using System;
using PX.Common;
using PX.Data;
using PX.Data.WorkflowAPI;
using PX.Objects.AR;
namespace MyProject
{
using static BoundedTo<ARInvoiceEntry, ARInvoice>;
/// <summary>
/// DAC extension with new
/// </summary>
public class ARInvoice_ReviewExtension : PXCacheExtension<ARInvoice>
{
#region Events
public class Events : PXEntityEvent<ARInvoice>.Container<Events>
{
public PXEntityEvent<ARInvoice> OnUpdateCuryOrigChanged;
}
#endregion
#region UsrReviewed
/// <summary>
/// Additional DAC field to store 'reviewed' mark
/// </summary>
public abstract class usrReviewed : PX.Data.BQL.BqlBool.Field<usrReviewed>
{
}
[PXDBBool]
[PXDefault(false)]
[PXUIField(DisplayName = "Reviewed")]
public virtual Boolean? UsrReviewed { get; set; }
#endregion
// New 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>
/// Class, that represent union of two DAC properties
/// Event will be fired if any of CuryOrigDocAmt OR CuryOrigDiscAmt fields will be 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>
{
protected void _(Events.FieldUpdated<ARInvoice, ARInvoice.curyOrigDocAmt> e)
{
if (e.Row != null)
{
ARInvoice_ReviewExtension.Events.Select(it => it.OnUpdateCuryOrigChanged)
.FireOn(this.Base, e.Row);
}
}
protected void _(Events.FieldUpdated<ARInvoice, ARInvoice.curyOrigDiscAmt> e)
{
if (e.Row != null)
{
ARInvoice_ReviewExtension.Events.Select(it => it.OnUpdateCuryOrigChanged)
.FireOn(this.Base, e.Row);
}
}
/// <summary>
/// Action to mark document as reviewed
/// </summary>
public PXAction<ARInvoice> MarkReviewed;
[PXButton]
[PXUIField(DisplayName = "Mark as Reviewed")]
public virtual void markReviewed()
{
}
/// <summary>
/// Here we have additional condition, that will be used in workflow
/// </summary>
public class Conditions : Condition.Pack
{
/// <summary>
/// UsrReviewed == True
/// </summary>
public Condition IsInspected => GetOrCreate(b => b.FromBql<
ARInvoice_ReviewExtension.usrReviewed.IsEqual<True>>());
/// <summary>
/// UsrReviewed == False
/// </summary>
public Condition IsNotInspected => GetOrCreate(b => b.FromBql<
ARInvoice_ReviewExtension.usrReviewed.IsEqual<False>>());
}
/// <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)
{
const string initialState = "_";
// Get 'Process' category to add one more action below
var processingCategory = context.Categories.Get(ARInvoiceEntry_Workflow.CategoryID.Processing);
// Get conditions pack to use it workflow configuration
var conditions = context.Conditions.GetPack<Conditions>();
// Include MarkReviewed action into screen configuration
var markReviewedAction = context.ActionDefinitions.CreateExisting<ARInvoiceEntry_ReviewWorkflowExtension>(
extension => extension.MarkReviewed, act => act
// add MarkReviewed into 'Process' category
.WithCategory(processingCategory)
// update usrReviewed field on MarkReviewed execution
.WithFieldAssignments(fields =>
fields.Add<ARInvoice_ReviewExtension.usrReviewed>(v =>
v.SetFromValue(true))));
// Create new workflow event handler OnUpdateCuryOrigHandler, that will be subscribed on changing
// of DAC's CuryOrigDocAmt OR CuryOrigDiscAmt fields
var onUpdateCuryOrigHandler = context.WorkflowEventHandlers.Create(config =>
config.WithTargetOf<ARInvoice>()
.OfEntityEvent<ARInvoice_ReviewExtension.Events>(events => events.OnUpdateCuryOrigChanged)
.IsNew("OnUpdateCuryOrigHandler")
.UsesTargetAsPrimaryEntity()
// when one of the amount fields changed - usrReviewed is returned back to FALSE
.WithFieldAssignments(fields => fields.Add<ARInvoice_ReviewExtension.usrReviewed>(v =>
v.SetFromValue(false))));
// Update Screen Configuration for ARInvoiceEntry
context.UpdateScreenConfigurationFor(screen =>
// Update default ARInvoiceEntry workflow
screen.UpdateDefaultFlow(flow =>
// modify workflow states
flow.WithFlowStates(states =>
{
states.Add<ARInvoice_ReviewExtension.pendingReview>(flowState =>
{
return flowState
.WithActions(actions =>
{
actions.Add(markReviewedAction, a => a.IsDuplicatedInToolbar());
actions.Add(g => g.putOnHold);
}).WithEventHandlers(handlers => { handlers.Add(onUpdateCuryOrigHandler); });
});
})
// modify transitions for the new state
.WithTransitions(transitions =>
{
#region Changes in transitions to reorder states
transitions.UpdateGroupFrom(initialState, ts =>
{
ts.Update(
t => t.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.initializeState)
.When(context.Conditions.Get("IsPendingPrint")),
transition => transition.PlaceAfter(source =>
source.From(initialState).To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.initializeState)));
});
transitions.UpdateGroupFrom<ARDocStatus.hold>(ts =>
{
ts.Update(
t => t.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.releaseFromHold)
.When(context.Conditions.Get("IsPendingPrint")),
transition => transition.PlaceAfter(source =>
source.From<ARDocStatus.hold>().To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.releaseFromHold)));
ts.Update(
t => t.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.OnUpdateStatus)
.When(context.Conditions.Get("IsPendingPrint")),
transition => transition.PlaceAfter(source =>
source.From<ARDocStatus.hold>().To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.OnUpdateStatus)));
});
transitions.UpdateGroupFrom<ARDocStatus.creditHold>(ts =>
{
ts.Update(
t => t.To<ARDocStatus.pendingPrint>()
.IsTriggeredOn(g => g.releaseFromCreditHold)
.When(context.Conditions.Get("IsPendingPrint")),
transition => transition.PlaceAfter(source =>
source.From<ARDocStatus.creditHold>().To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.releaseFromCreditHold)));
});
transitions.UpdateGroupFrom<ARDocStatus.balanced>(ts =>
{
ts.Update(
t => t.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.OnUpdateStatus)
.When(context.Conditions.Get("IsPendingPrint")),
transition => transition.PlaceAfter(source =>
source.From<ARDocStatus.balanced>().To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.OnUpdateStatus)));
});
#endregion
#region Changes in transitions only to add one state
transitions.UpdateGroupFrom(initialState, ts =>
{
ts.Add(t => t // New Pending Approval
.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.initializeState)
.When(conditions.IsNotInspected)
.PlaceAfter(selector => selector
.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.initializeState))
);
});
transitions.UpdateGroupFrom<ARDocStatus.hold>(ts =>
{
ts.Add(t => t
.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.releaseFromHold)
.DoesNotPersist()
.When(conditions.IsNotInspected)
.PlaceAfter(selector => selector
.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.releaseFromHold))
);
ts.Add(t => t
.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.OnUpdateStatus)
.When(conditions.IsNotInspected)
.PlaceAfter(selector => selector
.To<ARDocStatus.pendingPrint>().IsTriggeredOn(g => g.OnUpdateStatus))
);
ts.Add(t => t
.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(onUpdateCuryOrigHandler)
.When(conditions.IsNotInspected)
);
});
transitions.UpdateGroupFrom<ARDocStatus.creditHold>(ts =>
{
ts.Add(t => t.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.releaseFromCreditHold)
.When(conditions.IsNotInspected).PlaceAfter(source =>
source.To<ARDocStatus.pendingPrint>()
.IsTriggeredOn(g => g.releaseFromCreditHold)));
});
transitions.UpdateGroupFrom<ARDocStatus.pendingPrint>(ts =>
{
ts.Add(t => t.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.printInvoice)
.When(conditions.IsNotInspected).PlaceAfter(source =>
source.To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.printInvoice)));
ts.Add(t => t.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.OnUpdateStatus)
.When(conditions.IsNotInspected).PlaceAfter(source =>
source.To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(g => g.OnUpdateStatus)));
ts.Add(t => t
.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(onUpdateCuryOrigHandler)
.When(conditions.IsNotInspected)
);
});
transitions.UpdateGroupFrom<ARDocStatus.pendingEmail>(ts =>
{
ts.Add(t => t.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.OnUpdateStatus)
.When(conditions.IsNotInspected).PlaceAfter(source =>
source.To<ARDocStatus.pendingPrint>()
.IsTriggeredOn(g => g.OnUpdateStatus)));
});
transitions.AddGroupFrom<ARInvoice_ReviewExtension.pendingReview>(ts =>
{
ts.Add(t => t
.To<ARDocStatus.hold>()
.IsTriggeredOn(g => g.OnUpdateStatus)
.When(context.Conditions.Get("IsOnHold")));
ts.Add(t => t
.To<ARDocStatus.creditHold>()
.IsTriggeredOn(markReviewedAction)
.When(context.Conditions.Get("IsOnCreditHold")));
// ORDER IS IMPORTANT HERE!
ts.Add(t => t
.To<ARDocStatus.pendingEmail>()
.IsTriggeredOn(markReviewedAction)
.When(context.Conditions.Get("IsPendingEmail")));
ts.Add(t => t
.To<ARDocStatus.pendingPrint>()
.IsTriggeredOn(markReviewedAction)
.When(context.Conditions.Get("IsPendingPrint")));
ts.Add(t => t
.To<ARDocStatus.balanced>()
.IsTriggeredOn(markReviewedAction)
.When(context.Conditions.Get("IsBalanced")));
ts.Add(t => t
.To<ARDocStatus.hold>()
.IsTriggeredOn(g => g.putOnHold)
.DoesNotPersist());
ts.Add(t => t.To<ARDocStatus.scheduled>()
.IsTriggeredOn(g => g.OnConfirmSchedule)
.WithFieldAssignments(fas =>
{
fas.Add<ARInvoice.scheduled>(e => e.SetFromValue(true));
fas.Add<ARInvoice.scheduleID>(e => e.SetFromExpression("@ScheduleID"));
}));
});
transitions.UpdateGroupFrom<ARDocStatus.balanced>(ts =>
{
ts.Add(t => t.To<ARInvoice_ReviewExtension.pendingReview>()
.IsTriggeredOn(g => g.OnUpdateStatus)
.When(conditions.IsNotInspected).PlaceAfter(source =>
source.To<ARDocStatus.pendingPrint>()
.IsTriggeredOn(g => g.OnUpdateStatus)));
});
#endregion
}))
// add new action into configuration
.WithActions(actions => actions.Add(markReviewedAction))
// modify fields in WorkflowConfiguration
.WithFieldStates(fields =>
{
// Add new 'PendingInspection' value to Status ComboBox
fields.Add<ARInvoice.status>(field =>
field.SetComboValue(ARInvoice_ReviewExtension.PendingReview, "On Review"));
// Make new 'usrReviewed' field always disabled
fields.Add<ARInvoice_ReviewExtension.usrReviewed>(field => field.IsDisabledAlways());
})
// add new workflow event handler to the configuration
.WithHandlers(handlers => handlers.Add(onUpdateCuryOrigHandler)));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment