102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
// 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<typeof partyViewSchema>
|
|
|
|
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<typeof contactViewSchema>
|
|
|
|
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()
|