// The procedure contracts of the crm module: schemas and names, no handler and no port. // // The module is a client of the platform's CRM service (B-wave contract ยง3.14): parties and their // contact points live there, in the deployment's organization, and every procedure is one or two // service calls. A read reads; a change is an `effect` (it calls an external service and writes // nothing of this app's own). import { procedure } from "@conusai-kit/core" import { z } from "zod" import { CONTACT_KINDS, CONTACT_PURPOSES, CRM_PERMISSIONS } from "./constants.js" const inProject = { org: z.string(), workspace: z.string(), project: z.string() } /** What a form generates once per submission, so a retried request creates once. */ const requestId = z.string().uuid() const partyId = z.string().min(1).max(64) export const partyViewSchema = z.object({ partyId: z.string(), kind: z.enum(["person", "organization"]), displayName: z.string(), legalName: z.string().nullable(), /** ISO 3166-1 alpha-2. */ country: z.string(), status: z.enum(["active", "archived", "merged"]), /** Whether an identifier links the party to one of this app's people. */ linked: z.boolean(), }) export type PartyView = z.output export const contactViewSchema = z.object({ contactId: z.string(), kind: z.enum(["postal", "email", "phone", "web", "peppol_endpoint"]), /** The value as a person reads it; empty once the service erased it. */ value: z.string(), purposes: z.array(z.enum(CONTACT_PURPOSES)), isPrimary: z.boolean(), erased: z.boolean(), }) export type ContactView = z.output export const partyList = procedure("crm.party.list") .kind("read") .scope("project") .permission(CRM_PERMISSIONS.read) .input( z.object({ ...inProject, q: z.string().max(200).optional(), after: z.string().max(64).optional(), limit: z.number().int().min(1).max(200).optional(), }), ) .output(z.object({ rows: z.array(partyViewSchema), after: z.string().optional() })) .contract() export const partyGet = procedure("crm.party.get") .kind("read") .scope("project") .permission(CRM_PERMISSIONS.read) .input(z.object({ ...inProject, partyId })) .output(z.object({ party: partyViewSchema, contacts: z.array(contactViewSchema) })) .contract() export const partyCreate = procedure("crm.party.create") .kind("effect") .tool({ approval: "human" }) .scope("project") .permission(CRM_PERMISSIONS.write) .input( z.object({ ...inProject, kind: z.enum(["person", "organization"]), displayName: z.string().trim().min(1).max(300), legalName: z.string().trim().max(300).optional(), country: z.string().regex(/^[A-Z]{2}$/), /** A person who is the member creating it: the party is linked to them (an export or erasure of them reaches it). */ linkSelf: z.boolean().optional(), requestId, }), ) .output(partyViewSchema) .contract() export const contactAdd = procedure("crm.contact.add") .kind("effect") .tool({ approval: "human" }) .scope("project") .permission(CRM_PERMISSIONS.write) .input( z.object({ ...inProject, partyId, kind: z.enum(CONTACT_KINDS), value: z.string().trim().min(1).max(256), purposes: z.array(z.enum(CONTACT_PURPOSES)).max(CONTACT_PURPOSES.length), isPrimary: z.boolean(), requestId, }), ) .output(contactViewSchema) .contract()