// 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, 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[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[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[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[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[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[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 }, } }