Flows & Tasks Engine
Several business journeys are sequences of modals with branching, back
navigation and cancellation. Rather than encoding that in component state, the
app uses a small orchestration engine in
src/app/core/_base/tasks-flow/tasks-flow.ts.
Core types
| Type | Role |
|---|---|
|
One step. |
|
A |
|
|
|
|
|
The runner. Holds the ordered task list, the shared context, and the cursor. |
|
Root-provided fluent builder. |
Control flow
| Call | Behaviour |
|---|---|
|
Executes the task at the current cursor. |
|
Advances the cursor, or emits |
|
Moves the cursor back, or emits |
|
Re-executes the current task. |
|
Registers a completion handler. Handlers receive |
The conventional per-task handler maps a result type onto navigation:
private defaultHandler(flow: TasksFlow): FlowTaskDoneHandler {
return result => {
switch (result.type) {
case FlowTaskResultType.Done: flow.runNext(); break;
case FlowTaskResultType.Cancel: flow.runPrevious(); break;
case FlowTaskResultType.Terminated: break;
}
};
}
Terminated deliberately does nothing — it ends the flow in place, which is how
"user closed the dialog and we should show a banner instead" is expressed.
Task catalog
Tasks live in src/app/core/pyb/tasks/ and are registered by TasksModule.
| Task | Purpose |
|---|---|
Advance Line — |
|
|
Collect the draw amount. |
|
Confirm amount, fee and offer type. |
|
Success confirmation after a completed draw. |
|
Show terms of an advance offer. |
|
Show terms of an Instant Advance offer. |
|
Collect ACH details before a first draw. |
|
Accept an offer through the API. |
|
Accept without the confirmation step. |
|
Entry point that launches the accept-offer flow. |
|
Call the withdraw endpoint. |
DocuSign — |
|
|
Build the redirect URL and leave for DocuSign. |
|
Same, for the re-assign-contract flow. |
General — |
|
|
Dispatch |
|
Remove a dashboard notification once acted on. |
Legal — |
|
|
Present legal disclosures for acknowledgement. Note the file is |
LTC — |
|
|
Present available Long Term Contract options. |
|
Confirm the chosen option. |
|
Redirect to DocuSign for the LTC. |
Composed flows
Flows live in src/app/core/pyb/flows/ and are registered by FlowsModule.
| Flow | Description |
|---|---|
|
Draw funds from an active Advance Line. |
|
Accept a new Advance Line offer. |
|
First draw for an expedited, still-pending supplier. |
|
Activation path for the legacy advance product. |
|
Sign a new Long Term Contract. |
|
Renew an existing Long Term Contract. |
|
Re-sign an updated supplier agreement. |
Worked example: AdvanceLineWithdrawFlow
The flow branches on user state while it is being built, which is the main reason this engine exists.
private buildTaskFlow(): TasksFlow {
const taskFlow = new TaskFlowBuilder();
const { status }: { status: Status } = this.userService.getCurrentUser();
taskFlow
.setContextData('data', this.prepareFlowContextData())
.addTask(this.openAdvanceLineInputModalTask, this.defaultHandler)
.addTask(this.openAdvanceLineConfirmationModalTask, this.defaultHandler);
if (!status.instantAdvanceSigned || this.userService.isAprUserContract()) {
taskFlow.addTask(this.docusignAdvanceLineTask); (1)
} else {
taskFlow
.addTask(this.withdrawAdvanceLineTask, this.defaultHandler)
.addTask(this.updateUserStatusTask, this.defaultHandler)
.addTask(this.openAdvanceLineSuccessModalTask); (2)
}
return taskFlow.getFlow();
}
| 1 | A supplier who has not yet signed, or who is on an APR contract, must go
through DocuSign. The flow ends here because the browser navigates away; it
resumes on return via
DocusignVerificationService.initializeFlow(). |
| 2 | An already-signed, non-APR supplier withdraws inline — no redirect, and the success modal closes the loop in-app. |
The context is seeded with everything downstream tasks need:
private prepareFlowContextData() {
const user = this.userService.getCurrentUser();
const { advanceLine, autodebitPmtCfg, instantAdvanceSigned } = user.status;
return {
advanceLine,
id: user.id,
autodebitPmtCfg,
docuSignNoteVisible: !instantAdvanceSigned,
};
}
Worked example: ReSignContractFlow with a fallback banner
GlobalSubscriptionsService runs the re-sign flow, and if the supplier
terminates it, replaces the modal with a persistent banner that can restart it:
private runReSignContractFlow() {
const banner = reSignContractBannerFactory(() => this.reSignContractFlow.run());
this.reSignContractFlow.run(({ type }) => {
if (type === FlowTaskResultType.Terminated) {
this.bannerService.showBanner(banner);
}
});
}
When to use a flow
Use TasksFlow when all of the following hold:
-
there are three or more sequential steps,
-
a step can send the user backwards,
-
the sequence itself depends on user state, or
-
the sequence is interrupted by an external redirect.
For a simple confirm-then-act interaction, open a MatDialog / PybDialogService
directly — a flow is overhead.