Skip to main content

getStores

Lists stores, optionally filtered by their owning unitId, sorted alphabetically by name. Open to every authenticated role. This is the store picker used by the inventory acts.

note

Import alias The code below imports the framework as lesan โ€” that's the path alias this repo's app uses (see Project Layout). In your own project, import from @hemedani/lesan (npm/Bun) or jsr:@hemedani/lesan (Deno) instead.

Registration (mod.ts)โ€‹

import { grantAccess, setTokens, setUser } from "@lib";
import { coreApp } from "../../../mod.ts";
import { getStoresFn } from "./getStores.fn.ts";
import { getStoresValidator } from "./getStores.val.ts";

export const getStoresSetup = () =>
coreApp.acts.setAct({
schema: "store",
actName: "getStores",
preAct: [setTokens, setUser, grantAccess([{ roles: ["Manager", "Admin", "OrgHead", "UnitHead", "Employee", "Ordinary"] }])],
validator: getStoresValidator(),
fn: getStoresFn,
});

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

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

export const getStoresValidator = () => {
return object({
set: object({
...activeRoleMixin,
unitId: optional(string()),
}),
get: selectStruct("store", 2),
});
};
  • unitId is an optional plain string โ€” omit it for all stores, or pass a unit id for that unit's stores only.
  • get is depth 2, so the unit relation snapshot is projectable.

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

import { type ActFn, type Document, ObjectId } from "lesan";
import { store } from "../../../mod.ts";

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

const filters: Document = {};
unitId && (filters["unit._id"] = new ObjectId(unitId as string));

return await store
.aggregation({
pipeline: [
...(Object.keys(filters).length > 0 ? [{ $match: filters }] : []),
{ $sort: { name: 1 } },
] as Document[],
projection: get,
})
.toArray();
};
  1. If unitId was sent, the filter targets "unit._id" โ€” the dotted path into the embedded unit snapshot on each store.
  2. $match is prepended only when a filter exists; $sort: { name: 1 } orders results.
  3. .toArray() returns the list with the client's get projection applied.

In the workflowโ€‹

getStores feeds the inventory UI: pick a store, then addStock/getInventories/getStockLevel/transferStock (see the Inventory chapter) take a storeId. The unitId filter pairs with getUnits to show "the warehouses of this department".

Run itโ€‹

# All stores
curl http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"model": "store",
"act": "getStores",
"details": {
"set": { "activeRoleId": "ghost-role" },
"get": { "_id": 1, "name": 1, "code": 1 }
}
}'

# Stores of one unit
curl http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"model": "store",
"act": "getStores",
"details": {
"set": { "activeRoleId": "ghost-role", "unitId": "<unitId>" },
"get": { "_id": 1, "name": 1, "unit": { "_id": 1, "name": 1 } }
}
}'

Errors & fixesโ€‹

ErrorMeaningFix
activeRoleId is requiredNo activeRoleId in set.Add it (ghost: any string, e.g. "ghost-role").
Active role not foundactiveRoleId isn't one of the user's roles.Pass a real roleId.
You cant do thisActive role not in the allowed list.Use an allowed role or the ghost.
Invalid or expired token / you should send your id with token key in req headerAuth header problem.Send token: <jwt>; re-login if expired.
superstruct "expected a string"unitId wasn't a string.Send the id as a string.

An unmatched unitId returns [], not an error.