// The procedure contracts of the inventory module: schemas and names, no handler and no port. // // The module is a client of the platform's Inventory service (B-wave contract ยง3.14): the rows live // there, in the deployment's organization, and every procedure here is one service call. A read // reads; a change is an `effect` (it calls an external service and writes nothing of this app's own). // Stock is reserved for the project the procedure runs in: the service holds it against the // project's reference, and a project lists and releases its own reservations only. import { procedure } from "@conusai-kit/core" import { z } from "zod" import { INVENTORY_PERMISSIONS } from "./constants.js" const inProject = { org: z.string(), workspace: z.string(), project: z.string() } const id = z.string().uuid() /** What a form generates once per submission, so a retried request reserves once. */ const requestId = z.string().uuid() const cursor = { after: id.optional(), limit: z.number().int().min(1).max(200).optional() } export const itemViewSchema = z.object({ itemId: z.string(), sku: z.string(), name: z.string(), unit: z.string(), description: z.string().nullable(), /** An archived item takes no reservation. */ active: z.boolean(), }) export type ItemView = z.output export const stockLevelViewSchema = z.object({ locationId: z.string(), onHand: z.number().int(), reserved: z.number().int(), available: z.number().int(), }) export type StockLevelView = z.output export const availabilityViewSchema = z.object({ itemId: z.string(), unit: z.string(), onHand: z.number().int(), reserved: z.number().int(), available: z.number().int(), /** Every location with a balance, in the service's location order. */ locations: z.array(stockLevelViewSchema), }) export type AvailabilityView = z.output export const reservationViewSchema = z.object({ reservationId: z.string(), itemId: z.string(), locationId: z.string(), quantity: z.number().int(), outstanding: z.number().int(), released: z.number().int(), issued: z.number().int(), status: z.enum(["open", "closed"]), createdAt: z.string(), }) export type ReservationView = z.output /** A page of a list: `after` is what the next page is asked with, absent on the last. */ const pageOf = (rows: T) => z.object({ rows: z.array(rows), after: z.string().optional() }) export const itemList = procedure("inventory.item.list") .kind("read") .scope("project") .permission(INVENTORY_PERMISSIONS.read) .input(z.object({ ...inProject, q: z.string().max(200).optional(), ...cursor })) .output(pageOf(itemViewSchema)) .contract() export const itemCreate = procedure("inventory.item.create") .kind("effect") .scope("project") .permission(INVENTORY_PERMISSIONS.write) .input( z.object({ ...inProject, sku: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/), name: z.string().trim().min(1).max(200), unit: z.string().regex(/^[a-z][a-z0-9_]{0,15}$/), description: z.string().max(1000).optional(), }), ) .output(itemViewSchema) .contract() export const itemAvailability = procedure("inventory.item.availability") .kind("read") .scope("project") .permission(INVENTORY_PERMISSIONS.read) .input(z.object({ ...inProject, itemId: id })) .output(z.object({ item: itemViewSchema, availability: availabilityViewSchema })) .contract() export const reservationList = procedure("inventory.reservation.list") .kind("read") .scope("project") .permission(INVENTORY_PERMISSIONS.read) .input(z.object({ ...inProject, status: z.enum(["open", "closed"]).optional(), ...cursor })) .output(pageOf(reservationViewSchema)) .contract() export const stockReserve = procedure("inventory.stock.reserve") .kind("effect") .scope("project") .permission(INVENTORY_PERMISSIONS.reserve) .input( z.object({ ...inProject, itemId: id, locationId: id, quantity: z.number().int().min(1).max(1_000_000_000), requestId, }), ) .output(reservationViewSchema) .contract() export const stockRelease = procedure("inventory.stock.release") .kind("effect") .scope("project") .permission(INVENTORY_PERMISSIONS.reserve) .input( z.object({ ...inProject, reservationId: id, /** Everything still held when absent. */ quantity: z.number().int().min(1).max(1_000_000_000).optional(), requestId, }), ) .output(reservationViewSchema) .contract()