@@ -0,0 +1,26 @@
|
||||
// The module's names, in a file with no import: a browser bundle can take them without the schemas.
|
||||
|
||||
/** The platform service this module is a client of (B-wave contract §3.14). */
|
||||
export const CRM_SERVICE_NAME = "crm"
|
||||
|
||||
/** The app permissions of the module. */
|
||||
export const CRM_PERMISSIONS = {
|
||||
read: "crm.party.read",
|
||||
write: "crm.party.write",
|
||||
} as const
|
||||
|
||||
/**
|
||||
* The identifier scheme that links a party to one of this app's people: its value is the person's
|
||||
* partition-bound member reference (`actorRef` of @conusai-kit/business), which an export or an erasure of that
|
||||
* person through the platform reaches (B-wave contract §3.8).
|
||||
*/
|
||||
export const SUBJECT_SCHEME = "conusai.subject"
|
||||
|
||||
/** The contact kinds the module's form adds; the service keeps postal addresses and Peppol endpoints too. */
|
||||
export const CONTACT_KINDS = ["email", "phone", "web"] as const
|
||||
|
||||
/** What a contact point is for, as the service names it. */
|
||||
export const CONTACT_PURPOSES = ["billing", "delivery", "invoice_email", "site"] as const
|
||||
|
||||
/** The most rows one page of a list asks the service for. */
|
||||
export const PAGE_LIMIT = 50
|
||||
@@ -0,0 +1,21 @@
|
||||
// The public API of the crm module: what a page, another module and the OpenAPI document may import.
|
||||
// Nothing here reaches core/, adapters/ or a service client.
|
||||
|
||||
export {
|
||||
CONTACT_KINDS,
|
||||
CONTACT_PURPOSES,
|
||||
CRM_PERMISSIONS,
|
||||
CRM_SERVICE_NAME,
|
||||
PAGE_LIMIT,
|
||||
SUBJECT_SCHEME,
|
||||
} from "./constants.js"
|
||||
export {
|
||||
type ContactView,
|
||||
contactAdd,
|
||||
contactViewSchema,
|
||||
type PartyView,
|
||||
partyCreate,
|
||||
partyGet,
|
||||
partyList,
|
||||
partyViewSchema,
|
||||
} from "./procedures.js"
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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()
|
||||
Reference in New Issue
Block a user