Skip to main content

Create Approval Tasks from Form Submission (Part 1)

This script creates approval task child records under a parent record based on business rules such as type, category, and value.

Use Case

Use this when you want a form submission or record creation to automatically:

  • Route approvals based on conditions
  • Create multi-step approval workflows
  • Skip approvals for certain cases (small value, pass-through, etc.)

Examples:

  • Expense approvals based on amount and department
  • Purchase requests routed through finance and leadership
  • Vendor onboarding approvals based on category
  • Donations or grants requiring multi-level approval

What It Does

On trigger, the script will:

  1. Read the triggering parent record from input.recordId
  2. Evaluate whether approval is required
  3. If not required:
    • Mark the record as Approved
    • Skip all approval steps
  4. If required:
    • Determine the approval path based on record data
    • Create approval task child records
    • Set the first task to Pending Approval
    • Set remaining tasks to Waiting
    • Set the parent record to In Review
    • Set the current approval stage

Key Concept

Instead of embedding approval logic inside forms or workflows, this approach:

  • Models each approval step as a child record
  • Uses sequence-based progression
  • Keeps a clear audit trail of decisions
  • Allows workflows to scale without becoming brittle

This structure works across domains where approvals depend on multiple conditions and stakeholders.

Configuration

KeyDescriptionExample
parent.fields.donationTypeField storing the type of requestDonation Type
parent.fields.donationCategoryField storing category used for routingDonation Category
parent.fields.donationValueNumeric field used for conditional logicDonation Value
parent.fields.statusParent record status fieldStatus
parent.fields.currentApprovalStageTracks current approval stageCurrent Approval Stage
parent.statuses.inReviewStatus when approvals are in progressIn Review
parent.statuses.approvedStatus when auto-approved or completedApproved
child.typeChild record type for approval stepsApproval Task
child.fields.approvalField storing approval stateApproval
child.fields.sequenceField storing step orderSeq
child.fields.approvalStageField storing step nameApproval Stage
child.fields.departmentField storing assigned departmentDepartment
child.statuses.pendingActive approval stepPending Approval
child.statuses.waitingFuture steps not yet activeWaiting

Inputs

InputRequiredDescription
recordIdYesParent record ID from the workflow trigger

Outputs

OutputDescription
createdCountNumber of approval task records created

Script

const CONFIG = {
parent: {
fields: {
donationType: "Donation Type",
donationCategory: "Donation Category",
donationValue: "Donation Value",
status: "Status",
currentApprovalStage: "Current Approval Stage",
},
statuses: {
inReview: "In Review",
approved: "Approved",
},
},

child: {
type: "Approval Task",
fields: {
approval: "Approval",
sequence: "Seq",
approvalStage: "Approval Stage",
department: "Department",
},
statuses: {
waiting: "Waiting",
pending: "Pending Approval",
},
},
};

if (!input.recordId) {
throw new Error("No Donation record ID provided.");
}

const donation = await anydb.getRecordById(input.recordId);

if (!donation) {
throw new Error(`Donation not found: ${input.recordId}`);
}

const values = donation.cellValues || {};

const donationType = String(values[CONFIG.parent.fields.donationType] || "").toLowerCase();
const donationCategory = String(values[CONFIG.parent.fields.donationCategory] || "").toLowerCase();
const donationValue = Number(values[CONFIG.parent.fields.donationValue] || 0);

function getDepartment(category) {
const map = {
media: "Media & Ed Tech",
supplies: "Curriculum & Instruction",
tech: "IT Office",
pe: "PE / Health / Dance",
other: "Safety / Risk",
fac: "School Facilities",
};
return map[category] || "Department Approval";
}

function addStep(steps, stage, department) {
steps.push({
sequence: steps.length + 1,
stage,
department,
});
}

const approvalSteps = [];

// Gate checks
const approvalNotRequired =
donationType === "pass-through" ||
donationType === "vehicle" ||
donationValue < 500;

if (approvalNotRequired) {
await anydb.updateRecord({
adoid: donation.id,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.approved,
[CONFIG.parent.fields.currentApprovalStage]: "No approval required",
},
});

output.set("createdCount", 0);
return;
}

// Routing
if (donationType === "cash") {
addStep(approvalSteps, "Finance Approval", "Finance");
addStep(approvalSteps, "Division of Schools Approval", "Division of Schools");
addStep(approvalSteps, "Board Acceptance", "Board of Education");
} else if (donationType === "non-cash") {
const dept = getDepartment(donationCategory);

addStep(approvalSteps, "Department Approval", dept);
addStep(approvalSteps, "Division of Schools Approval", "Division of Schools");
addStep(approvalSteps, "Board Acceptance", "Board of Education");
}

// Create tasks
let createdCount = 0;

for (const step of approvalSteps) {
await anydb.yield();

const status =
step.sequence === 1
? CONFIG.child.statuses.pending
: CONFIG.child.statuses.waiting;

await anydb.createRecord({
name: `${step.sequence}. ${step.stage}`,
parentid: donation.id,
typename: CONFIG.child.type,
cellValues: {
[CONFIG.child.fields.approval]: status,
[CONFIG.child.fields.sequence]: step.sequence,
[CONFIG.child.fields.approvalStage]: step.stage,
[CONFIG.child.fields.department]: step.department,
},
});

createdCount++;
}

// Update parent
await anydb.updateRecord({
adoid: donation.id,
cellValues: {
[CONFIG.parent.fields.status]: CONFIG.parent.statuses.inReview,
[CONFIG.parent.fields.currentApprovalStage]: approvalSteps[0].stage,
},
});

output.set("createdCount", createdCount);