Advance Approval Workflow from Task Decision (Part 2)
This script advances a multi-step approval workflow when an approval task is updated to Approved or Rejected.
Use Case
Use this when approval tasks are created as child records and each task represents one step in an approval chain.
Examples:
- Move from Department Approval to Finance Approval
- Move from Finance Approval to Leadership Approval
- Stop the workflow when any approver rejects the request
- Mark the parent record as approved when all steps are complete
What It Does
On trigger, the script will:
- Read the triggered Approval Task from
input.recordId - Check whether the task was
ApprovedorRejected - If rejected:
- Set the parent record status to
Rejected - Set the current approval stage to
Rejected - Set the completed timestamp
- Set the parent record status to
- If approved:
- Find the next waiting approval task by sequence
- Set the next task to
Pending Approval - Update the parent record’s current approval stage
- If no next task exists:
- Set the parent record status to
Approved - Set the current approval stage to
Completed - Set the completed timestamp
- Set the parent record status to
Key Concept
Each approval task stores its own step status and sequence.
The workflow does not need to know the full approval path in advance. It only needs to:
- Look at the current task sequence
- Find the next waiting task
- Activate the next task
- Complete or reject the parent record when needed
This keeps approval progression simple, predictable, and auditable.
Configuration
| Key | Description | Example |
|---|---|---|
parent.fields.status | Parent record status field | Status |
parent.fields.currentApprovalStage | Tracks current approval stage | Current Approval Stage |
parent.fields.completed | Stores completion timestamp | Completed |
parent.fields.lastUpdated | Stores last update timestamp | Last Updated |
parent.statuses.inReview | Status while approval is still active | In Review |
parent.statuses.approved | Status when all approval steps are complete | Approved |
parent.statuses.rejected | Status when any approval step is rejected | Rejected |
child.type | Child record type for approval steps | Approval Task |
child.fields.approval | Field storing approval state | Approval |
child.fields.sequence | Field storing approval step order | Seq |
child.fields.approvalStage | Field storing approval step name | Approval Stage |
child.fields.department | Field storing assigned department | Department |
child.statuses.waiting | Future approval step not yet active | Waiting |
child.statuses.pending | Active approval step | Pending Approval |
child.statuses.approved | Completed approval step | Approved |
child.statuses.rejected | Rejected approval step | Rejected |
Inputs
| Input | Required | Description |
|---|---|---|
recordId | Yes | Approval Task record ID from the workflow trigger |
Outputs
| Output | Description |
|---|---|
result | Workflow result: advanced, approved, or rejected |
nextStage | Name of the next approval stage, when the workflow advances |
Script
const CONFIG = {
parent: {
fields: {
status: "Status",
currentApprovalStage: "Current Approval Stage",
completed: "Completed",
lastUpdated: "Last Updated",
},
statuses: {
inReview: "In Review",
approved: "Approved",
rejected: "Rejected",
},
},
child: {
type: "Approval Task",
fields: {
approval: "Approval",
sequence: "Seq",
approvalStage: "Approval Stage",
department: "Department",
},
statuses: {
waiting: "Waiting",
pending: "Pending Approval",
approved: "Approved",
rejected: "Rejected",
},
},
};
if (!input.recordId) {
throw new Error("No Approval Task record ID provided.");
}
const currentTask = await anydb.getRecordById(input.recordId);
if (!currentTask) {
throw new Error(`Approval Task not found: ${input.recordId}`);
}
const taskValues = currentTask.cellValues || {};
const currentApproval = taskValues[CONFIG.child.fields.approval];
const currentSeq = Number(taskValues[CONFIG.child.fields.sequence] || 0);
const parentDonationId = currentTask.meta.parent;
if (!parentDonationId) {
throw new Error("Approval Task does not have a parent Donation.");
}
// Only act on terminal approval states
if (
currentApproval !== CONFIG.child.statuses.approved &&
currentApproval !== CONFIG.child.statuses.rejected
) {
output.text(`No action needed. Approval status is: ${currentApproval}`);
return;
}
const now = Math.floor(Date.now() / 1000);
// If rejected, stop the parent workflow
if (currentApproval === CONFIG.child.statuses.rejected) {
await anydb.updateRecord({
adoid: parentDonationId,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.rejected,
[CONFIG.parent.fields.currentApprovalStage]: "Rejected",
[CONFIG.parent.fields.lastUpdated]: now,
[CONFIG.parent.fields.completed]: now,
},
});
output.set("result", "rejected");
output.text("Approval rejected. Parent donation marked as Rejected.");
return;
}
// Current task was approved. Find next waiting task.
const approvalTasks = await anydb.getChildren(parentDonationId, {
typename: CONFIG.child.type,
});
const nextTask = approvalTasks
.filter((task) => {
const values = task.cellValues || {};
const seq = Number(values[CONFIG.child.fields.sequence] || 0);
const approval = values[CONFIG.child.fields.approval];
return (
seq > currentSeq &&
approval === CONFIG.child.statuses.waiting
);
})
.sort((a, b) => {
const aSeq = Number((a.cellValues || {})[CONFIG.child.fields.sequence] || 0);
const bSeq = Number((b.cellValues || {})[CONFIG.child.fields.sequence] || 0);
return aSeq - bSeq;
})[0];
if (nextTask) {
const nextValues = nextTask.cellValues || {};
const nextStage = nextValues[CONFIG.child.fields.approvalStage];
await anydb.updateRecord({
adoid: nextTask.id,
cellValues: {
[CONFIG.child.fields.approval]: CONFIG.child.statuses.pending,
},
});
await anydb.updateRecord({
adoid: parentDonationId,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.inReview,
[CONFIG.parent.fields.currentApprovalStage]: nextStage,
[CONFIG.parent.fields.lastUpdated]: now,
},
});
output.set("result", "advanced");
output.set("nextStage", nextStage);
output.text(`Moved to next approval stage: ${nextStage}`);
return;
}
// No next waiting task. Approval chain is complete.
await anydb.updateRecord({
adoid: parentDonationId,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.approved,
[CONFIG.parent.fields.currentApprovalStage]: "Completed",
[CONFIG.parent.fields.lastUpdated]: now,
[CONFIG.parent.fields.completed]: now,
},
});
output.set("result", "approved");
output.text("All approval steps completed. Parent donation marked as Approved.");