Skip to main content

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:

  1. Read the triggered Approval Task from input.recordId
  2. Check whether the task was Approved or Rejected
  3. If rejected:
    • Set the parent record status to Rejected
    • Set the current approval stage to Rejected
    • Set the completed timestamp
  4. 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
  5. If no next task exists:
    • Set the parent record status to Approved
    • Set the current approval stage to Completed
    • Set the completed timestamp

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

KeyDescriptionExample
parent.fields.statusParent record status fieldStatus
parent.fields.currentApprovalStageTracks current approval stageCurrent Approval Stage
parent.fields.completedStores completion timestampCompleted
parent.fields.lastUpdatedStores last update timestampLast Updated
parent.statuses.inReviewStatus while approval is still activeIn Review
parent.statuses.approvedStatus when all approval steps are completeApproved
parent.statuses.rejectedStatus when any approval step is rejectedRejected
child.typeChild record type for approval stepsApproval Task
child.fields.approvalField storing approval stateApproval
child.fields.sequenceField storing approval step orderSeq
child.fields.approvalStageField storing approval step nameApproval Stage
child.fields.departmentField storing assigned departmentDepartment
child.statuses.waitingFuture approval step not yet activeWaiting
child.statuses.pendingActive approval stepPending Approval
child.statuses.approvedCompleted approval stepApproved
child.statuses.rejectedRejected approval stepRejected

Inputs

InputRequiredDescription
recordIdYesApproval Task record ID from the workflow trigger

Outputs

OutputDescription
resultWorkflow result: advanced, approved, or rejected
nextStageName 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.");