Skip to main content

ProcessStep

processStep is one step inside a process workflow. Each step has a stepType (Approval, Review, Notification, Action, Delivery, Receipt, Payment), an order number, and assignee groups with AND/OR logic: the step's groupsOperator combines groups, while each group's internal operator combines its unitIds.

// models/processStep.ts (definition, trimmed of the doc comment)
import { coreApp } from "../mod.ts";
import {
array, boolean, coerce, defaulted, enums, number, object, optional,
type RelationDataType, type RelationSortOrderType, string,
} from "lesan";
import { createUpdateAt } from "@lib";
import { process_excludes } from "./excludes.ts";

export const group_operator_array = ["AND", "OR"];
export const group_operator_emums = enums(group_operator_array);

export const step_type_array = [
"Approval", "Review", "Notification", "Action", "Delivery", "Receipt", "Payment",
];
export const step_type_emums = enums(step_type_array);

export const assigneeGroup_pure = {
operator: coerce(group_operator_emums, enums(group_operator_array), (value) => value as typeof group_operator_array[number]),
unitIds: array(string()),
};

export const processStep_pure = {
name: string(),
description: optional(string()),
stepType: defaulted(
coerce(step_type_emums, string(), (value) => value as typeof step_type_array[number]),
"Approval",
),
order: number(),
required: defaulted(boolean(), true),
groupsOperator: coerce(group_operator_emums, enums(group_operator_array), (value) => value as typeof group_operator_array[number]),
assigneeGroups: defaulted(array(object(assigneeGroup_pure)), []),
...createUpdateAt,
};

export const processStep_relations = {
process: {
schemaName: "process",
type: "single" as RelationDataType,
optional: false,
excludes: process_excludes,
relatedRelations: {
steps: {
type: "multiple" as RelationDataType,
limit: 50,
sort: { field: "order", order: "asc" as RelationSortOrderType },
},
},
},
};

Pure fieldsโ€‹

FieldTypeNotes
namestring()e.g. Department Head Approval
descriptionoptional(string())
stepTypecoerce(enums([...7 types]), "Approval")
ordernumber()position in the workflow; sorted ascending in the process relation
requireddefaulted(boolean(), true)non-required steps are skipped if unresolved
groupsOperatorcoerce(enums([AND, OR]))combines the groups across
assigneeGroupsdefaulted(array({operator, unitIds}), [])each group combines its units OR/AND
createdAt / updatedAtspread from createUpdateAt

The AND/OR semanticsโ€‹

The approval engine reads this shape as a nested boolean expression:

(unitIds[0] OR unitIds[1] ...) [groupsOperator] (unitIds[0] OR unitIds[1] ...) ...

A single group { operator: "OR", unitIds: [purchasing, warehouse] } with groupsOperator: "AND" means: purchasing OR warehouse must act. Two groups with groupsOperator: "AND" means both groups must resolve โ€” e.g. one finance unit AND (one of two warehouse units). The full evaluation is explained on the step-evaluator page.

Relationsโ€‹

RelationTargetTypeBack-reference
processprocesssingle (required)process.steps (sorted by order asc, limit 50)

Note the back-reference process.steps sorts on field: "order" ascending โ€” steps come back in workflow order, which is exactly what the submit/evaluate acts need.

Factoryโ€‹

export const processSteps = () =>
coreApp.odm.newModel("processStep", processStep_pure, processStep_relations);

In the workflowโ€‹

Run itโ€‹

curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"service": "main",
"model": "processStep",
"act": "getProcessSteps",
"details": {
"set": { "query": { "process._id": "<process_id>" }, "page": 1 },
"get": { "name": true, "order": true, "stepType": true, "assigneeGroups": true }
}
}'

Errors & fixesโ€‹

ErrorCauseFix
processStep not foundact given an unknown _idpass a real _id
note

Runtime On npm/Bun import the framework from @hemedani/lesan; on Deno from jsr:@hemedani/lesan. The repo app itself uses the lesan path alias.