109 lines
3.6 KiB
TypeScript
109 lines
3.6 KiB
TypeScript
// Shared pure transformations used by the actual CRM core AND its approval
|
|
// inspector. No database, network, permission or Root authority is established.
|
|
import { actorRef } from "@conusai-kit/business"
|
|
import { forbidden, type ProjectScope } from "@conusai-kit/core"
|
|
import { type ContactView, SUBJECT_SCHEME } from "../contract/index.js"
|
|
import type { CrmService } from "./ports.js"
|
|
|
|
export interface PartyActionInput {
|
|
kind: "person" | "organization"
|
|
displayName: string
|
|
legalName?: string | undefined
|
|
country: string
|
|
linkSelf?: boolean | undefined
|
|
requestId: string
|
|
}
|
|
export interface ContactActionInput {
|
|
partyId: string
|
|
kind: "email" | "phone" | "web"
|
|
value: string
|
|
purposes: ContactView["purposes"]
|
|
isPrimary: boolean
|
|
requestId: string
|
|
}
|
|
export const requestKey = (scope: Pick<ProjectScope, "projectId">, requestId: string): string =>
|
|
`app-project:${scope.projectId}:${requestId}`
|
|
export const dayOf = (at: Date): string => at.toISOString().slice(0, 10)
|
|
export function partyActionData(
|
|
scope: ProjectScope,
|
|
bindingId: string,
|
|
input: PartyActionInput,
|
|
): Parameters<CrmService["createParty"]>[0] {
|
|
const identifiers: { scheme: string; value: string }[] = []
|
|
if (input.linkSelf) {
|
|
if (input.kind !== "person") throw forbidden("only a person can be linked to a member")
|
|
if (scope.actor.system) throw forbidden("a system actor is nobody's party")
|
|
identifiers.push({ scheme: SUBJECT_SCHEME, value: actorRef(bindingId, scope.actor.userId) })
|
|
}
|
|
const legalName = input.legalName?.trim()
|
|
return {
|
|
kind: input.kind,
|
|
displayName: input.displayName.trim(),
|
|
...(legalName ? { legalName } : {}),
|
|
country: input.country,
|
|
identifiers,
|
|
key: requestKey(scope, input.requestId),
|
|
}
|
|
}
|
|
export function contactActionData(
|
|
scope: ProjectScope,
|
|
input: ContactActionInput,
|
|
now: Date,
|
|
): Parameters<CrmService["addContact"]>[0] {
|
|
return {
|
|
partyId: input.partyId,
|
|
kind: input.kind,
|
|
value: input.value.trim(),
|
|
purposes: [...new Set(input.purposes)],
|
|
isPrimary: input.isPrimary,
|
|
effectiveFrom: dayOf(now),
|
|
key: requestKey(scope, input.requestId),
|
|
}
|
|
}
|
|
export function partyRequestBody(input: Parameters<CrmService["createParty"]>[0]) {
|
|
return {
|
|
data: {
|
|
kind: input.kind,
|
|
display_name: input.displayName,
|
|
country: input.country,
|
|
...(input.legalName === undefined ? {} : { legal_name: input.legalName }),
|
|
...(input.identifiers.length === 0 ? {} : { identifiers: input.identifiers }),
|
|
},
|
|
idempotency_key: input.key,
|
|
}
|
|
}
|
|
export function contactRequestBody(input: Parameters<CrmService["addContact"]>[0]) {
|
|
return {
|
|
data: {
|
|
kind: input.kind,
|
|
value: input.value,
|
|
purposes: input.purposes,
|
|
is_primary: input.isPrimary,
|
|
effective: { from: input.effectiveFrom },
|
|
},
|
|
idempotency_key: input.key,
|
|
}
|
|
}
|
|
export function partyEncodedRequest(orgSlug: string, input: Parameters<CrmService["createParty"]>[0]) {
|
|
const body = partyRequestBody(input)
|
|
return {
|
|
service: "crm",
|
|
operation: "crm_create_party",
|
|
method: "POST" as const,
|
|
path: `/v1/orgs/${encodeURIComponent(orgSlug)}/crm/parties`,
|
|
body,
|
|
arguments: { body },
|
|
}
|
|
}
|
|
export function contactEncodedRequest(orgSlug: string, input: Parameters<CrmService["addContact"]>[0]) {
|
|
const body = contactRequestBody(input)
|
|
return {
|
|
service: "crm",
|
|
operation: "crm_add_contact_point",
|
|
method: "POST" as const,
|
|
path: `/v1/orgs/${encodeURIComponent(orgSlug)}/crm/parties/${encodeURIComponent(input.partyId)}/contacts`,
|
|
body,
|
|
arguments: { id: input.partyId, body },
|
|
}
|
|
}
|