Skip to main content

activateProcess

activateProcess turns a Draft process into an Active one. Only an active process can be resolved for a purchase order โ€” resolveProcessForPO filters strictly on status: "Active" โ€” so this is a mandatory step between defining a workflow and submitting POs against it. It refuses to activate a process twice, and it refuses to activate a process that has no steps yet. Only Manager/Admin users can call it.

note

Package import The tutorial source imports the framework as "lesan" โ€” in this repo that alias maps to the local framework source (deno.json โ†’ ../../src/mod.ts). In your own app import from @hemedani/lesan (npm/Bun) or jsr:@hemedani/lesan (Deno). @lib and @model are the tutorial's aliases for utils/ and models/.

The validator (activateProcess.val.ts)โ€‹

Just the _id of the process plus the activeRoleId mixin. The get is a depth-1 projection of the process schema.

import { object, objectIdValidation } from "lesan";
import { selectStruct } from "../../../mod.ts";
import { activeRoleMixin } from "@lib";

export const activateProcessValidator = () => {
return object({
set: object({
...activeRoleMixin,
_id: objectIdValidation,
}),
get: selectStruct("process", 1),
});
};

The implementation (activateProcess.fn.ts)โ€‹

Three guards, then one update:

  1. Does it exist? process.findOne with projection: { _id: 1, status: 1, steps: { _id: 1 } }; if nothing comes back โ†’ throwError("process not found").
  2. Is it already active? If status === "Active" โ†’ throwError("process is already active").
  3. Does it have steps? It counts processStep documents whose embedded "process._id" matches, using $count in an aggregation. Zero steps โ†’ throwError("cannot activate a process without steps").

Only then does it set status: "Active" and isActive: true in one atomic findOneAndUpdate, returning the get projection.

import { type ActFn, ObjectId } from "lesan";
import { process, processStep } from "../../../mod.ts";
import { throwError } from "@lib";

export const activateProcessFn: ActFn = async (body) => {
const {
set: { _id },
get,
} = body.details;

const processId = new ObjectId(_id as string);

const foundedProcess = await process.findOne({
filters: { _id: processId },
projection: { _id: 1, status: 1, steps: { _id: 1 } },
});

!foundedProcess && throwError("process not found");

if (foundedProcess!.status === "Active") {
throwError("process is already active");
}

const stepsCount = await processStep
.aggregation({
pipeline: [
{ $match: { "process._id": processId } },
{ $count: "count" },
],
})
.toArray();

const count = stepsCount[0]?.count || 0;
if (count === 0) {
throwError("cannot activate a process without steps");
}

return await process.findOneAndUpdate({
filter: { _id: processId },
update: { $set: { status: "Active", isActive: true } },
projection: get,
});
};

In the workflowโ€‹

The third step of the workflow chapter: after addProcess and addProcessStep. From here on, purchase orders created with add or submitted with submit will resolve to this process. Note the step-count guard โ€” that's why you must add steps before activating.

Links: overview, process model, addProcess, addProcessStep, resolveProcess.

Run itโ€‹

curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: $TOKEN" \
-d '{
"model": "process",
"act": "activateProcess",
"details": {
"set": {
"activeRoleId": "ghost-role",
"_id": "<processId>"
},
"get": {
"_id": 1,
"name": 1,
"status": 1,
"isActive": 1
}
}
}'

Expect { "body": { "status": "Active", "isActive": true, ... }, "success": true }.

Errors & fixesโ€‹

ErrorMeaningFix
process not foundNo process exists with that _idVerify the _id via getProcesses
process is already activeThe process is already ActiveIt's a no-op; just proceed with POs
cannot activate a process without stepsThe process has no processStep documentsAdd at least one step with addProcessStep first

Plus the shared auth-chain errors from the addProcess table. A Manager/Admin role is required (You cant do this otherwise).