@aglyn/plugins-crm 1.0.0-beta.219 → 1.0.0-beta.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +14 -15
- package/src/lib/components/company-import-drawer.d.ts +1 -1
- package/src/lib/components/contact-associations-card.js +4 -3
- package/src/lib/components/contact-associations-card.js.map +1 -1
- package/src/lib/components/crm-booking-zone.d.ts +8 -2
- package/src/lib/components/crm-booking-zone.js.map +1 -1
- package/src/lib/components/crm-glance-card.js +2 -1
- package/src/lib/components/crm-glance-card.js.map +1 -1
- package/src/lib/components/deal-products-card.js +27 -16
- package/src/lib/components/deal-products-card.js.map +1 -1
- package/src/lib/components/lead-campaigns-card.d.ts +4 -4
- package/src/lib/components/lead-campaigns-card.js +9 -8
- package/src/lib/components/lead-campaigns-card.js.map +1 -1
- package/src/lib/components/lead-properties-card.js +4 -0
- package/src/lib/components/lead-properties-card.js.map +1 -1
- package/src/lib/components/lead-status-chip.js +3 -1
- package/src/lib/components/lead-status-chip.js.map +1 -1
- package/src/lib/components/leads-bulk-bar.js +7 -9
- package/src/lib/components/leads-bulk-bar.js.map +1 -1
- package/src/lib/components/leads-section.js +5 -1
- package/src/lib/components/leads-section.js.map +1 -1
- package/src/lib/components/new-lead-drawer.js +5 -4
- package/src/lib/components/new-lead-drawer.js.map +1 -1
- package/src/lib/components/reports/activity-card.js +4 -3
- package/src/lib/components/reports/activity-card.js.map +1 -1
- package/src/lib/components/reports/closed-deals-card.js +4 -3
- package/src/lib/components/reports/closed-deals-card.js.map +1 -1
- package/src/lib/components/reports/contacts-mix-card.js +3 -2
- package/src/lib/components/reports/contacts-mix-card.js.map +1 -1
- package/src/lib/components/reports/contacts-trend-card.js +2 -1
- package/src/lib/components/reports/contacts-trend-card.js.map +1 -1
- package/src/lib/components/reports/forecast-card.js +3 -2
- package/src/lib/components/reports/forecast-card.js.map +1 -1
- package/src/lib/components/reports/lead-funnel-card.js +8 -6
- package/src/lib/components/reports/lead-funnel-card.js.map +1 -1
- package/src/lib/components/reports/pipeline-card.js +4 -3
- package/src/lib/components/reports/pipeline-card.js.map +1 -1
- package/src/lib/components/reports/report-format.d.ts +1 -1
- package/src/lib/components/reports/report-format.js.map +1 -1
- package/src/lib/components/reports/report-scope.d.ts +1 -1
- package/src/lib/components/reports/report-scope.js.map +1 -1
- package/src/lib/components/reports/source-conversion-card.js +2 -1
- package/src/lib/components/reports/source-conversion-card.js.map +1 -1
- package/src/lib/components/reports/tasks-card.js +2 -1
- package/src/lib/components/reports/tasks-card.js.map +1 -1
- package/src/lib/components/reports/won-lost-by-owner-card.js +3 -2
- package/src/lib/components/reports/won-lost-by-owner-card.js.map +1 -1
- package/src/lib/components/reports-section.js +4 -4
- package/src/lib/components/reports-section.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +2 -0
- package/src/lib/constants/bundle-common.js +1 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +4 -0
- package/src/lib/declarations.console-server.js +12 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.server.d.ts +6 -0
- package/src/lib/declarations.server.js +51 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-crm-campaigns.d.ts +2 -2
- package/src/lib/hooks/use-crm-campaigns.js +3 -3
- package/src/lib/hooks/use-crm-campaigns.js.map +1 -1
- package/src/lib/model/campaign-filing-activity.d.ts +1 -1
- package/src/lib/model/campaign-filing-activity.js +1 -1
- package/src/lib/model/campaign-filing-activity.js.map +1 -1
- package/src/lib/model/contact-record.js +1 -1
- package/src/lib/model/contact-record.js.map +1 -1
- package/src/lib/model/crm-digest.d.ts +45 -3
- package/src/lib/model/crm-digest.js +75 -11
- package/src/lib/model/crm-digest.js.map +1 -1
- package/src/lib/model/crm-inbound.d.ts +162 -0
- package/src/lib/model/crm-inbound.js +208 -0
- package/src/lib/model/crm-inbound.js.map +1 -0
- package/src/lib/model/crm-lead-import.d.ts +1 -1
- package/src/lib/model/crm-lead-import.js +4 -4
- package/src/lib/model/crm-lead-import.js.map +1 -1
- package/src/lib/model/crm-reports.d.ts +467 -0
- package/src/lib/model/crm-reports.js +667 -0
- package/src/lib/model/crm-reports.js.map +1 -0
- package/src/lib/model/lead-filters.d.ts +4 -3
- package/src/lib/model/lead-filters.js +7 -5
- package/src/lib/model/lead-filters.js.map +1 -1
- package/src/lib/plugin.js +36 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/crm-leads.js +1 -0
- package/src/lib/server/api-v1/crm-leads.js.map +1 -1
- package/src/lib/server/api-v1/openapi.js +1 -1
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/campaign-filing-activity.d.ts +1 -1
- package/src/lib/server/campaign-filing-activity.js +7 -11
- package/src/lib/server/campaign-filing-activity.js.map +1 -1
- package/src/lib/server/capture-contact.js +19 -5
- package/src/lib/server/capture-contact.js.map +1 -1
- package/src/lib/server/contact-email-engagement.d.ts +33 -0
- package/src/lib/server/contact-email-engagement.js +146 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-update.js +3 -3
- package/src/lib/server/contact-update.js.map +1 -1
- package/src/lib/server/container-detach.d.ts +31 -0
- package/src/lib/server/container-detach.js +115 -0
- package/src/lib/server/container-detach.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +162 -0
- package/src/lib/server/crm-inbound-email.js +259 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-records-meter.d.ts +50 -0
- package/src/lib/server/crm-records-meter.js +126 -0
- package/src/lib/server/crm-records-meter.js.map +1 -0
- package/src/lib/server/daily-digest-route.d.ts +2 -8
- package/src/lib/server/daily-digest-route.js +15 -25
- package/src/lib/server/daily-digest-route.js.map +1 -1
- package/src/lib/server/inbound-address.js +5 -3
- package/src/lib/server/inbound-address.js.map +1 -1
- package/src/lib/server/inbound-route.js +7 -4
- package/src/lib/server/inbound-route.js.map +1 -1
- package/src/lib/server/lead-campaign-carry.js +3 -3
- package/src/lib/server/lead-campaign-carry.js.map +1 -1
- package/src/lib/server/lead-create.d.ts +1 -1
- package/src/lib/server/lead-create.js +10 -15
- package/src/lib/server/lead-create.js.map +1 -1
- package/src/lib/server/lead-nurturing.d.ts +23 -0
- package/src/lib/server/lead-nurturing.js +82 -0
- package/src/lib/server/lead-nurturing.js.map +1 -0
- package/src/lib/server/leads-import.js +7 -7
- package/src/lib/server/leads-import.js.map +1 -1
- package/src/lib/server/record-email-state.js +23 -0
- package/src/lib/server/record-email-state.js.map +1 -1
- package/src/lib/server/record-timeline.d.ts +12 -0
- package/src/lib/server/record-timeline.js +127 -15
- package/src/lib/server/record-timeline.js.map +1 -1
- package/src/lib/server/task-reminders-route.js +4 -2
- package/src/lib/server/task-reminders-route.js.map +1 -1
- package/src/lib/server/task-routes.d.ts +2 -0
- package/src/lib/server/task-routes.js +22 -3
- package/src/lib/server/task-routes.js.map +1 -1
- package/src/lib/usage-axes.js +8 -1
- package/src/lib/usage-axes.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/crm-leads.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `/v1/leads` (AGL-2627) — the work queue: every person a site captured,\n * with the working state a sales team keeps on them and the door that turns\n * one into a contact.\n *\n * ## A lead belongs to a site, so every request names one\n *\n * The five CRM collections sit under the org and a key reads them whole\n * (`crm-shared.ts`). A lead does not: it lives at\n * `hosts/{siteId}/leads/{id}`, private to the site that captured it, with no\n * `visibleTo` to filter on and an id that is only unique within its site —\n * the person key, so one address is one row per site. There is no org-wide\n * list to read, and `/v1/leads/{id}` alone names a row in every site the org\n * has. So `siteId` is required on every endpoint here, as a query parameter\n * (a `PATCH` or `POST` may carry it in the body instead), and a site the\n * organization does not own is refused the way `consentSiteId` is on a\n * create. That is also what keeps this resource mountable at the org level:\n * the site is a parameter, never an assumption.\n *\n * ## Newest first, and the status filter runs on the page\n *\n * A polling integration asks for what arrived since it last looked, so the\n * list is ordered by `lastSeen` descending — the one field the capture door\n * stamps on every lead, at every capture — with the id as tiebreak, and the\n * cursor carries both. `?status=` is applied to the page rather than sent to\n * Firestore, for a reason a `where` cannot answer: a lead nobody has touched\n * carries no `status` field at all and reads as `new` (`crmLeadStatus`), so\n * `where('status', '==', 'new')` would miss exactly the leads a work queue\n * exists to show. The page can come back short, which the conventions page\n * already documents; no composite index is needed and none is shipped.\n *\n * ## A lead is a record of its own (AGL-3231)\n *\n * `POST /v1/leads` creates one — Salesforce's lead, sourced outside the\n * site and entered before anyone has qualified it: the address, a name,\n * the company as text, a title, a phone, a website, an address, tags and a\n * lead source, with the working state beside them. It goes through the one\n * lead door (`addHostLead`), so a second create for one address updates the\n * lead the site holds and answers `200` rather than minting a second row;\n * it writes no contact and no company, which are the conversion's to make;\n * and it records no marketing basis, because an integration is not a box\n * the person ticked. The same profile fields are writable on a `PATCH`,\n * `null` clearing one, and every one of them is published on the lead.\n *\n * ## What a PATCH may say about a status\n *\n * The same three moves the console offers: `new`, `working`, and\n * `unqualified` with a reason. `qualified` is the converted state — a lead\n * reaches it through `POST /v1/leads/{id}/convert` and nowhere else, because\n * the status is a claim that a contact exists and the conversion is what\n * makes it true. A converted lead's status is fixed for the same reason.\n * Reopening an unqualified lead drops its reason, as the console does: a\n * lead being worked again is not \"unqualified because …\".\n */\nimport {\n checkVisitorRecordCeiling,\n CRM_LEAD_STATUSES,\n type CrmLeadFields,\n type CrmLeadProfilePatch,\n type CrmLeadStatus,\n crmLeadStatus,\n crmMemberOption,\n findOrgMember,\n LEADS_MAX_PER_HOST,\n normalizeCompanyDomain,\n normalizeContactEmail,\n crmPicklistDefaultLabel,\n judgeCrmLeadSource,\n normalizeCrmLeadProfile,\n personKey,\n readMarketingBasis,\n soloConsentGroup,\n} from '@aglyn/aglyn/server'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n addHostLead,\n apiJson,\n ApiErrors,\n decodeCursor,\n encodeCursor,\n listOrgMembers,\n listResponse,\n parseLimit,\n restampCrmListFieldsAt,\n} from '@aglyn/tenant-data-admin'\nimport {\n type ConvertHostLeadInput,\n type ConvertHostLeadRefusal,\n convertHostLead,\n} from '../convert-host-lead'\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\nimport {\n type ApiV1Context,\n claimWrite,\n orgOwnsHost,\n readJsonBody,\n requireScope,\n} from '@aglyn/tenant-data-admin/server/api-v1-kit'\nimport {\n type Clearable,\n CRM_ID_MAX,\n CRM_LABEL_MAX,\n CRM_TEXT_MAX,\n CRM_TITLE_MAX,\n crmCustomUpdate,\n crmCustomView,\n crmTimes,\n crmValidationFailed,\n isoFromMs,\n memberError,\n readCrmCustomBody,\n readOptionalText,\n readOrgLeadSourcePicklist,\n refuseUnknownKeys,\n updatePayload,\n} from './crm-shared'\n\n/**\n * The statuses a PATCH may set — every status but the converted one.\n *\n * A literal, checked against the model's union, rather than a filter over\n * `CRM_LEAD_STATUSES`: nothing in this module may dereference the model at\n * load time, for the reason `crm-shared.ts` gives — the `/v1` suites mock\n * `@aglyn/aglyn/server` as a closed world, and a module-level read of a\n * constant they never asked for throws at import in every one of them.\n */\nconst LEAD_PATCH_STATUSES = [\n 'new',\n 'working',\n 'unqualified',\n] as const satisfies readonly Exclude<CrmLeadStatus, 'qualified'>[]\n\n// ── The view ────────────────────────────────────────────────────────────────\n\n/** The lead object as published. Every writable field appears here. */\nfunction leadView(siteId: string, doc: FirebaseFirestore.DocumentSnapshot) {\n const data = (doc.data() ?? {}) as Record<string, unknown> & CrmLeadFields\n // The lead's own silo — a group of one, however the org pools its\n // contacts — which is where `addHostLead` recorded the basis.\n const consent = readMarketingBasis(data, soloConsentGroup(siteId))\n return {\n id: doc.id,\n object: 'lead',\n siteId,\n email: typeof data['email'] === 'string' ? data['email'] : null,\n name: typeof data['name'] === 'string' ? data['name'] : null,\n status: crmLeadStatus(data),\n ownerUid: data.ownerUid ?? null,\n notes: data.notes ?? null,\n unqualifiedReason: data.unqualifiedReason ?? null,\n // The lead's own profile (AGL-3231).\n company: data.company ?? null,\n jobTitle: data.jobTitle ?? null,\n phone: data.phone ?? null,\n website: data.website ?? null,\n address: data.address ?? null,\n tags: Array.isArray(data.tags) ? data.tags.map(String) : [],\n leadSource: data.leadSource ?? null,\n sources: Array.isArray(data['sources']) ? data['sources'].map(String) : [],\n submissionCount:\n typeof data['submissionCount'] === 'number' ? data['submissionCount'] : 0,\n firstSeen: isoFromMs(data['firstSeenAtMs']),\n lastSeen: isoFromMs(data['lastSeenAtMs']),\n marketingConsent: consent.basis === 'granted',\n marketingConsentAt: consent.basis === 'granted' ? isoFromMs(consent.basisAtMs) : null,\n convertedContactId: data.convertedContactId ?? null,\n convertedAt: isoFromMs(data.convertedAtMs),\n companyId: data.companyId ?? null,\n dealId: data.dealId ?? null,\n // The org's own lead fields (AGL-3272) — `{}` on a lead that carries\n // none, so a client can index it without a guard.\n custom: crmCustomView(data as FirebaseFirestore.DocumentData),\n ...crmTimes(data as FirebaseFirestore.DocumentData),\n }\n}\n\n// ── The site ────────────────────────────────────────────────────────────────\n\n/**\n * The `siteId` a request names, validated — the query parameter first, the\n * body as the fallback a `POST`/`PATCH` may use. Required, and required to\n * be one of the organization's own sites, for the reason the module header\n * gives.\n */\nfunction readLeadSite(\n ctx: ApiV1Context,\n url: URL,\n body: Record<string, unknown> = {},\n): { siteId: string } | { response: Response } {\n const refuse = (error: string) => ({\n response: crmValidationFailed(ctx, 'lead', { siteId: error }),\n })\n const raw = url.searchParams.get('siteId') ?? body.siteId\n if (raw === undefined || raw === null) {\n return refuse('Required — name the site the lead belongs to')\n }\n const siteId = String(raw).trim()\n if (!siteId) return refuse('Must name a site')\n if (!orgOwnsHost(ctx, siteId)) return refuse('No such site in this organization')\n return { siteId }\n}\n\n/**\n * The org's leads (AGL-3275). The site is still named by every caller — a\n * lead is created and listed in a site's context — but it selects by\n * `visibleTo` now rather than by the collection it lives in, so\n * {@link leadsVisibleTo} is what a LIST must use. This bare ref is for\n * `doc(id)` reads and writes, which the route authorizes for itself.\n */\nfunction leadsCollection(ctx: ApiV1Context, _siteId: string) {\n return ctx.firestore.collection('orgs').doc(ctx.orgId).collection('leads')\n}\n\n/** The same collection, narrowed to the leads one site may see. */\nfunction leadsVisibleTo(ctx: ApiV1Context, siteId: string) {\n return leadsCollection(ctx, siteId).where(\n 'visibleTo',\n 'array-contains-any',\n scopeTokensForHost(siteId),\n )\n}\n\n// ── The owner ───────────────────────────────────────────────────────────────\n\n/**\n * The owner a body names, by uid or by address, resolved to a uid on the\n * roster — or `null` to clear, or `undefined` when the body named nobody.\n *\n * `ownerEmail` is what a spreadsheet or a zap has; a uid is what the record\n * stores. The address resolves the way the CRM's own import and automation\n * steps resolve one (AGL-2614): against the roster alone, so an address the\n * project's auth knows but the organization does not names nobody. One\n * roster read, paid only when an address was given.\n */\nasync function readOwner(\n ctx: ApiV1Context,\n body: Record<string, unknown>,\n errors: Record<string, string>,\n): Promise<Clearable<string>> {\n const ownerUid = readOptionalText(body, 'ownerUid', CRM_TITLE_MAX, errors)\n const ownerEmail = readOptionalText(body, 'ownerEmail', CRM_TITLE_MAX, errors)\n if (ownerUid !== undefined && ownerEmail !== undefined) {\n errors.ownerEmail = 'Name the owner once — by ownerUid or by ownerEmail, not both'\n return undefined\n }\n if (ownerEmail === undefined) return ownerUid\n if (ownerEmail === null) return null\n const email = normalizeContactEmail(ownerEmail)\n if (!email) {\n errors.ownerEmail = 'Must be an email address'\n return undefined\n }\n const members = await listOrgMembers(ctx.orgId)\n const roster = members\n .map((member) => crmMemberOption(member as unknown as Record<string, unknown>))\n .filter((option): option is NonNullable<typeof option> => option !== null)\n const found = findOrgMember(roster, email)\n if (!found) {\n errors.ownerEmail = 'No member of this organization has this address'\n return undefined\n }\n return found.uid\n}\n\n// ── PATCH ───────────────────────────────────────────────────────────────────\n\n/** The lead's own fields, writable on a create and a PATCH alike (AGL-3231). */\nconst LEAD_PROFILE_KEYS = [\n 'company',\n 'jobTitle',\n 'phone',\n 'website',\n 'address',\n 'tags',\n 'leadSource',\n] as const\n\nconst LEAD_WRITABLE = new Set([\n 'siteId',\n 'status',\n 'ownerUid',\n 'ownerEmail',\n 'notes',\n 'unqualifiedReason',\n // The org's own lead fields (AGL-3272), judged against the definitions\n // whose object is `lead` — never read by `readLeadInput`, which knows\n // nothing about the org's definitions, but named here so a body that\n // carries one is not refused as an unknown key.\n 'custom',\n ...LEAD_PROFILE_KEYS,\n])\n\ninterface LeadInput {\n status?: Exclude<CrmLeadStatus, 'qualified'>\n notes?: Clearable<string>\n unqualifiedReason?: Clearable<string>\n /** The profile as the model read it: a key present and `null` clears. */\n profile: CrmLeadProfilePatch\n}\n\n/** The profile as an update: a cleared field is deleted, not blanked. */\nfunction profileUpdate(patch: CrmLeadProfilePatch): Record<string, unknown> {\n const update: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue\n update[key] = value === null ? FieldValue.delete() : value\n }\n return update\n}\n\nfunction readLeadInput(\n body: Record<string, unknown>,\n): { values: LeadInput } | { errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n const values: LeadInput = { profile: {} }\n refuseUnknownKeys(body, LEAD_WRITABLE, 'lead', errors)\n\n const profileBody: Record<string, unknown> = {}\n for (const key of LEAD_PROFILE_KEYS) {\n if (key in body) profileBody[key] = body[key]\n }\n const profile = normalizeCrmLeadProfile(profileBody)\n Object.assign(errors, profile.errors)\n values.profile = profile.patch\n\n if (body.status !== undefined) {\n if (body.status === 'qualified') {\n errors.status =\n 'Not settable — a lead is qualified by converting it: POST /v1/leads/{id}/convert'\n } else if (\n typeof body.status === 'string' &&\n (LEAD_PATCH_STATUSES as readonly string[]).includes(body.status)\n ) {\n values.status = body.status as LeadInput['status']\n } else {\n errors.status = `Must be one of: ${LEAD_PATCH_STATUSES.join(', ')}`\n }\n }\n const notes = readOptionalText(body, 'notes', CRM_TEXT_MAX, errors)\n if (notes !== undefined) values.notes = notes\n const reason = readOptionalText(body, 'unqualifiedReason', CRM_TEXT_MAX, errors)\n if (reason !== undefined) values.unqualifiedReason = reason\n\n return Object.keys(errors).length ? { errors } : { values }\n}\n\n/** `PATCH /v1/leads/{id}`. */\nasync function updateLead(\n request: Request,\n ctx: ApiV1Context,\n url: URL,\n leadId: string,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const parsed = readLeadInput(body)\n if ('errors' in parsed) return crmValidationFailed(ctx, 'lead', parsed.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) return site.response\n const ref = leadsCollection(ctx, site.siteId).doc(leadId)\n const snap = await ref.get()\n if (!snap.exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n const stored = (snap.data() ?? {}) as Record<string, unknown> & CrmLeadFields\n\n const { status, unqualifiedReason, profile, ...rest } = parsed.values\n if (status !== undefined && stored.convertedContactId) {\n return ApiErrors.conflict({\n message: 'This lead was converted; its status is fixed',\n code: 'lead_converted',\n headers: ctx.headers,\n })\n }\n const errors: Record<string, string> = {}\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n const current = crmLeadStatus(stored)\n const next = status ?? current\n /*\n * A reason travels with the closed state and only with it. Sent beside\n * `status: \"unqualified\"` it is required; sent on its own it changes the\n * reason of a lead that is already unqualified; sent with any other\n * status it describes a state the lead is not in.\n */\n if (next === 'unqualified') {\n const reason =\n unqualifiedReason !== undefined ? unqualifiedReason : stored.unqualifiedReason\n if (!reason) {\n errors.unqualifiedReason = 'A reason is required to mark a lead unqualified'\n }\n } else if (unqualifiedReason) {\n errors.unqualifiedReason = 'Only an unqualified lead carries a reason'\n }\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298): a restricted picklist. An active\n * value is stored as the list spells it; the value the lead already\n * holds is kept even when it has since been deactivated; anything else\n * is a 400 naming the values the list allows.\n */\n if (typeof profile.leadSource === 'string') {\n const judged = judgeCrmLeadSource(\n await readOrgLeadSourcePicklist(ctx),\n profile.leadSource,\n stored.leadSource,\n )\n if (judged.ok === false) errors.leadSource = judged.error\n else profile.leadSource = judged.value\n }\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'lead', errors)\n // The org's own lead fields (AGL-3272), judged against the definitions\n // whose object is `lead` — a contact field of the same key is a\n // different field with a possibly different type.\n const custom = await readCrmCustomBody(ctx, body, 'lead')\n if (custom && 'errors' in custom) return crmValidationFailed(ctx, 'lead', custom.errors)\n\n const update: Record<string, unknown> = {\n ...updatePayload({ ...rest, ownerUid }),\n ...profileUpdate(profile),\n // One dotted path per key, so the map is merged rather than replaced.\n ...(custom && 'values' in custom ? crmCustomUpdate(custom.values) : {}),\n }\n if (status !== undefined && status !== current) update.status = status\n if (next === 'unqualified') {\n if (unqualifiedReason !== undefined) update.unqualifiedReason = unqualifiedReason\n } else if (stored.unqualifiedReason !== undefined) {\n // Reopening drops the reason with the closed state.\n update.unqualifiedReason = FieldValue.delete()\n }\n if (Object.keys(update).length > 0) {\n await ref.update({ ...update, updatedAt: Timestamp.now() })\n // What the console's Leads list searches and filters by (AGL-3321).\n await restampCrmListFieldsAt(ref, 'leads')\n }\n return apiJson(leadView(site.siteId, await ref.get()), { headers: ctx.headers })\n}\n\n// ── Create ──────────────────────────────────────────────────────────────────\n\nconst LEAD_CREATE_STATUSES = ['new', 'working'] as const\nconst LEAD_NAME_MAX = 120\n\n/** The surface a lead created over the API names, beside `signup`, `booking`, `form:{id}`, `import` and `manual`. */\nconst LEAD_API_SOURCE = 'api'\n\n/**\n * `POST /v1/leads` (AGL-3231). Accepts an `Idempotency-Key`, scoped to the\n * site. Through the one lead door, so one address is one lead per site: a\n * second create updates and answers `200`.\n */\nasync function createLead(request: Request, ctx: ApiV1Context, url: URL): Promise<Response> {\n const body = await readJsonBody(request)\n const errors: Record<string, string> = {}\n refuseUnknownKeys(\n body,\n new Set([...LEAD_WRITABLE, 'email', 'name']),\n 'lead',\n errors,\n )\n const email = normalizeContactEmail(body.email)\n if (!email) errors.email = 'Required — an email address'\n const name = readOptionalText(body, 'name', LEAD_NAME_MAX, errors)\n const notes = readOptionalText(body, 'notes', CRM_TEXT_MAX, errors)\n let status: (typeof LEAD_CREATE_STATUSES)[number] | undefined\n if (body.status !== undefined && body.status !== null) {\n if (\n typeof body.status === 'string' &&\n (LEAD_CREATE_STATUSES as readonly string[]).includes(body.status)\n ) {\n status = body.status as (typeof LEAD_CREATE_STATUSES)[number]\n } else {\n errors.status = `Must be one of: ${LEAD_CREATE_STATUSES.join(', ')}`\n }\n }\n if (body.unqualifiedReason !== undefined) {\n errors.unqualifiedReason = 'Not settable on a create — unqualify with a PATCH'\n }\n const profileBody: Record<string, unknown> = {}\n for (const key of LEAD_PROFILE_KEYS) {\n if (key in body) profileBody[key] = body[key]\n }\n const profile = normalizeCrmLeadProfile(profileBody)\n Object.assign(errors, profile.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) {\n if (Object.keys(errors).length) {\n return crmValidationFailed(ctx, 'lead', {\n ...errors,\n siteId: 'Required — name the site the lead belongs to',\n })\n }\n return site.response\n }\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'lead', errors)\n // Judged above the claim (AGL-3272), so a refused field costs no\n // idempotency key — the shape the companies resource writes.\n const custom = await readCrmCustomBody(ctx, body, 'lead')\n if (custom && 'errors' in custom) return crmValidationFailed(ctx, 'lead', custom.errors)\n const customValues = custom && 'values' in custom ? custom.values : {}\n\n const claimed = await claimWrite(\n ctx,\n site.siteId,\n request.headers.get('Idempotency-Key'),\n 'lead-creates',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n try {\n const hostRef = ctx.firestore.collection('hosts').doc(site.siteId)\n const leadsRef = leadsCollection(ctx, site.siteId)\n // Non-null by construction: the normalizer refused every address this\n // derivation could not key.\n const leadId = personKey(email as string) as string\n const existing = await leadsRef.doc(leadId).get()\n const created = !existing.exists\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298), judged once the lead's own value\n * is known: a value outside the list is a 400 naming what it allows, a\n * lead the site already held keeps its current value, and a new lead\n * that named none starts from the list's default.\n */\n const leadSources = await readOrgLeadSourcePicklist(ctx)\n if (typeof profile.patch.leadSource === 'string') {\n const judged = judgeCrmLeadSource(\n leadSources,\n profile.patch.leadSource,\n existing.get('leadSource'),\n )\n if (judged.ok === false) {\n await claim.release()\n return crmValidationFailed(ctx, 'lead', { leadSource: judged.error })\n }\n profile.patch.leadSource = judged.value\n } else if (profile.patch.leadSource === undefined && created) {\n const fallback = crmPicklistDefaultLabel(leadSources)\n if (fallback) profile.patch.leadSource = fallback\n }\n if (created) {\n // What the SITE may see (AGL-3275), matching `addHostLead`'s own count:\n // an unscoped count would charge one site for a sibling's leads.\n const used = (await leadsVisibleTo(ctx, site.siteId).count().get()).data().count\n if (checkVisitorRecordCeiling(used, LEADS_MAX_PER_HOST).exceeded) {\n await claim.release()\n return ApiErrors.conflict({\n message:\n 'This site is at the platform lead limit, so the lead was not ' +\n 'created. Remove some leads, or contact support if this is real traffic.',\n code: 'lead_ceiling',\n headers: ctx.headers,\n })\n }\n }\n const stored = await addHostLead({\n hostRef,\n hostId: site.siteId,\n lead: {\n email: email as string,\n ...(name ? { name } : {}),\n source: LEAD_API_SOURCE,\n },\n })\n if (!stored) {\n await claim.release()\n return ApiErrors.conflict({\n message: 'The lead could not be stored',\n code: 'lead_not_stored',\n headers: ctx.headers,\n })\n }\n const working: Record<string, unknown> = {\n ...profileUpdate(profile.patch),\n ...(status ? { status } : {}),\n ...(ownerUid ? { ownerUid } : {}),\n ...(notes ? { notes } : {}),\n /*\n * The map as a map. The write below is a `set` with `merge`, and a\n * merge is a DEEP one: a second create for an address the site\n * already holds fills in the keys this body named and leaves the\n * rest of the map alone. A dotted path would NOT do it — only\n * `update` reads dots as paths, and `set` would file a field\n * literally called `custom.tier`. An explicit `null` is kept, which\n * is how a create clears a key a first create had set.\n */\n ...(Object.keys(customValues).length ? { custom: customValues } : {}),\n }\n const ref = leadsRef.doc(leadId)\n if (Object.keys(working).length) {\n await ref.set({ ...working, updatedAt: Timestamp.now() }, { merge: true })\n // What the console's Leads list searches and filters by (AGL-3321).\n await restampCrmListFieldsAt(ref, 'leads')\n }\n const view = leadView(site.siteId, await ref.get())\n await claim.record(created ? 201 : 200, view)\n return apiJson(view, { status: created ? 201 : 200, headers: ctx.headers })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n\n// ── Convert ─────────────────────────────────────────────────────────────────\n\nconst CONVERT_KEYS = new Set(['siteId', 'company', 'deal', 'ownerUid', 'ownerEmail'])\nconst CURRENCY_PATTERN = /^[a-z]{3}$/\n\ntype ConvertPlan = Pick<ConvertHostLeadInput, 'companyId' | 'createCompany' | 'deal'>\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\n/**\n * The convert body: `company` is `{ link }`, `{ create }` or `null`; `deal`\n * is what to open, or `null`. Each key is named in full — `company.link`,\n * `deal.amountCents` — so a refused nested field is the one the integrator\n * sent.\n */\nfunction readConvertInput(\n body: Record<string, unknown>,\n): { plan: ConvertPlan } | { errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n const plan: ConvertPlan = {}\n refuseUnknownKeys(body, CONVERT_KEYS, 'conversion', errors)\n\n if (body.company !== undefined && body.company !== null) {\n if (!isPlainObject(body.company)) {\n errors.company = 'Must be { \"link\": id }, { \"create\": { … } } or null'\n } else {\n const company = body.company\n const keys = Object.keys(company)\n const shape = keys.length === 1 ? keys[0] : null\n if (shape === 'link') {\n const link = company.link\n if (typeof link !== 'string' || !link.trim() || link.trim().length > CRM_ID_MAX) {\n errors['company.link'] = 'Must be a company id'\n } else {\n plan.companyId = link.trim()\n }\n } else if (shape === 'create' && isPlainObject(company.create)) {\n const create = company.create\n for (const key of Object.keys(create)) {\n if (key !== 'name' && key !== 'domain') {\n errors[`company.create.${key}`] = 'Not writable on a company created here'\n }\n }\n const name = String(create.name ?? '')\n .trim()\n .slice(0, CRM_LABEL_MAX)\n if (!name) errors['company.create.name'] = 'A name is required'\n let domain: string | null = null\n if (create.domain !== undefined && create.domain !== null) {\n const rawDomain = String(create.domain).trim()\n domain = rawDomain ? normalizeCompanyDomain(rawDomain) : null\n if (rawDomain && !domain) {\n errors['company.create.domain'] = 'Must be a domain, like acme.com'\n }\n }\n if (name) plan.createCompany = { name, domain }\n } else {\n errors.company = 'Must be exactly one of { \"link\": id } or { \"create\": { \"name\", \"domain\"? } }'\n }\n }\n }\n\n if (body.deal !== undefined && body.deal !== null) {\n if (!isPlainObject(body.deal)) {\n errors.deal = 'Must be an object, or null'\n } else {\n const deal = body.deal\n for (const key of Object.keys(deal)) {\n if (!['title', 'amountCents', 'currency', 'stageId'].includes(key)) {\n errors[`deal.${key}`] = 'Not writable on a deal opened here'\n }\n }\n const title = String(deal.title ?? '')\n .trim()\n .slice(0, CRM_TITLE_MAX)\n if (!title) errors['deal.title'] = 'A title is required'\n let amountCents: number | null = null\n if (deal.amountCents !== undefined && deal.amountCents !== null) {\n if (\n typeof deal.amountCents !== 'number' ||\n !Number.isInteger(deal.amountCents) ||\n deal.amountCents < 0\n ) {\n errors['deal.amountCents'] = 'Must be a whole number of cents, 0 or more'\n } else {\n amountCents = deal.amountCents\n }\n }\n let currency = 'usd'\n if (deal.currency !== undefined && deal.currency !== null) {\n const value = String(deal.currency).trim().toLowerCase()\n if (CURRENCY_PATTERN.test(value)) currency = value\n else errors['deal.currency'] = 'Must be a three-letter ISO 4217 code, like usd'\n }\n let stageId: string | undefined\n if (deal.stageId !== undefined && deal.stageId !== null) {\n if (typeof deal.stageId !== 'string' || !deal.stageId.trim()) {\n errors['deal.stageId'] = 'Must be a stage id'\n } else {\n stageId = deal.stageId.trim().slice(0, CRM_ID_MAX)\n }\n }\n if (title) plan.deal = { title, amountCents, currency, ...(stageId ? { stageId } : {}) }\n }\n }\n\n return Object.keys(errors).length ? { errors } : { plan }\n}\n\n/** `POST /v1/leads/{id}/convert`. */\nasync function convertLead(\n request: Request,\n ctx: ApiV1Context,\n url: URL,\n leadId: string,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const parsed = readConvertInput(body)\n if ('errors' in parsed) return crmValidationFailed(ctx, 'conversion', parsed.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) return site.response\n const errors: Record<string, string> = {}\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'conversion', errors)\n\n const ref = leadsCollection(ctx, site.siteId).doc(leadId)\n if (!(await ref.get()).exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n\n // Scoped to the SITE: the lead is the site's record, and its id is only\n // unique within the site — see the module header.\n const claimed = await claimWrite(\n ctx,\n site.siteId,\n request.headers.get('Idempotency-Key'),\n 'lead-conversions',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n\n try {\n const result = await convertHostLead({\n firestore: ctx.firestore,\n hostId: site.siteId,\n orgId: ctx.orgId,\n org: ctx.org as Record<string, unknown>,\n leadId,\n // A key has no uid; `'api'` is the attribution every REST create\n // stamps, and a key owns nothing — see the runtime's header. Its name\n // is what the site's activity feed shows for the conversion.\n actor: { uid: 'api', email: null, kind: 'api', apiKeyName: ctx.keyName },\n ...(ownerUid ? { ownerUid } : {}),\n ...parsed.plan,\n })\n if (result.ok === false) {\n await claim.release()\n return convertRefusal(ctx, result.reason)\n }\n const receipt = {\n object: 'lead_conversion',\n id: leadId,\n siteId: site.siteId,\n contactId: result.contactId,\n companyId: result.companyId ?? null,\n dealId: result.dealId ?? null,\n alreadyConverted: result.alreadyConverted,\n lead: leadView(site.siteId, await ref.get()),\n }\n await claim.record(200, receipt)\n return apiJson(receipt, {\n status: result.alreadyConverted ? 200 : 201,\n headers: ctx.headers,\n })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n\n/**\n * Each refusal the writes can answer, in the envelope this API publishes.\n * A band is `plan_required` with the creates' own code, because on the one\n * plan that refuses an upgrade is the whole remedy; the rest are states of\n * the record, which is what `409 conflict` means here.\n */\nfunction convertRefusal(\n ctx: ApiV1Context,\n reason: ConvertHostLeadRefusal,\n): Response {\n switch (reason) {\n case 'unknown-lead':\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n case 'unknown-company':\n return crmValidationFailed(ctx, 'conversion', {\n 'company.link': 'No such company in this organization',\n })\n case 'band-full':\n return ApiErrors.planRequired({\n message:\n 'CRM records limit reached across contacts, companies and deals — ' +\n 'the lead was not converted. Upgrade the plan to add more.',\n code: 'crm_records_quota',\n headers: ctx.headers,\n })\n case 'no-email':\n return ApiErrors.conflict({\n message: 'This lead has no usable email address to convert',\n code: 'lead_not_convertible',\n headers: ctx.headers,\n })\n case 'contact-not-created':\n return ApiErrors.conflict({\n message:\n 'The contact could not be created — the audience band may be full. ' +\n 'Nothing was changed.',\n code: 'contact_not_created',\n headers: ctx.headers,\n })\n case 'erased':\n return ApiErrors.conflict({\n message:\n 'This person was erased from the organization at their request, so ' +\n 'a contact cannot be created for this address. Nothing was changed.',\n code: 'person_erased',\n headers: ctx.headers,\n })\n case 'no-stages':\n return ApiErrors.conflict({\n message: 'The default pipeline has no stages to open a deal in',\n code: 'pipeline_has_no_stages',\n headers: ctx.headers,\n })\n }\n}\n\n// ── List ────────────────────────────────────────────────────────────────────\n\n/** The `lastSeen`-ordered cursor: `ms|id`. */\nfunction encodeLeadCursor(lastSeenAtMs: unknown, id: string): string {\n return `${typeof lastSeenAtMs === 'number' ? lastSeenAtMs : 0}|${id}`\n}\n\nfunction decodeLeadCursor(raw: string | undefined): { lastSeenAtMs: number; id: string } | null {\n if (!raw) return null\n const match = /^(\\d+)\\|(.+)$/.exec(raw)\n return match ? { lastSeenAtMs: Number(match[1]), id: match[2] } : null\n}\n\n/** `GET /v1/leads`. */\nasync function listLeads(ctx: ApiV1Context, url: URL): Promise<Response> {\n const site = readLeadSite(ctx, url)\n if ('response' in site) return site.response\n const rawStatus = (url.searchParams.get('status') ?? '').trim()\n if (rawStatus && !(CRM_LEAD_STATUSES as readonly string[]).includes(rawStatus)) {\n return crmValidationFailed(ctx, 'lead filter', {\n status: `Must be one of: ${CRM_LEAD_STATUSES.join(', ')}`,\n })\n }\n const status = rawStatus as CrmLeadStatus | ''\n const ownerUid = (url.searchParams.get('ownerUid') ?? '').trim().slice(0, CRM_ID_MAX)\n\n const limit = parseLimit(url.searchParams.get('limit'))\n let query: FirebaseFirestore.Query = leadsVisibleTo(ctx, site.siteId)\n .orderBy('lastSeenAtMs', 'desc')\n .orderBy(FieldPath.documentId(), 'desc')\n .limit(limit + 1)\n const cursor = decodeLeadCursor(decodeCursor(url.searchParams.get('cursor')))\n if (cursor) query = query.startAfter(cursor.lastSeenAtMs, cursor.id)\n const snap = await query.get()\n const docs = snap.docs.slice(0, limit)\n const last = docs[docs.length - 1]\n const nextCursor =\n snap.docs.length > limit && last\n ? encodeCursor(encodeLeadCursor(last.get('lastSeenAtMs'), last.id))\n : null\n const page = docs.filter((doc) => {\n const data = (doc.data() ?? {}) as CrmLeadFields\n if (status && crmLeadStatus(data) !== status) return false\n if (ownerUid && data.ownerUid !== ownerUid) return false\n return true\n })\n return listResponse(\n page.map((doc) => leadView(site.siteId, doc)),\n nextCursor,\n ctx.headers,\n )\n}\n\n// ── Dispatch ────────────────────────────────────────────────────────────────\n\nexport async function handleLeads(\n request: Request,\n ctx: ApiV1Context,\n segments: string[],\n url: URL,\n): Promise<Response> {\n const [, leadId, action] = segments\n\n if (!leadId) {\n if (request.method === 'GET') {\n const denied = requireScope(ctx, 'crm:read')\n if (denied) return denied\n return listLeads(ctx, url)\n }\n if (request.method === 'POST') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return createLead(request, ctx, url)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'GET, POST' },\n })\n }\n\n if (action === 'convert') {\n if (request.method === 'POST') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return convertLead(request, ctx, url, leadId)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'POST' },\n })\n }\n if (action !== undefined) {\n return ApiErrors.notFound({\n message: `Unknown endpoint: /v1/${segments.join('/')}`,\n headers: ctx.headers,\n })\n }\n\n if (request.method === 'GET') {\n const denied = requireScope(ctx, 'crm:read')\n if (denied) return denied\n const site = readLeadSite(ctx, url)\n if ('response' in site) return site.response\n const snap = await leadsCollection(ctx, site.siteId).doc(leadId).get()\n if (!snap.exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n return apiJson(leadView(site.siteId, snap), { headers: ctx.headers })\n }\n if (request.method === 'PATCH') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return updateLead(request, ctx, url, leadId)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'GET, PATCH' },\n })\n}\n"],"names":["checkVisitorRecordCeiling","CRM_LEAD_STATUSES","crmLeadStatus","crmMemberOption","findOrgMember","LEADS_MAX_PER_HOST","normalizeCompanyDomain","normalizeContactEmail","crmPicklistDefaultLabel","judgeCrmLeadSource","normalizeCrmLeadProfile","personKey","readMarketingBasis","soloConsentGroup","scopeTokensForHost","addHostLead","apiJson","ApiErrors","decodeCursor","encodeCursor","listOrgMembers","listResponse","parseLimit","restampCrmListFieldsAt","convertHostLead","FieldPath","FieldValue","Timestamp","claimWrite","orgOwnsHost","readJsonBody","requireScope","CRM_ID_MAX","CRM_LABEL_MAX","CRM_TEXT_MAX","CRM_TITLE_MAX","crmCustomUpdate","crmCustomView","crmTimes","crmValidationFailed","isoFromMs","memberError","readCrmCustomBody","readOptionalText","readOrgLeadSourcePicklist","refuseUnknownKeys","updatePayload","LEAD_PATCH_STATUSES","leadView","siteId","doc","data","consent","id","object","email","name","status","ownerUid","notes","unqualifiedReason","company","jobTitle","phone","website","address","tags","Array","isArray","map","String","leadSource","sources","submissionCount","firstSeen","lastSeen","marketingConsent","basis","marketingConsentAt","basisAtMs","convertedContactId","convertedAt","convertedAtMs","companyId","dealId","custom","readLeadSite","ctx","url","body","refuse","error","response","raw","searchParams","get","undefined","trim","leadsCollection","_siteId","firestore","collection","orgId","leadsVisibleTo","where","readOwner","errors","ownerEmail","members","roster","member","filter","option","found","uid","LEAD_PROFILE_KEYS","LEAD_WRITABLE","Set","profileUpdate","patch","update","key","value","Object","entries","delete","readLeadInput","values","profile","profileBody","assign","includes","join","reason","keys","length","updateLead","request","leadId","snap","parsed","site","ref","exists","notFound","message","headers","stored","rest","conflict","code","current","next","judged","ok","updatedAt","now","LEAD_CREATE_STATUSES","LEAD_NAME_MAX","LEAD_API_SOURCE","createLead","customValues","claimed","replay","claim","hostRef","leadsRef","existing","created","leadSources","release","fallback","used","count","exceeded","hostId","lead","source","working","set","merge","view","record","CONVERT_KEYS","CURRENCY_PATTERN","isPlainObject","Boolean","readConvertInput","plan","shape","link","create","slice","domain","rawDomain","createCompany","deal","title","amountCents","Number","isInteger","currency","toLowerCase","test","stageId","convertLead","result","org","actor","kind","apiKeyName","keyName","convertRefusal","receipt","contactId","alreadyConverted","planRequired","encodeLeadCursor","lastSeenAtMs","decodeLeadCursor","match","exec","listLeads","rawStatus","limit","query","orderBy","documentId","cursor","startAfter","docs","last","nextCursor","page","handleLeads","segments","action","method","denied","methodNotAllowed","Allow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/crm-leads.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * `/v1/leads` (AGL-2627) — the work queue: every person a site captured,\n * with the working state a sales team keeps on them and the door that turns\n * one into a contact.\n *\n * ## A lead belongs to a site, so every request names one\n *\n * The five CRM collections sit under the org and a key reads them whole\n * (`crm-shared.ts`). A lead does not: it lives at\n * `hosts/{siteId}/leads/{id}`, private to the site that captured it, with no\n * `visibleTo` to filter on and an id that is only unique within its site —\n * the person key, so one address is one row per site. There is no org-wide\n * list to read, and `/v1/leads/{id}` alone names a row in every site the org\n * has. So `siteId` is required on every endpoint here, as a query parameter\n * (a `PATCH` or `POST` may carry it in the body instead), and a site the\n * organization does not own is refused the way `consentSiteId` is on a\n * create. That is also what keeps this resource mountable at the org level:\n * the site is a parameter, never an assumption.\n *\n * ## Newest first, and the status filter runs on the page\n *\n * A polling integration asks for what arrived since it last looked, so the\n * list is ordered by `lastSeen` descending — the one field the capture door\n * stamps on every lead, at every capture — with the id as tiebreak, and the\n * cursor carries both. `?status=` is applied to the page rather than sent to\n * Firestore, for a reason a `where` cannot answer: a lead nobody has touched\n * carries no `status` field at all and reads as `new` (`crmLeadStatus`), so\n * `where('status', '==', 'new')` would miss exactly the leads a work queue\n * exists to show. The page can come back short, which the conventions page\n * already documents; no composite index is needed and none is shipped.\n *\n * ## A lead is a record of its own (AGL-3231)\n *\n * `POST /v1/leads` creates one — Salesforce's lead, sourced outside the\n * site and entered before anyone has qualified it: the address, a name,\n * the company as text, a title, a phone, a website, an address, tags and a\n * lead source, with the working state beside them. It goes through the one\n * lead door (`addHostLead`), so a second create for one address updates the\n * lead the site holds and answers `200` rather than minting a second row;\n * it writes no contact and no company, which are the conversion's to make;\n * and it records no marketing basis, because an integration is not a box\n * the person ticked. The same profile fields are writable on a `PATCH`,\n * `null` clearing one, and every one of them is published on the lead.\n *\n * ## What a PATCH may say about a status\n *\n * The same three moves the console offers: `new`, `working`, and\n * `unqualified` with a reason. `qualified` is the converted state — a lead\n * reaches it through `POST /v1/leads/{id}/convert` and nowhere else, because\n * the status is a claim that a contact exists and the conversion is what\n * makes it true. A converted lead's status is fixed for the same reason.\n * Reopening an unqualified lead drops its reason, as the console does: a\n * lead being worked again is not \"unqualified because …\".\n */\nimport {\n checkVisitorRecordCeiling,\n CRM_LEAD_STATUSES,\n type CrmLeadFields,\n type CrmLeadProfilePatch,\n type CrmLeadStatus,\n crmLeadStatus,\n crmMemberOption,\n findOrgMember,\n LEADS_MAX_PER_HOST,\n normalizeCompanyDomain,\n normalizeContactEmail,\n crmPicklistDefaultLabel,\n judgeCrmLeadSource,\n normalizeCrmLeadProfile,\n personKey,\n readMarketingBasis,\n soloConsentGroup,\n} from '@aglyn/aglyn/server'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n addHostLead,\n apiJson,\n ApiErrors,\n decodeCursor,\n encodeCursor,\n listOrgMembers,\n listResponse,\n parseLimit,\n restampCrmListFieldsAt,\n} from '@aglyn/tenant-data-admin'\nimport {\n type ConvertHostLeadInput,\n type ConvertHostLeadRefusal,\n convertHostLead,\n} from '../convert-host-lead'\nimport { FieldPath, FieldValue, Timestamp } from 'firebase-admin/firestore'\nimport {\n type ApiV1Context,\n claimWrite,\n orgOwnsHost,\n readJsonBody,\n requireScope,\n} from '@aglyn/tenant-data-admin/server/api-v1-kit'\nimport {\n type Clearable,\n CRM_ID_MAX,\n CRM_LABEL_MAX,\n CRM_TEXT_MAX,\n CRM_TITLE_MAX,\n crmCustomUpdate,\n crmCustomView,\n crmTimes,\n crmValidationFailed,\n isoFromMs,\n memberError,\n readCrmCustomBody,\n readOptionalText,\n readOrgLeadSourcePicklist,\n refuseUnknownKeys,\n updatePayload,\n} from './crm-shared'\n\n/**\n * The statuses a PATCH may set — every status but the converted one.\n *\n * A literal, checked against the model's union, rather than a filter over\n * `CRM_LEAD_STATUSES`: nothing in this module may dereference the model at\n * load time, for the reason `crm-shared.ts` gives — the `/v1` suites mock\n * `@aglyn/aglyn/server` as a closed world, and a module-level read of a\n * constant they never asked for throws at import in every one of them.\n */\nconst LEAD_PATCH_STATUSES = [\n 'new',\n 'nurturing',\n 'working',\n 'unqualified',\n] as const satisfies readonly Exclude<CrmLeadStatus, 'qualified'>[]\n\n// ── The view ────────────────────────────────────────────────────────────────\n\n/** The lead object as published. Every writable field appears here. */\nfunction leadView(siteId: string, doc: FirebaseFirestore.DocumentSnapshot) {\n const data = (doc.data() ?? {}) as Record<string, unknown> & CrmLeadFields\n // The lead's own silo — a group of one, however the org pools its\n // contacts — which is where `addHostLead` recorded the basis.\n const consent = readMarketingBasis(data, soloConsentGroup(siteId))\n return {\n id: doc.id,\n object: 'lead',\n siteId,\n email: typeof data['email'] === 'string' ? data['email'] : null,\n name: typeof data['name'] === 'string' ? data['name'] : null,\n status: crmLeadStatus(data),\n ownerUid: data.ownerUid ?? null,\n notes: data.notes ?? null,\n unqualifiedReason: data.unqualifiedReason ?? null,\n // The lead's own profile (AGL-3231).\n company: data.company ?? null,\n jobTitle: data.jobTitle ?? null,\n phone: data.phone ?? null,\n website: data.website ?? null,\n address: data.address ?? null,\n tags: Array.isArray(data.tags) ? data.tags.map(String) : [],\n leadSource: data.leadSource ?? null,\n sources: Array.isArray(data['sources']) ? data['sources'].map(String) : [],\n submissionCount:\n typeof data['submissionCount'] === 'number' ? data['submissionCount'] : 0,\n firstSeen: isoFromMs(data['firstSeenAtMs']),\n lastSeen: isoFromMs(data['lastSeenAtMs']),\n marketingConsent: consent.basis === 'granted',\n marketingConsentAt: consent.basis === 'granted' ? isoFromMs(consent.basisAtMs) : null,\n convertedContactId: data.convertedContactId ?? null,\n convertedAt: isoFromMs(data.convertedAtMs),\n companyId: data.companyId ?? null,\n dealId: data.dealId ?? null,\n // The org's own lead fields (AGL-3272) — `{}` on a lead that carries\n // none, so a client can index it without a guard.\n custom: crmCustomView(data as FirebaseFirestore.DocumentData),\n ...crmTimes(data as FirebaseFirestore.DocumentData),\n }\n}\n\n// ── The site ────────────────────────────────────────────────────────────────\n\n/**\n * The `siteId` a request names, validated — the query parameter first, the\n * body as the fallback a `POST`/`PATCH` may use. Required, and required to\n * be one of the organization's own sites, for the reason the module header\n * gives.\n */\nfunction readLeadSite(\n ctx: ApiV1Context,\n url: URL,\n body: Record<string, unknown> = {},\n): { siteId: string } | { response: Response } {\n const refuse = (error: string) => ({\n response: crmValidationFailed(ctx, 'lead', { siteId: error }),\n })\n const raw = url.searchParams.get('siteId') ?? body.siteId\n if (raw === undefined || raw === null) {\n return refuse('Required — name the site the lead belongs to')\n }\n const siteId = String(raw).trim()\n if (!siteId) return refuse('Must name a site')\n if (!orgOwnsHost(ctx, siteId)) return refuse('No such site in this organization')\n return { siteId }\n}\n\n/**\n * The org's leads (AGL-3275). The site is still named by every caller — a\n * lead is created and listed in a site's context — but it selects by\n * `visibleTo` now rather than by the collection it lives in, so\n * {@link leadsVisibleTo} is what a LIST must use. This bare ref is for\n * `doc(id)` reads and writes, which the route authorizes for itself.\n */\nfunction leadsCollection(ctx: ApiV1Context, _siteId: string) {\n return ctx.firestore.collection('orgs').doc(ctx.orgId).collection('leads')\n}\n\n/** The same collection, narrowed to the leads one site may see. */\nfunction leadsVisibleTo(ctx: ApiV1Context, siteId: string) {\n return leadsCollection(ctx, siteId).where(\n 'visibleTo',\n 'array-contains-any',\n scopeTokensForHost(siteId),\n )\n}\n\n// ── The owner ───────────────────────────────────────────────────────────────\n\n/**\n * The owner a body names, by uid or by address, resolved to a uid on the\n * roster — or `null` to clear, or `undefined` when the body named nobody.\n *\n * `ownerEmail` is what a spreadsheet or a zap has; a uid is what the record\n * stores. The address resolves the way the CRM's own import and automation\n * steps resolve one (AGL-2614): against the roster alone, so an address the\n * project's auth knows but the organization does not names nobody. One\n * roster read, paid only when an address was given.\n */\nasync function readOwner(\n ctx: ApiV1Context,\n body: Record<string, unknown>,\n errors: Record<string, string>,\n): Promise<Clearable<string>> {\n const ownerUid = readOptionalText(body, 'ownerUid', CRM_TITLE_MAX, errors)\n const ownerEmail = readOptionalText(body, 'ownerEmail', CRM_TITLE_MAX, errors)\n if (ownerUid !== undefined && ownerEmail !== undefined) {\n errors.ownerEmail = 'Name the owner once — by ownerUid or by ownerEmail, not both'\n return undefined\n }\n if (ownerEmail === undefined) return ownerUid\n if (ownerEmail === null) return null\n const email = normalizeContactEmail(ownerEmail)\n if (!email) {\n errors.ownerEmail = 'Must be an email address'\n return undefined\n }\n const members = await listOrgMembers(ctx.orgId)\n const roster = members\n .map((member) => crmMemberOption(member as unknown as Record<string, unknown>))\n .filter((option): option is NonNullable<typeof option> => option !== null)\n const found = findOrgMember(roster, email)\n if (!found) {\n errors.ownerEmail = 'No member of this organization has this address'\n return undefined\n }\n return found.uid\n}\n\n// ── PATCH ───────────────────────────────────────────────────────────────────\n\n/** The lead's own fields, writable on a create and a PATCH alike (AGL-3231). */\nconst LEAD_PROFILE_KEYS = [\n 'company',\n 'jobTitle',\n 'phone',\n 'website',\n 'address',\n 'tags',\n 'leadSource',\n] as const\n\nconst LEAD_WRITABLE = new Set([\n 'siteId',\n 'status',\n 'ownerUid',\n 'ownerEmail',\n 'notes',\n 'unqualifiedReason',\n // The org's own lead fields (AGL-3272), judged against the definitions\n // whose object is `lead` — never read by `readLeadInput`, which knows\n // nothing about the org's definitions, but named here so a body that\n // carries one is not refused as an unknown key.\n 'custom',\n ...LEAD_PROFILE_KEYS,\n])\n\ninterface LeadInput {\n status?: Exclude<CrmLeadStatus, 'qualified'>\n notes?: Clearable<string>\n unqualifiedReason?: Clearable<string>\n /** The profile as the model read it: a key present and `null` clears. */\n profile: CrmLeadProfilePatch\n}\n\n/** The profile as an update: a cleared field is deleted, not blanked. */\nfunction profileUpdate(patch: CrmLeadProfilePatch): Record<string, unknown> {\n const update: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch)) {\n if (value === undefined) continue\n update[key] = value === null ? FieldValue.delete() : value\n }\n return update\n}\n\nfunction readLeadInput(\n body: Record<string, unknown>,\n): { values: LeadInput } | { errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n const values: LeadInput = { profile: {} }\n refuseUnknownKeys(body, LEAD_WRITABLE, 'lead', errors)\n\n const profileBody: Record<string, unknown> = {}\n for (const key of LEAD_PROFILE_KEYS) {\n if (key in body) profileBody[key] = body[key]\n }\n const profile = normalizeCrmLeadProfile(profileBody)\n Object.assign(errors, profile.errors)\n values.profile = profile.patch\n\n if (body.status !== undefined) {\n if (body.status === 'qualified') {\n errors.status =\n 'Not settable — a lead is qualified by converting it: POST /v1/leads/{id}/convert'\n } else if (\n typeof body.status === 'string' &&\n (LEAD_PATCH_STATUSES as readonly string[]).includes(body.status)\n ) {\n values.status = body.status as LeadInput['status']\n } else {\n errors.status = `Must be one of: ${LEAD_PATCH_STATUSES.join(', ')}`\n }\n }\n const notes = readOptionalText(body, 'notes', CRM_TEXT_MAX, errors)\n if (notes !== undefined) values.notes = notes\n const reason = readOptionalText(body, 'unqualifiedReason', CRM_TEXT_MAX, errors)\n if (reason !== undefined) values.unqualifiedReason = reason\n\n return Object.keys(errors).length ? { errors } : { values }\n}\n\n/** `PATCH /v1/leads/{id}`. */\nasync function updateLead(\n request: Request,\n ctx: ApiV1Context,\n url: URL,\n leadId: string,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const parsed = readLeadInput(body)\n if ('errors' in parsed) return crmValidationFailed(ctx, 'lead', parsed.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) return site.response\n const ref = leadsCollection(ctx, site.siteId).doc(leadId)\n const snap = await ref.get()\n if (!snap.exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n const stored = (snap.data() ?? {}) as Record<string, unknown> & CrmLeadFields\n\n const { status, unqualifiedReason, profile, ...rest } = parsed.values\n if (status !== undefined && stored.convertedContactId) {\n return ApiErrors.conflict({\n message: 'This lead was converted; its status is fixed',\n code: 'lead_converted',\n headers: ctx.headers,\n })\n }\n const errors: Record<string, string> = {}\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n const current = crmLeadStatus(stored)\n const next = status ?? current\n /*\n * A reason travels with the closed state and only with it. Sent beside\n * `status: \"unqualified\"` it is required; sent on its own it changes the\n * reason of a lead that is already unqualified; sent with any other\n * status it describes a state the lead is not in.\n */\n if (next === 'unqualified') {\n const reason =\n unqualifiedReason !== undefined ? unqualifiedReason : stored.unqualifiedReason\n if (!reason) {\n errors.unqualifiedReason = 'A reason is required to mark a lead unqualified'\n }\n } else if (unqualifiedReason) {\n errors.unqualifiedReason = 'Only an unqualified lead carries a reason'\n }\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298): a restricted picklist. An active\n * value is stored as the list spells it; the value the lead already\n * holds is kept even when it has since been deactivated; anything else\n * is a 400 naming the values the list allows.\n */\n if (typeof profile.leadSource === 'string') {\n const judged = judgeCrmLeadSource(\n await readOrgLeadSourcePicklist(ctx),\n profile.leadSource,\n stored.leadSource,\n )\n if (judged.ok === false) errors.leadSource = judged.error\n else profile.leadSource = judged.value\n }\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'lead', errors)\n // The org's own lead fields (AGL-3272), judged against the definitions\n // whose object is `lead` — a contact field of the same key is a\n // different field with a possibly different type.\n const custom = await readCrmCustomBody(ctx, body, 'lead')\n if (custom && 'errors' in custom) return crmValidationFailed(ctx, 'lead', custom.errors)\n\n const update: Record<string, unknown> = {\n ...updatePayload({ ...rest, ownerUid }),\n ...profileUpdate(profile),\n // One dotted path per key, so the map is merged rather than replaced.\n ...(custom && 'values' in custom ? crmCustomUpdate(custom.values) : {}),\n }\n if (status !== undefined && status !== current) update.status = status\n if (next === 'unqualified') {\n if (unqualifiedReason !== undefined) update.unqualifiedReason = unqualifiedReason\n } else if (stored.unqualifiedReason !== undefined) {\n // Reopening drops the reason with the closed state.\n update.unqualifiedReason = FieldValue.delete()\n }\n if (Object.keys(update).length > 0) {\n await ref.update({ ...update, updatedAt: Timestamp.now() })\n // What the console's Leads list searches and filters by (AGL-3321).\n await restampCrmListFieldsAt(ref, 'leads')\n }\n return apiJson(leadView(site.siteId, await ref.get()), { headers: ctx.headers })\n}\n\n// ── Create ──────────────────────────────────────────────────────────────────\n\nconst LEAD_CREATE_STATUSES = ['new', 'working'] as const\nconst LEAD_NAME_MAX = 120\n\n/** The surface a lead created over the API names, beside `signup`, `booking`, `form:{id}`, `import` and `manual`. */\nconst LEAD_API_SOURCE = 'api'\n\n/**\n * `POST /v1/leads` (AGL-3231). Accepts an `Idempotency-Key`, scoped to the\n * site. Through the one lead door, so one address is one lead per site: a\n * second create updates and answers `200`.\n */\nasync function createLead(request: Request, ctx: ApiV1Context, url: URL): Promise<Response> {\n const body = await readJsonBody(request)\n const errors: Record<string, string> = {}\n refuseUnknownKeys(\n body,\n new Set([...LEAD_WRITABLE, 'email', 'name']),\n 'lead',\n errors,\n )\n const email = normalizeContactEmail(body.email)\n if (!email) errors.email = 'Required — an email address'\n const name = readOptionalText(body, 'name', LEAD_NAME_MAX, errors)\n const notes = readOptionalText(body, 'notes', CRM_TEXT_MAX, errors)\n let status: (typeof LEAD_CREATE_STATUSES)[number] | undefined\n if (body.status !== undefined && body.status !== null) {\n if (\n typeof body.status === 'string' &&\n (LEAD_CREATE_STATUSES as readonly string[]).includes(body.status)\n ) {\n status = body.status as (typeof LEAD_CREATE_STATUSES)[number]\n } else {\n errors.status = `Must be one of: ${LEAD_CREATE_STATUSES.join(', ')}`\n }\n }\n if (body.unqualifiedReason !== undefined) {\n errors.unqualifiedReason = 'Not settable on a create — unqualify with a PATCH'\n }\n const profileBody: Record<string, unknown> = {}\n for (const key of LEAD_PROFILE_KEYS) {\n if (key in body) profileBody[key] = body[key]\n }\n const profile = normalizeCrmLeadProfile(profileBody)\n Object.assign(errors, profile.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) {\n if (Object.keys(errors).length) {\n return crmValidationFailed(ctx, 'lead', {\n ...errors,\n siteId: 'Required — name the site the lead belongs to',\n })\n }\n return site.response\n }\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'lead', errors)\n // Judged above the claim (AGL-3272), so a refused field costs no\n // idempotency key — the shape the companies resource writes.\n const custom = await readCrmCustomBody(ctx, body, 'lead')\n if (custom && 'errors' in custom) return crmValidationFailed(ctx, 'lead', custom.errors)\n const customValues = custom && 'values' in custom ? custom.values : {}\n\n const claimed = await claimWrite(\n ctx,\n site.siteId,\n request.headers.get('Idempotency-Key'),\n 'lead-creates',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n try {\n const hostRef = ctx.firestore.collection('hosts').doc(site.siteId)\n const leadsRef = leadsCollection(ctx, site.siteId)\n // Non-null by construction: the normalizer refused every address this\n // derivation could not key.\n const leadId = personKey(email as string) as string\n const existing = await leadsRef.doc(leadId).get()\n const created = !existing.exists\n /*\n * THE ORG'S LEAD SOURCES (AGL-3298), judged once the lead's own value\n * is known: a value outside the list is a 400 naming what it allows, a\n * lead the site already held keeps its current value, and a new lead\n * that named none starts from the list's default.\n */\n const leadSources = await readOrgLeadSourcePicklist(ctx)\n if (typeof profile.patch.leadSource === 'string') {\n const judged = judgeCrmLeadSource(\n leadSources,\n profile.patch.leadSource,\n existing.get('leadSource'),\n )\n if (judged.ok === false) {\n await claim.release()\n return crmValidationFailed(ctx, 'lead', { leadSource: judged.error })\n }\n profile.patch.leadSource = judged.value\n } else if (profile.patch.leadSource === undefined && created) {\n const fallback = crmPicklistDefaultLabel(leadSources)\n if (fallback) profile.patch.leadSource = fallback\n }\n if (created) {\n // What the SITE may see (AGL-3275), matching `addHostLead`'s own count:\n // an unscoped count would charge one site for a sibling's leads.\n const used = (await leadsVisibleTo(ctx, site.siteId).count().get()).data().count\n if (checkVisitorRecordCeiling(used, LEADS_MAX_PER_HOST).exceeded) {\n await claim.release()\n return ApiErrors.conflict({\n message:\n 'This site is at the platform lead limit, so the lead was not ' +\n 'created. Remove some leads, or contact support if this is real traffic.',\n code: 'lead_ceiling',\n headers: ctx.headers,\n })\n }\n }\n const stored = await addHostLead({\n hostRef,\n hostId: site.siteId,\n lead: {\n email: email as string,\n ...(name ? { name } : {}),\n source: LEAD_API_SOURCE,\n },\n })\n if (!stored) {\n await claim.release()\n return ApiErrors.conflict({\n message: 'The lead could not be stored',\n code: 'lead_not_stored',\n headers: ctx.headers,\n })\n }\n const working: Record<string, unknown> = {\n ...profileUpdate(profile.patch),\n ...(status ? { status } : {}),\n ...(ownerUid ? { ownerUid } : {}),\n ...(notes ? { notes } : {}),\n /*\n * The map as a map. The write below is a `set` with `merge`, and a\n * merge is a DEEP one: a second create for an address the site\n * already holds fills in the keys this body named and leaves the\n * rest of the map alone. A dotted path would NOT do it — only\n * `update` reads dots as paths, and `set` would file a field\n * literally called `custom.tier`. An explicit `null` is kept, which\n * is how a create clears a key a first create had set.\n */\n ...(Object.keys(customValues).length ? { custom: customValues } : {}),\n }\n const ref = leadsRef.doc(leadId)\n if (Object.keys(working).length) {\n await ref.set({ ...working, updatedAt: Timestamp.now() }, { merge: true })\n // What the console's Leads list searches and filters by (AGL-3321).\n await restampCrmListFieldsAt(ref, 'leads')\n }\n const view = leadView(site.siteId, await ref.get())\n await claim.record(created ? 201 : 200, view)\n return apiJson(view, { status: created ? 201 : 200, headers: ctx.headers })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n\n// ── Convert ─────────────────────────────────────────────────────────────────\n\nconst CONVERT_KEYS = new Set(['siteId', 'company', 'deal', 'ownerUid', 'ownerEmail'])\nconst CURRENCY_PATTERN = /^[a-z]{3}$/\n\ntype ConvertPlan = Pick<ConvertHostLeadInput, 'companyId' | 'createCompany' | 'deal'>\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\n/**\n * The convert body: `company` is `{ link }`, `{ create }` or `null`; `deal`\n * is what to open, or `null`. Each key is named in full — `company.link`,\n * `deal.amountCents` — so a refused nested field is the one the integrator\n * sent.\n */\nfunction readConvertInput(\n body: Record<string, unknown>,\n): { plan: ConvertPlan } | { errors: Record<string, string> } {\n const errors: Record<string, string> = {}\n const plan: ConvertPlan = {}\n refuseUnknownKeys(body, CONVERT_KEYS, 'conversion', errors)\n\n if (body.company !== undefined && body.company !== null) {\n if (!isPlainObject(body.company)) {\n errors.company = 'Must be { \"link\": id }, { \"create\": { … } } or null'\n } else {\n const company = body.company\n const keys = Object.keys(company)\n const shape = keys.length === 1 ? keys[0] : null\n if (shape === 'link') {\n const link = company.link\n if (typeof link !== 'string' || !link.trim() || link.trim().length > CRM_ID_MAX) {\n errors['company.link'] = 'Must be a company id'\n } else {\n plan.companyId = link.trim()\n }\n } else if (shape === 'create' && isPlainObject(company.create)) {\n const create = company.create\n for (const key of Object.keys(create)) {\n if (key !== 'name' && key !== 'domain') {\n errors[`company.create.${key}`] = 'Not writable on a company created here'\n }\n }\n const name = String(create.name ?? '')\n .trim()\n .slice(0, CRM_LABEL_MAX)\n if (!name) errors['company.create.name'] = 'A name is required'\n let domain: string | null = null\n if (create.domain !== undefined && create.domain !== null) {\n const rawDomain = String(create.domain).trim()\n domain = rawDomain ? normalizeCompanyDomain(rawDomain) : null\n if (rawDomain && !domain) {\n errors['company.create.domain'] = 'Must be a domain, like acme.com'\n }\n }\n if (name) plan.createCompany = { name, domain }\n } else {\n errors.company = 'Must be exactly one of { \"link\": id } or { \"create\": { \"name\", \"domain\"? } }'\n }\n }\n }\n\n if (body.deal !== undefined && body.deal !== null) {\n if (!isPlainObject(body.deal)) {\n errors.deal = 'Must be an object, or null'\n } else {\n const deal = body.deal\n for (const key of Object.keys(deal)) {\n if (!['title', 'amountCents', 'currency', 'stageId'].includes(key)) {\n errors[`deal.${key}`] = 'Not writable on a deal opened here'\n }\n }\n const title = String(deal.title ?? '')\n .trim()\n .slice(0, CRM_TITLE_MAX)\n if (!title) errors['deal.title'] = 'A title is required'\n let amountCents: number | null = null\n if (deal.amountCents !== undefined && deal.amountCents !== null) {\n if (\n typeof deal.amountCents !== 'number' ||\n !Number.isInteger(deal.amountCents) ||\n deal.amountCents < 0\n ) {\n errors['deal.amountCents'] = 'Must be a whole number of cents, 0 or more'\n } else {\n amountCents = deal.amountCents\n }\n }\n let currency = 'usd'\n if (deal.currency !== undefined && deal.currency !== null) {\n const value = String(deal.currency).trim().toLowerCase()\n if (CURRENCY_PATTERN.test(value)) currency = value\n else errors['deal.currency'] = 'Must be a three-letter ISO 4217 code, like usd'\n }\n let stageId: string | undefined\n if (deal.stageId !== undefined && deal.stageId !== null) {\n if (typeof deal.stageId !== 'string' || !deal.stageId.trim()) {\n errors['deal.stageId'] = 'Must be a stage id'\n } else {\n stageId = deal.stageId.trim().slice(0, CRM_ID_MAX)\n }\n }\n if (title) plan.deal = { title, amountCents, currency, ...(stageId ? { stageId } : {}) }\n }\n }\n\n return Object.keys(errors).length ? { errors } : { plan }\n}\n\n/** `POST /v1/leads/{id}/convert`. */\nasync function convertLead(\n request: Request,\n ctx: ApiV1Context,\n url: URL,\n leadId: string,\n): Promise<Response> {\n const body = await readJsonBody(request)\n const parsed = readConvertInput(body)\n if ('errors' in parsed) return crmValidationFailed(ctx, 'conversion', parsed.errors)\n const site = readLeadSite(ctx, url, body)\n if ('response' in site) return site.response\n const errors: Record<string, string> = {}\n const ownerUid = await readOwner(ctx, body, errors)\n if (ownerUid) Object.assign(errors, await memberError(ctx, 'ownerUid', ownerUid))\n if (Object.keys(errors).length) return crmValidationFailed(ctx, 'conversion', errors)\n\n const ref = leadsCollection(ctx, site.siteId).doc(leadId)\n if (!(await ref.get()).exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n\n // Scoped to the SITE: the lead is the site's record, and its id is only\n // unique within the site — see the module header.\n const claimed = await claimWrite(\n ctx,\n site.siteId,\n request.headers.get('Idempotency-Key'),\n 'lead-conversions',\n )\n if ('replay' in claimed) return claimed.replay\n const { claim } = claimed\n\n try {\n const result = await convertHostLead({\n firestore: ctx.firestore,\n hostId: site.siteId,\n orgId: ctx.orgId,\n org: ctx.org as Record<string, unknown>,\n leadId,\n // A key has no uid; `'api'` is the attribution every REST create\n // stamps, and a key owns nothing — see the runtime's header. Its name\n // is what the site's activity feed shows for the conversion.\n actor: { uid: 'api', email: null, kind: 'api', apiKeyName: ctx.keyName },\n ...(ownerUid ? { ownerUid } : {}),\n ...parsed.plan,\n })\n if (result.ok === false) {\n await claim.release()\n return convertRefusal(ctx, result.reason)\n }\n const receipt = {\n object: 'lead_conversion',\n id: leadId,\n siteId: site.siteId,\n contactId: result.contactId,\n companyId: result.companyId ?? null,\n dealId: result.dealId ?? null,\n alreadyConverted: result.alreadyConverted,\n lead: leadView(site.siteId, await ref.get()),\n }\n await claim.record(200, receipt)\n return apiJson(receipt, {\n status: result.alreadyConverted ? 200 : 201,\n headers: ctx.headers,\n })\n } catch (error) {\n await claim.release()\n throw error\n }\n}\n\n/**\n * Each refusal the writes can answer, in the envelope this API publishes.\n * A band is `plan_required` with the creates' own code, because on the one\n * plan that refuses an upgrade is the whole remedy; the rest are states of\n * the record, which is what `409 conflict` means here.\n */\nfunction convertRefusal(\n ctx: ApiV1Context,\n reason: ConvertHostLeadRefusal,\n): Response {\n switch (reason) {\n case 'unknown-lead':\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n case 'unknown-company':\n return crmValidationFailed(ctx, 'conversion', {\n 'company.link': 'No such company in this organization',\n })\n case 'band-full':\n return ApiErrors.planRequired({\n message:\n 'CRM records limit reached across contacts, companies and deals — ' +\n 'the lead was not converted. Upgrade the plan to add more.',\n code: 'crm_records_quota',\n headers: ctx.headers,\n })\n case 'no-email':\n return ApiErrors.conflict({\n message: 'This lead has no usable email address to convert',\n code: 'lead_not_convertible',\n headers: ctx.headers,\n })\n case 'contact-not-created':\n return ApiErrors.conflict({\n message:\n 'The contact could not be created — the audience band may be full. ' +\n 'Nothing was changed.',\n code: 'contact_not_created',\n headers: ctx.headers,\n })\n case 'erased':\n return ApiErrors.conflict({\n message:\n 'This person was erased from the organization at their request, so ' +\n 'a contact cannot be created for this address. Nothing was changed.',\n code: 'person_erased',\n headers: ctx.headers,\n })\n case 'no-stages':\n return ApiErrors.conflict({\n message: 'The default pipeline has no stages to open a deal in',\n code: 'pipeline_has_no_stages',\n headers: ctx.headers,\n })\n }\n}\n\n// ── List ────────────────────────────────────────────────────────────────────\n\n/** The `lastSeen`-ordered cursor: `ms|id`. */\nfunction encodeLeadCursor(lastSeenAtMs: unknown, id: string): string {\n return `${typeof lastSeenAtMs === 'number' ? lastSeenAtMs : 0}|${id}`\n}\n\nfunction decodeLeadCursor(raw: string | undefined): { lastSeenAtMs: number; id: string } | null {\n if (!raw) return null\n const match = /^(\\d+)\\|(.+)$/.exec(raw)\n return match ? { lastSeenAtMs: Number(match[1]), id: match[2] } : null\n}\n\n/** `GET /v1/leads`. */\nasync function listLeads(ctx: ApiV1Context, url: URL): Promise<Response> {\n const site = readLeadSite(ctx, url)\n if ('response' in site) return site.response\n const rawStatus = (url.searchParams.get('status') ?? '').trim()\n if (rawStatus && !(CRM_LEAD_STATUSES as readonly string[]).includes(rawStatus)) {\n return crmValidationFailed(ctx, 'lead filter', {\n status: `Must be one of: ${CRM_LEAD_STATUSES.join(', ')}`,\n })\n }\n const status = rawStatus as CrmLeadStatus | ''\n const ownerUid = (url.searchParams.get('ownerUid') ?? '').trim().slice(0, CRM_ID_MAX)\n\n const limit = parseLimit(url.searchParams.get('limit'))\n let query: FirebaseFirestore.Query = leadsVisibleTo(ctx, site.siteId)\n .orderBy('lastSeenAtMs', 'desc')\n .orderBy(FieldPath.documentId(), 'desc')\n .limit(limit + 1)\n const cursor = decodeLeadCursor(decodeCursor(url.searchParams.get('cursor')))\n if (cursor) query = query.startAfter(cursor.lastSeenAtMs, cursor.id)\n const snap = await query.get()\n const docs = snap.docs.slice(0, limit)\n const last = docs[docs.length - 1]\n const nextCursor =\n snap.docs.length > limit && last\n ? encodeCursor(encodeLeadCursor(last.get('lastSeenAtMs'), last.id))\n : null\n const page = docs.filter((doc) => {\n const data = (doc.data() ?? {}) as CrmLeadFields\n if (status && crmLeadStatus(data) !== status) return false\n if (ownerUid && data.ownerUid !== ownerUid) return false\n return true\n })\n return listResponse(\n page.map((doc) => leadView(site.siteId, doc)),\n nextCursor,\n ctx.headers,\n )\n}\n\n// ── Dispatch ────────────────────────────────────────────────────────────────\n\nexport async function handleLeads(\n request: Request,\n ctx: ApiV1Context,\n segments: string[],\n url: URL,\n): Promise<Response> {\n const [, leadId, action] = segments\n\n if (!leadId) {\n if (request.method === 'GET') {\n const denied = requireScope(ctx, 'crm:read')\n if (denied) return denied\n return listLeads(ctx, url)\n }\n if (request.method === 'POST') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return createLead(request, ctx, url)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'GET, POST' },\n })\n }\n\n if (action === 'convert') {\n if (request.method === 'POST') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return convertLead(request, ctx, url, leadId)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'POST' },\n })\n }\n if (action !== undefined) {\n return ApiErrors.notFound({\n message: `Unknown endpoint: /v1/${segments.join('/')}`,\n headers: ctx.headers,\n })\n }\n\n if (request.method === 'GET') {\n const denied = requireScope(ctx, 'crm:read')\n if (denied) return denied\n const site = readLeadSite(ctx, url)\n if ('response' in site) return site.response\n const snap = await leadsCollection(ctx, site.siteId).doc(leadId).get()\n if (!snap.exists) {\n return ApiErrors.notFound({ message: 'No such lead', headers: ctx.headers })\n }\n return apiJson(leadView(site.siteId, snap), { headers: ctx.headers })\n }\n if (request.method === 'PATCH') {\n const denied = requireScope(ctx, 'crm:write')\n if (denied) return denied\n return updateLead(request, ctx, url, leadId)\n }\n return ApiErrors.methodNotAllowed({\n headers: { ...ctx.headers, Allow: 'GET, PATCH' },\n })\n}\n"],"names":["checkVisitorRecordCeiling","CRM_LEAD_STATUSES","crmLeadStatus","crmMemberOption","findOrgMember","LEADS_MAX_PER_HOST","normalizeCompanyDomain","normalizeContactEmail","crmPicklistDefaultLabel","judgeCrmLeadSource","normalizeCrmLeadProfile","personKey","readMarketingBasis","soloConsentGroup","scopeTokensForHost","addHostLead","apiJson","ApiErrors","decodeCursor","encodeCursor","listOrgMembers","listResponse","parseLimit","restampCrmListFieldsAt","convertHostLead","FieldPath","FieldValue","Timestamp","claimWrite","orgOwnsHost","readJsonBody","requireScope","CRM_ID_MAX","CRM_LABEL_MAX","CRM_TEXT_MAX","CRM_TITLE_MAX","crmCustomUpdate","crmCustomView","crmTimes","crmValidationFailed","isoFromMs","memberError","readCrmCustomBody","readOptionalText","readOrgLeadSourcePicklist","refuseUnknownKeys","updatePayload","LEAD_PATCH_STATUSES","leadView","siteId","doc","data","consent","id","object","email","name","status","ownerUid","notes","unqualifiedReason","company","jobTitle","phone","website","address","tags","Array","isArray","map","String","leadSource","sources","submissionCount","firstSeen","lastSeen","marketingConsent","basis","marketingConsentAt","basisAtMs","convertedContactId","convertedAt","convertedAtMs","companyId","dealId","custom","readLeadSite","ctx","url","body","refuse","error","response","raw","searchParams","get","undefined","trim","leadsCollection","_siteId","firestore","collection","orgId","leadsVisibleTo","where","readOwner","errors","ownerEmail","members","roster","member","filter","option","found","uid","LEAD_PROFILE_KEYS","LEAD_WRITABLE","Set","profileUpdate","patch","update","key","value","Object","entries","delete","readLeadInput","values","profile","profileBody","assign","includes","join","reason","keys","length","updateLead","request","leadId","snap","parsed","site","ref","exists","notFound","message","headers","stored","rest","conflict","code","current","next","judged","ok","updatedAt","now","LEAD_CREATE_STATUSES","LEAD_NAME_MAX","LEAD_API_SOURCE","createLead","customValues","claimed","replay","claim","hostRef","leadsRef","existing","created","leadSources","release","fallback","used","count","exceeded","hostId","lead","source","working","set","merge","view","record","CONVERT_KEYS","CURRENCY_PATTERN","isPlainObject","Boolean","readConvertInput","plan","shape","link","create","slice","domain","rawDomain","createCompany","deal","title","amountCents","Number","isInteger","currency","toLowerCase","test","stageId","convertLead","result","org","actor","kind","apiKeyName","keyName","convertRefusal","receipt","contactId","alreadyConverted","planRequired","encodeLeadCursor","lastSeenAtMs","decodeLeadCursor","match","exec","listLeads","rawStatus","limit","query","orderBy","documentId","cursor","startAfter","docs","last","nextCursor","page","handleLeads","segments","action","method","denied","methodNotAllowed","Allow"],"mappings":"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"}
|
|
@@ -745,7 +745,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
745
745
|
],
|
|
746
746
|
writeOnly: {
|
|
747
747
|
siteId: stringField('The site the lead belongs to, instead of the `siteId` query parameter.'),
|
|
748
|
-
status: stringField('`new`, `working` or `unqualified` (`unqualified` on a `PATCH` only). A lead becomes `qualified` by being converted.'),
|
|
748
|
+
status: stringField('`new`, `nurturing`, `working` or `unqualified` (`nurturing` and `unqualified` on a `PATCH` only). A lead becomes `qualified` by being converted.'),
|
|
749
749
|
ownerEmail: stringField('A member’s address, resolved against the organization’s roster. Not with `ownerUid` in the same request.')
|
|
750
750
|
},
|
|
751
751
|
fields: _extends({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/openapi.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ApiV1ResourceDescription,\n booleanField,\n integerField,\n isoField,\n nullableField,\n objectKindField,\n openObjectField,\n postalAddressField,\n queryParam,\n RECORD_STAMPS,\n stringField,\n stringListField,\n UPDATED_AFTER_PARAM,\n} from '@aglyn/tenant-data-admin/server/api-v1-description'\n\n/**\n * The CRM's resources as the customer API's OpenAPI document describes them\n * (AGL-2733), keyed by the resource each describes — what the console's\n * builder turns into their paths, tags and schemas, and the MCP tools derive\n * from.\n *\n * Transcribed from the response examples in `apps/docs/api/resources/*.md`,\n * the customer-facing contract, as the platform's own resources are; each\n * write body lists its handler's own writable set. `api-v1-openapi.spec.ts`\n * holds the document to every endpoint the documentation promises.\n */\nexport const CRM_API_V1_DESCRIPTIONS: Readonly<Record<string, ApiV1ResourceDescription>> = {\n contacts: {\n tag: 'Contacts',\n description: 'People, organization-wide. Part of the CRM, which is included from Starter.',\n schemaName: 'Contact',\n required: ['id', 'object', 'email'],\n writable: [\n 'email', 'name', 'tags', 'notes', 'marketingConsent', 'consentSiteId', 'consentGroupId', 'custom',\n 'phone', 'jobTitle', 'companyId', 'address', 'ownerUid', 'lifecycleStage', 'mediaIds',\n ],\n writeOnly: {\n consentSiteId: stringField('The site this write is made on behalf of: where the person opted in, and whose profile the profile fields land on.'),\n consentGroupId: stringField('With `marketingConsent: true`: the consent group the person was shown when they opted in. The opt-in then covers every site of that group; without it, or when the site is no longer in that group, it covers `consentSiteId` alone.'),\n custom: openObjectField('Contact custom fields, keyed by field key.'),\n mediaIds: stringListField('Media library files attached by the named site, by id, at most 20. An empty array clears them.'),\n },\n writeNote: '`email` is accepted on create only — a `PATCH` that names it is a `400`, because a contact is identified by its email.',\n fields: {\n id: stringField('Contact id.'),\n object: objectKindField('contact'),\n email: stringField('Primary email. Unique per organization.'),\n name: stringField('Display name.'),\n tags: stringListField('Free-form tags.'),\n notes: stringField('Free-form notes.'),\n marketingConsent: booleanField('Whether the contact accepted marketing email.'),\n consentSites: stringListField('Site ids the consent was given on.'),\n sources: stringListField('Where this contact came from.'),\n phone: stringField('Telephone number.'),\n jobTitle: stringField('Job title.'),\n companyId: nullableField(stringField('Primary company.')),\n address: postalAddressField(),\n ownerUid: nullableField(stringField('Owning user. Checked on the page, not the query.')),\n lifecycleStage: nullableField(stringField('CRM lifecycle stage.')),\n companyIds: stringListField('Every company this contact belongs to.'),\n alternateEmails: stringListField('Other addresses that resolve to this contact.'),\n ...RECORD_STAMPS,\n },\n ops: [\n {\n path: '/v1/contacts', method: 'get', operationId: 'listContacts', summary: 'List contacts', list: true, returns: 'Contact', entitlement: 'crm',\n description: 'Combining `email` with `tag` narrows on `email` and checks `tag` on the page, so pages can come back short. `lifecycleStage` and `ownerUid` live on a per-site profile and are ALWAYS checked on the page.',\n filters: [\n queryParam('email', 'Exact match, normalized the way the write path normalizes it. An unusable value is a 400.'),\n queryParam('tag', 'Checked on the page when combined with `email`.'),\n queryParam('lifecycleStage', 'Always checked on the page.'),\n queryParam('ownerUid', 'Always checked on the page.'),\n ],\n },\n { path: '/v1/contacts', method: 'post', operationId: 'createContact', summary: 'Create a contact', accepts: 'ContactWrite', returns: 'Contact', entitlement: 'crm', creates: true, description: 'A duplicate email is `409 conflict` (`code: \"contact_exists\"`), and the message names the existing id.' },\n { path: '/v1/contacts/{contactId}', method: 'get', operationId: 'getContact', summary: 'Retrieve a contact', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}', method: 'patch', operationId: 'updateContact', summary: 'Update a contact', accepts: 'ContactWrite', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}', method: 'delete', operationId: 'deleteContact', summary: 'Delete a contact', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}/merge', method: 'post', operationId: 'mergeContact', summary: 'Merge two contacts', accepts: 'ContactMerge', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'The contact that survives.' }] },\n ],\n components: {\n ContactMerge: {\n type: 'object',\n required: ['sourceContactId'],\n description:\n 'Merge another contact into this one. The source is removed. Any other ' +\n 'member is a `400`.',\n properties: {\n sourceContactId: {\n type: 'string',\n description:\n 'The contact to merge FROM. It does not survive, and it must not be ' +\n 'the contact in the path.',\n },\n },\n additionalProperties: false,\n },\n },\n },\n companies: {\n tag: 'Companies',\n description: 'Organizations in the CRM.',\n schemaName: 'Company',\n required: ['id', 'object', 'name'],\n writable: ['name', 'domain', 'website', 'phone', 'address', 'industry', 'ownerUid', 'notes', 'custom', 'mediaIds', 'consentSiteId'],\n writeOnly: {\n mediaIds: stringListField('Media library files attached to this company, by id, at most 20. An empty array clears them.'),\n consentSiteId: stringField('The site the company is created on behalf of.'),\n },\n writeNote: 'A create needs `name` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Company id.'),\n object: objectKindField('company'),\n name: stringField('Company name.'),\n domain: nullableField(stringField('Primary domain. Unique per organization.')),\n website: nullableField(stringField('Website URL.')),\n phone: nullableField(stringField('Telephone number.')),\n address: postalAddressField(),\n industry: nullableField(stringField('Industry label.')),\n ownerUid: nullableField(stringField('Owning user.')),\n notes: nullableField(stringField('Free-form notes.')),\n custom: openObjectField('Customer-defined fields.'),\n nextTaskAt: nullableField(isoField('When the next open task on this company is due.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/companies', method: 'get', operationId: 'listCompanies', summary: 'List companies', list: true, returns: 'Company', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('domain', 'Exact match.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/companies', method: 'post', operationId: 'createCompany', summary: 'Create a company', accepts: 'CompanyWrite', returns: 'Company', entitlement: 'crm', creates: true, description: 'A duplicate domain is `409 conflict` (`code: \"company_exists\"`), naming the existing id.' },\n { path: '/v1/companies/{companyId}', method: 'get', operationId: 'getCompany', summary: 'Retrieve a company', returns: 'Company', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n { path: '/v1/companies/{companyId}', method: 'patch', operationId: 'updateCompany', summary: 'Update a company', accepts: 'CompanyWrite', returns: 'Company', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n { path: '/v1/companies/{companyId}', method: 'delete', operationId: 'deleteCompany', summary: 'Delete a company', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n ],\n },\n pipelines: {\n tag: 'Pipelines',\n description: 'Deal pipelines and their stages. Read-only over the API.',\n schemaName: 'Pipeline',\n required: ['id', 'object', 'name', 'stages'],\n fields: {\n id: stringField('Pipeline id.'),\n object: objectKindField('pipeline'),\n name: stringField('Pipeline name.'),\n isDefault: booleanField('Whether new deals land here when none is named.'),\n archived: booleanField('Whether the pipeline is archived.'),\n archivedAt: nullableField(isoField('When it was archived.')),\n stages: {\n type: 'array',\n description: 'Ordered stages. A deal’s `stageId` names one of these.',\n items: {\n type: 'object',\n properties: { id: { type: 'string' }, name: { type: 'string' }, order: { type: 'integer' } },\n additionalProperties: true,\n },\n },\n siteId: nullableField(stringField('Site the pipeline belongs to, when scoped.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/pipelines', method: 'get', operationId: 'listPipelines', summary: 'List pipelines', list: true, returns: 'Pipeline', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM] },\n { path: '/v1/pipelines/{pipelineId}', method: 'get', operationId: 'getPipeline', summary: 'Retrieve a pipeline', returns: 'Pipeline', entitlement: 'crm', pathParams: [{ name: 'pipelineId', description: 'Pipeline id.' }] },\n ],\n },\n deals: {\n tag: 'Deals',\n description: 'Opportunities moving through a pipeline.',\n schemaName: 'Deal',\n required: ['id', 'object', 'title', 'pipelineId', 'stageId'],\n writable: ['title', 'pipelineId', 'stageId', 'status', 'amountCents', 'currency', 'lineItems', 'expectedCloseAt', 'ownerUid', 'contactId', 'companyId', 'lostReason', 'notes', 'custom', 'mediaIds', 'consentSiteId'],\n writeOnly: {\n mediaIds: stringListField('Media library files attached to this deal, by id, at most 20. An empty array clears them.'),\n consentSiteId: stringField('The site the deal is created on behalf of.'),\n },\n writeNote: '`pipelineId` and `consentSiteId` are accepted on create only; a `PATCH` that names either is a `400`.',\n fields: {\n id: stringField('Deal id.'),\n object: objectKindField('deal'),\n title: stringField('Deal title.'),\n pipelineId: stringField('Pipeline the deal sits in.'),\n stageId: stringField('Stage within that pipeline.'),\n status: stringField('`open`, `won` or `lost`.'),\n amountCents: integerField('Value in the smallest unit of `currency`.'),\n currency: stringField('ISO 4217 code.'),\n lineItems: { type: 'array', description: 'Line items, when the deal carries them.', items: { type: 'object', additionalProperties: true } },\n expectedCloseAt: nullableField(isoField('Forecast close date.')),\n closedAt: nullableField(isoField('When it was actually closed.')),\n stageChangedAt: nullableField(isoField('When the stage last moved.')),\n ownerUid: nullableField(stringField('Owning user.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n lostReason: nullableField(stringField('Why it was lost.')),\n notes: nullableField(stringField('Free-form notes.')),\n custom: openObjectField('Customer-defined fields.'),\n nextTaskAt: nullableField(isoField('When the next open task on this deal is due.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/deals', method: 'get', operationId: 'listDeals', summary: 'List deals', list: true, returns: 'Deal', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('pipelineId', 'Pipeline.'), queryParam('stageId', 'Stage.'), queryParam('status', 'Deal status.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/deals', method: 'post', operationId: 'createDeal', summary: 'Create a deal', accepts: 'DealWrite', returns: 'Deal', entitlement: 'crm', creates: true },\n { path: '/v1/deals/{dealId}', method: 'get', operationId: 'getDeal', summary: 'Retrieve a deal', returns: 'Deal', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n { path: '/v1/deals/{dealId}', method: 'patch', operationId: 'updateDeal', summary: 'Update a deal', accepts: 'DealWrite', returns: 'Deal', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n { path: '/v1/deals/{dealId}', method: 'delete', operationId: 'deleteDeal', summary: 'Delete a deal', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n ],\n },\n tasks: {\n tag: 'Tasks',\n description: 'Follow-ups attached to CRM records.',\n schemaName: 'Task',\n required: ['id', 'object', 'title'],\n writable: ['title', 'notes', 'kind', 'priority', 'status', 'dueAt', 'remindAt', 'assigneeUid', 'contactId', 'companyId', 'dealId', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the task is created on behalf of.'),\n },\n writeNote: 'A create needs `title` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Task id.'),\n object: objectKindField('task'),\n title: stringField('Task title.'),\n notes: nullableField(stringField('Free-form notes.')),\n kind: stringField('Task kind, e.g. `call` or `email`.'),\n priority: stringField('Priority label.'),\n status: stringField('`open` or `done`.'),\n dueAt: nullableField(isoField('When the task is due.')),\n remindAt: nullableField(isoField('When a reminder is scheduled.')),\n reminderSentAt: nullableField(isoField('When the reminder was sent.')),\n completedAt: nullableField(isoField('When it was completed.')),\n assigneeUid: nullableField(stringField('Assigned user.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n dealId: nullableField(stringField('Associated deal.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/tasks', method: 'get', operationId: 'listTasks', summary: 'List tasks', list: true, returns: 'Task', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('status', 'Task status.'), queryParam('assigneeUid', 'Assigned user.'), queryParam('contactId', 'Associated contact.'), queryParam('dealId', 'Associated deal.')] },\n { path: '/v1/tasks', method: 'post', operationId: 'createTask', summary: 'Create a task', accepts: 'TaskWrite', returns: 'Task', entitlement: 'crm', creates: true },\n { path: '/v1/tasks/{taskId}', method: 'get', operationId: 'getTask', summary: 'Retrieve a task', returns: 'Task', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n { path: '/v1/tasks/{taskId}', method: 'patch', operationId: 'updateTask', summary: 'Update a task', accepts: 'TaskWrite', returns: 'Task', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n { path: '/v1/tasks/{taskId}', method: 'delete', operationId: 'deleteTask', summary: 'Delete a task', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n ],\n },\n activities: {\n tag: 'Activities',\n description: 'Logged interactions. Append-only: no PATCH.',\n schemaName: 'Activity',\n required: ['id', 'object', 'kind', 'at'],\n writable: ['kind', 'body', 'at', 'byUid', 'contactId', 'companyId', 'dealId', 'outcome', 'durationMinutes', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the activity is logged on behalf of.'),\n },\n writeRequired: ['body', 'consentSiteId'],\n fields: {\n id: stringField('Activity id.'),\n object: objectKindField('activity'),\n kind: stringField('What happened, e.g. `call`, `email`, `note`.'),\n body: stringField('Free-form body.'),\n at: isoField('When the interaction happened — not when it was logged.'),\n byUid: nullableField(stringField('User who logged it.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n dealId: nullableField(stringField('Associated deal.')),\n outcome: nullableField(stringField('Outcome label.')),\n durationMinutes: nullableField(integerField('Duration, for calls and meetings.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/activities', method: 'get', operationId: 'listActivities', summary: 'List activities', list: true, returns: 'Activity', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('kind', 'Activity kind.'), queryParam('contactId', 'Associated contact.'), queryParam('dealId', 'Associated deal.')] },\n { path: '/v1/activities', method: 'post', operationId: 'createActivity', summary: 'Log an activity', accepts: 'ActivityWrite', returns: 'Activity', entitlement: 'crm', creates: true },\n { path: '/v1/activities/{activityId}', method: 'get', operationId: 'getActivity', summary: 'Retrieve an activity', returns: 'Activity', entitlement: 'crm', pathParams: [{ name: 'activityId', description: 'Activity id.' }] },\n { path: '/v1/activities/{activityId}', method: 'delete', operationId: 'deleteActivity', summary: 'Delete an activity', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'activityId', description: 'Activity id.' }] },\n ],\n },\n leads: {\n tag: 'Leads',\n description: 'Unqualified interest, before it becomes a contact. A lead is a record of its own: the person and their company as text, until converting makes the contact and the company.',\n schemaName: 'Lead',\n required: ['id', 'object', 'siteId'],\n writable: ['siteId', 'email', 'name', 'status', 'ownerUid', 'ownerEmail', 'notes', 'unqualifiedReason', 'company', 'jobTitle', 'phone', 'website', 'address', 'tags', 'leadSource'],\n writeNote: '`email` and `name` are taken on a create only; a `PATCH` cannot change the address, which is the lead’s identity within its site.',\n writeRequired: ['email'],\n writeOnly: {\n siteId: stringField('The site the lead belongs to, instead of the `siteId` query parameter.'),\n status: stringField('`new`, `working` or `unqualified` (`unqualified` on a `PATCH` only). A lead becomes `qualified` by being converted.'),\n ownerEmail: stringField('A member’s address, resolved against the organization’s roster. Not with `ownerUid` in the same request.'),\n },\n fields: {\n id: stringField('Lead id.'),\n object: objectKindField('lead'),\n siteId: stringField('Site the lead arrived on.'),\n email: nullableField(stringField('Email — the lead’s identity within its site.')),\n name: nullableField(stringField('Name, when supplied.')),\n status: stringField('Lead status.'),\n ownerUid: nullableField(stringField('Owning user.')),\n notes: nullableField(stringField('Free-form notes.')),\n unqualifiedReason: nullableField(stringField('Why the lead was disqualified.')),\n company: nullableField(stringField('The company’s name, as text. Converting the lead is what links or creates the company record.')),\n jobTitle: nullableField(stringField('Job title.')),\n phone: nullableField(stringField('E.164 phone number.')),\n website: nullableField(stringField('An http(s) URL.')),\n address: nullableField(postalAddressField()),\n tags: stringListField('Lower-cased tags.'),\n leadSource: nullableField(\n stringField(\n \"Where the lead came from: one of the organization's active lead source values \" +\n '(CRM › Fields › Leads), matched without regard to case. Any other value is refused ' +\n 'with a 400 naming the values allowed; the value a lead already holds is kept even ' +\n \"after it is deactivated. A create that names none starts from the list's default.\",\n ),\n ),\n sources: stringListField('The surfaces that captured the lead: `signup`, `booking`, `form:{formId}`, `import`, `manual`, `api`.'),\n submissionCount: integerField('How many form submissions this lead has made.'),\n firstSeen: isoField('First interaction.'),\n lastSeen: isoField('Most recent interaction.'),\n marketingConsent: booleanField('Whether marketing consent was given.'),\n marketingConsentAt: nullableField(isoField('When consent was given.')),\n convertedContactId: nullableField(stringField('Contact created by conversion.')),\n convertedAt: nullableField(isoField('When it was converted.')),\n companyId: nullableField(stringField('Company created or matched by conversion.')),\n dealId: nullableField(stringField('Deal created by conversion.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/leads', method: 'get', operationId: 'listLeads', summary: 'List leads', list: true, returns: 'Lead', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('siteId', 'Site the lead arrived on.'), queryParam('status', 'Lead status.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/leads', method: 'post', operationId: 'createLead', summary: 'Create a lead', accepts: 'LeadWrite', returns: 'Lead', entitlement: 'crm', creates: true, description: 'A lead sourced outside the site, entered before anyone has qualified it. One address is one lead per site: creating one the site already holds updates it and answers 200. No contact and no company are created — converting the lead does that — and no marketing consent is recorded.' },\n { path: '/v1/leads/{leadId}', method: 'get', operationId: 'getLead', summary: 'Retrieve a lead', returns: 'Lead', entitlement: 'crm', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n { path: '/v1/leads/{leadId}', method: 'patch', operationId: 'updateLead', summary: 'Update a lead', accepts: 'LeadWrite', returns: 'Lead', entitlement: 'crm', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n { path: '/v1/leads/{leadId}/convert', method: 'post', operationId: 'convertLead', summary: 'Convert a lead', returns: 'LeadConversion', entitlement: 'crm', description: 'Creates a contact, and optionally a company and a deal. Idempotent on the lead: converting an already-converted lead returns the existing ids.', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n ],\n components: {\n LeadConversion: {\n type: 'object',\n description:\n 'What the conversion produced. Idempotent on the lead: converting an ' +\n 'already-converted lead returns the existing ids rather than creating ' +\n 'a second set.',\n properties: {\n object: { type: 'string', const: 'lead_conversion' },\n contactId: { type: 'string' },\n companyId: { type: ['string', 'null'] },\n dealId: { type: ['string', 'null'] },\n },\n additionalProperties: true,\n },\n },\n },\n 'email-templates': {\n tag: 'Email templates',\n description: 'Reusable email bodies.',\n schemaName: 'EmailTemplate',\n required: ['id', 'object', 'name'],\n writable: ['name', 'kind', 'visibility', 'ownerUid', 'subject', 'body', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the template is created on behalf of.'),\n },\n writeNote: 'A create needs `name`, `body` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Template id.'),\n object: objectKindField('email_template'),\n name: stringField('Template name.'),\n kind: stringField('Template kind.'),\n visibility: stringField('`org` or `private`.'),\n ownerUid: nullableField(stringField('Owner, for a private template.')),\n subject: stringField('Subject line.'),\n body: stringField('Body. May contain merge tokens.'),\n siteId: nullableField(stringField('Site the template belongs to, when scoped.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/email-templates', method: 'get', operationId: 'listEmailTemplates', summary: 'List email templates', list: true, returns: 'EmailTemplate', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('kind', 'Template kind.'), queryParam('visibility', 'Visibility.')] },\n { path: '/v1/email-templates', method: 'post', operationId: 'createEmailTemplate', summary: 'Create a template', accepts: 'EmailTemplateWrite', returns: 'EmailTemplate', entitlement: 'crm', creates: true },\n { path: '/v1/email-templates/{templateId}', method: 'get', operationId: 'getEmailTemplate', summary: 'Retrieve a template', returns: 'EmailTemplate', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n { path: '/v1/email-templates/{templateId}', method: 'patch', operationId: 'updateEmailTemplate', summary: 'Update a template', accepts: 'EmailTemplateWrite', returns: 'EmailTemplate', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n { path: '/v1/email-templates/{templateId}', method: 'delete', operationId: 'deleteEmailTemplate', summary: 'Delete a template', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n ],\n },\n}\n"],"names":["booleanField","integerField","isoField","nullableField","objectKindField","openObjectField","postalAddressField","queryParam","RECORD_STAMPS","stringField","stringListField","UPDATED_AFTER_PARAM","CRM_API_V1_DESCRIPTIONS","contacts","tag","description","schemaName","required","writable","writeOnly","consentSiteId","consentGroupId","custom","mediaIds","writeNote","fields","id","object","email","name","tags","notes","marketingConsent","consentSites","sources","phone","jobTitle","companyId","address","ownerUid","lifecycleStage","companyIds","alternateEmails","ops","path","method","operationId","summary","list","returns","entitlement","filters","accepts","creates","pathParams","components","ContactMerge","type","properties","sourceContactId","additionalProperties","companies","domain","website","industry","nextTaskAt","siteId","pipelines","isDefault","archived","archivedAt","stages","items","order","deals","title","pipelineId","stageId","status","amountCents","currency","lineItems","expectedCloseAt","closedAt","stageChangedAt","contactId","lostReason","tasks","kind","priority","dueAt","remindAt","reminderSentAt","completedAt","assigneeUid","dealId","activities","writeRequired","body","at","byUid","outcome","durationMinutes","leads","ownerEmail","unqualifiedReason","company","leadSource","submissionCount","firstSeen","lastSeen","marketingConsentAt","convertedContactId","convertedAt","LeadConversion","const","visibility","subject"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/crm/src/lib/server/api-v1/openapi.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ApiV1ResourceDescription,\n booleanField,\n integerField,\n isoField,\n nullableField,\n objectKindField,\n openObjectField,\n postalAddressField,\n queryParam,\n RECORD_STAMPS,\n stringField,\n stringListField,\n UPDATED_AFTER_PARAM,\n} from '@aglyn/tenant-data-admin/server/api-v1-description'\n\n/**\n * The CRM's resources as the customer API's OpenAPI document describes them\n * (AGL-2733), keyed by the resource each describes — what the console's\n * builder turns into their paths, tags and schemas, and the MCP tools derive\n * from.\n *\n * Transcribed from the response examples in `apps/docs/api/resources/*.md`,\n * the customer-facing contract, as the platform's own resources are; each\n * write body lists its handler's own writable set. `api-v1-openapi.spec.ts`\n * holds the document to every endpoint the documentation promises.\n */\nexport const CRM_API_V1_DESCRIPTIONS: Readonly<Record<string, ApiV1ResourceDescription>> = {\n contacts: {\n tag: 'Contacts',\n description: 'People, organization-wide. Part of the CRM, which is included from Starter.',\n schemaName: 'Contact',\n required: ['id', 'object', 'email'],\n writable: [\n 'email', 'name', 'tags', 'notes', 'marketingConsent', 'consentSiteId', 'consentGroupId', 'custom',\n 'phone', 'jobTitle', 'companyId', 'address', 'ownerUid', 'lifecycleStage', 'mediaIds',\n ],\n writeOnly: {\n consentSiteId: stringField('The site this write is made on behalf of: where the person opted in, and whose profile the profile fields land on.'),\n consentGroupId: stringField('With `marketingConsent: true`: the consent group the person was shown when they opted in. The opt-in then covers every site of that group; without it, or when the site is no longer in that group, it covers `consentSiteId` alone.'),\n custom: openObjectField('Contact custom fields, keyed by field key.'),\n mediaIds: stringListField('Media library files attached by the named site, by id, at most 20. An empty array clears them.'),\n },\n writeNote: '`email` is accepted on create only — a `PATCH` that names it is a `400`, because a contact is identified by its email.',\n fields: {\n id: stringField('Contact id.'),\n object: objectKindField('contact'),\n email: stringField('Primary email. Unique per organization.'),\n name: stringField('Display name.'),\n tags: stringListField('Free-form tags.'),\n notes: stringField('Free-form notes.'),\n marketingConsent: booleanField('Whether the contact accepted marketing email.'),\n consentSites: stringListField('Site ids the consent was given on.'),\n sources: stringListField('Where this contact came from.'),\n phone: stringField('Telephone number.'),\n jobTitle: stringField('Job title.'),\n companyId: nullableField(stringField('Primary company.')),\n address: postalAddressField(),\n ownerUid: nullableField(stringField('Owning user. Checked on the page, not the query.')),\n lifecycleStage: nullableField(stringField('CRM lifecycle stage.')),\n companyIds: stringListField('Every company this contact belongs to.'),\n alternateEmails: stringListField('Other addresses that resolve to this contact.'),\n ...RECORD_STAMPS,\n },\n ops: [\n {\n path: '/v1/contacts', method: 'get', operationId: 'listContacts', summary: 'List contacts', list: true, returns: 'Contact', entitlement: 'crm',\n description: 'Combining `email` with `tag` narrows on `email` and checks `tag` on the page, so pages can come back short. `lifecycleStage` and `ownerUid` live on a per-site profile and are ALWAYS checked on the page.',\n filters: [\n queryParam('email', 'Exact match, normalized the way the write path normalizes it. An unusable value is a 400.'),\n queryParam('tag', 'Checked on the page when combined with `email`.'),\n queryParam('lifecycleStage', 'Always checked on the page.'),\n queryParam('ownerUid', 'Always checked on the page.'),\n ],\n },\n { path: '/v1/contacts', method: 'post', operationId: 'createContact', summary: 'Create a contact', accepts: 'ContactWrite', returns: 'Contact', entitlement: 'crm', creates: true, description: 'A duplicate email is `409 conflict` (`code: \"contact_exists\"`), and the message names the existing id.' },\n { path: '/v1/contacts/{contactId}', method: 'get', operationId: 'getContact', summary: 'Retrieve a contact', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}', method: 'patch', operationId: 'updateContact', summary: 'Update a contact', accepts: 'ContactWrite', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}', method: 'delete', operationId: 'deleteContact', summary: 'Delete a contact', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'Contact id.' }] },\n { path: '/v1/contacts/{contactId}/merge', method: 'post', operationId: 'mergeContact', summary: 'Merge two contacts', accepts: 'ContactMerge', returns: 'Contact', entitlement: 'crm', pathParams: [{ name: 'contactId', description: 'The contact that survives.' }] },\n ],\n components: {\n ContactMerge: {\n type: 'object',\n required: ['sourceContactId'],\n description:\n 'Merge another contact into this one. The source is removed. Any other ' +\n 'member is a `400`.',\n properties: {\n sourceContactId: {\n type: 'string',\n description:\n 'The contact to merge FROM. It does not survive, and it must not be ' +\n 'the contact in the path.',\n },\n },\n additionalProperties: false,\n },\n },\n },\n companies: {\n tag: 'Companies',\n description: 'Organizations in the CRM.',\n schemaName: 'Company',\n required: ['id', 'object', 'name'],\n writable: ['name', 'domain', 'website', 'phone', 'address', 'industry', 'ownerUid', 'notes', 'custom', 'mediaIds', 'consentSiteId'],\n writeOnly: {\n mediaIds: stringListField('Media library files attached to this company, by id, at most 20. An empty array clears them.'),\n consentSiteId: stringField('The site the company is created on behalf of.'),\n },\n writeNote: 'A create needs `name` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Company id.'),\n object: objectKindField('company'),\n name: stringField('Company name.'),\n domain: nullableField(stringField('Primary domain. Unique per organization.')),\n website: nullableField(stringField('Website URL.')),\n phone: nullableField(stringField('Telephone number.')),\n address: postalAddressField(),\n industry: nullableField(stringField('Industry label.')),\n ownerUid: nullableField(stringField('Owning user.')),\n notes: nullableField(stringField('Free-form notes.')),\n custom: openObjectField('Customer-defined fields.'),\n nextTaskAt: nullableField(isoField('When the next open task on this company is due.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/companies', method: 'get', operationId: 'listCompanies', summary: 'List companies', list: true, returns: 'Company', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('domain', 'Exact match.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/companies', method: 'post', operationId: 'createCompany', summary: 'Create a company', accepts: 'CompanyWrite', returns: 'Company', entitlement: 'crm', creates: true, description: 'A duplicate domain is `409 conflict` (`code: \"company_exists\"`), naming the existing id.' },\n { path: '/v1/companies/{companyId}', method: 'get', operationId: 'getCompany', summary: 'Retrieve a company', returns: 'Company', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n { path: '/v1/companies/{companyId}', method: 'patch', operationId: 'updateCompany', summary: 'Update a company', accepts: 'CompanyWrite', returns: 'Company', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n { path: '/v1/companies/{companyId}', method: 'delete', operationId: 'deleteCompany', summary: 'Delete a company', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'companyId', description: 'Company id.' }] },\n ],\n },\n pipelines: {\n tag: 'Pipelines',\n description: 'Deal pipelines and their stages. Read-only over the API.',\n schemaName: 'Pipeline',\n required: ['id', 'object', 'name', 'stages'],\n fields: {\n id: stringField('Pipeline id.'),\n object: objectKindField('pipeline'),\n name: stringField('Pipeline name.'),\n isDefault: booleanField('Whether new deals land here when none is named.'),\n archived: booleanField('Whether the pipeline is archived.'),\n archivedAt: nullableField(isoField('When it was archived.')),\n stages: {\n type: 'array',\n description: 'Ordered stages. A deal’s `stageId` names one of these.',\n items: {\n type: 'object',\n properties: { id: { type: 'string' }, name: { type: 'string' }, order: { type: 'integer' } },\n additionalProperties: true,\n },\n },\n siteId: nullableField(stringField('Site the pipeline belongs to, when scoped.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/pipelines', method: 'get', operationId: 'listPipelines', summary: 'List pipelines', list: true, returns: 'Pipeline', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM] },\n { path: '/v1/pipelines/{pipelineId}', method: 'get', operationId: 'getPipeline', summary: 'Retrieve a pipeline', returns: 'Pipeline', entitlement: 'crm', pathParams: [{ name: 'pipelineId', description: 'Pipeline id.' }] },\n ],\n },\n deals: {\n tag: 'Deals',\n description: 'Opportunities moving through a pipeline.',\n schemaName: 'Deal',\n required: ['id', 'object', 'title', 'pipelineId', 'stageId'],\n writable: ['title', 'pipelineId', 'stageId', 'status', 'amountCents', 'currency', 'lineItems', 'expectedCloseAt', 'ownerUid', 'contactId', 'companyId', 'lostReason', 'notes', 'custom', 'mediaIds', 'consentSiteId'],\n writeOnly: {\n mediaIds: stringListField('Media library files attached to this deal, by id, at most 20. An empty array clears them.'),\n consentSiteId: stringField('The site the deal is created on behalf of.'),\n },\n writeNote: '`pipelineId` and `consentSiteId` are accepted on create only; a `PATCH` that names either is a `400`.',\n fields: {\n id: stringField('Deal id.'),\n object: objectKindField('deal'),\n title: stringField('Deal title.'),\n pipelineId: stringField('Pipeline the deal sits in.'),\n stageId: stringField('Stage within that pipeline.'),\n status: stringField('`open`, `won` or `lost`.'),\n amountCents: integerField('Value in the smallest unit of `currency`.'),\n currency: stringField('ISO 4217 code.'),\n lineItems: { type: 'array', description: 'Line items, when the deal carries them.', items: { type: 'object', additionalProperties: true } },\n expectedCloseAt: nullableField(isoField('Forecast close date.')),\n closedAt: nullableField(isoField('When it was actually closed.')),\n stageChangedAt: nullableField(isoField('When the stage last moved.')),\n ownerUid: nullableField(stringField('Owning user.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n lostReason: nullableField(stringField('Why it was lost.')),\n notes: nullableField(stringField('Free-form notes.')),\n custom: openObjectField('Customer-defined fields.'),\n nextTaskAt: nullableField(isoField('When the next open task on this deal is due.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/deals', method: 'get', operationId: 'listDeals', summary: 'List deals', list: true, returns: 'Deal', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('pipelineId', 'Pipeline.'), queryParam('stageId', 'Stage.'), queryParam('status', 'Deal status.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/deals', method: 'post', operationId: 'createDeal', summary: 'Create a deal', accepts: 'DealWrite', returns: 'Deal', entitlement: 'crm', creates: true },\n { path: '/v1/deals/{dealId}', method: 'get', operationId: 'getDeal', summary: 'Retrieve a deal', returns: 'Deal', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n { path: '/v1/deals/{dealId}', method: 'patch', operationId: 'updateDeal', summary: 'Update a deal', accepts: 'DealWrite', returns: 'Deal', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n { path: '/v1/deals/{dealId}', method: 'delete', operationId: 'deleteDeal', summary: 'Delete a deal', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'dealId', description: 'Deal id.' }] },\n ],\n },\n tasks: {\n tag: 'Tasks',\n description: 'Follow-ups attached to CRM records.',\n schemaName: 'Task',\n required: ['id', 'object', 'title'],\n writable: ['title', 'notes', 'kind', 'priority', 'status', 'dueAt', 'remindAt', 'assigneeUid', 'contactId', 'companyId', 'dealId', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the task is created on behalf of.'),\n },\n writeNote: 'A create needs `title` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Task id.'),\n object: objectKindField('task'),\n title: stringField('Task title.'),\n notes: nullableField(stringField('Free-form notes.')),\n kind: stringField('Task kind, e.g. `call` or `email`.'),\n priority: stringField('Priority label.'),\n status: stringField('`open` or `done`.'),\n dueAt: nullableField(isoField('When the task is due.')),\n remindAt: nullableField(isoField('When a reminder is scheduled.')),\n reminderSentAt: nullableField(isoField('When the reminder was sent.')),\n completedAt: nullableField(isoField('When it was completed.')),\n assigneeUid: nullableField(stringField('Assigned user.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n dealId: nullableField(stringField('Associated deal.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/tasks', method: 'get', operationId: 'listTasks', summary: 'List tasks', list: true, returns: 'Task', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('status', 'Task status.'), queryParam('assigneeUid', 'Assigned user.'), queryParam('contactId', 'Associated contact.'), queryParam('dealId', 'Associated deal.')] },\n { path: '/v1/tasks', method: 'post', operationId: 'createTask', summary: 'Create a task', accepts: 'TaskWrite', returns: 'Task', entitlement: 'crm', creates: true },\n { path: '/v1/tasks/{taskId}', method: 'get', operationId: 'getTask', summary: 'Retrieve a task', returns: 'Task', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n { path: '/v1/tasks/{taskId}', method: 'patch', operationId: 'updateTask', summary: 'Update a task', accepts: 'TaskWrite', returns: 'Task', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n { path: '/v1/tasks/{taskId}', method: 'delete', operationId: 'deleteTask', summary: 'Delete a task', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'taskId', description: 'Task id.' }] },\n ],\n },\n activities: {\n tag: 'Activities',\n description: 'Logged interactions. Append-only: no PATCH.',\n schemaName: 'Activity',\n required: ['id', 'object', 'kind', 'at'],\n writable: ['kind', 'body', 'at', 'byUid', 'contactId', 'companyId', 'dealId', 'outcome', 'durationMinutes', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the activity is logged on behalf of.'),\n },\n writeRequired: ['body', 'consentSiteId'],\n fields: {\n id: stringField('Activity id.'),\n object: objectKindField('activity'),\n kind: stringField('What happened, e.g. `call`, `email`, `note`.'),\n body: stringField('Free-form body.'),\n at: isoField('When the interaction happened — not when it was logged.'),\n byUid: nullableField(stringField('User who logged it.')),\n contactId: nullableField(stringField('Associated contact.')),\n companyId: nullableField(stringField('Associated company.')),\n dealId: nullableField(stringField('Associated deal.')),\n outcome: nullableField(stringField('Outcome label.')),\n durationMinutes: nullableField(integerField('Duration, for calls and meetings.')),\n siteId: nullableField(stringField('Site the record originated on.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/activities', method: 'get', operationId: 'listActivities', summary: 'List activities', list: true, returns: 'Activity', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('kind', 'Activity kind.'), queryParam('contactId', 'Associated contact.'), queryParam('dealId', 'Associated deal.')] },\n { path: '/v1/activities', method: 'post', operationId: 'createActivity', summary: 'Log an activity', accepts: 'ActivityWrite', returns: 'Activity', entitlement: 'crm', creates: true },\n { path: '/v1/activities/{activityId}', method: 'get', operationId: 'getActivity', summary: 'Retrieve an activity', returns: 'Activity', entitlement: 'crm', pathParams: [{ name: 'activityId', description: 'Activity id.' }] },\n { path: '/v1/activities/{activityId}', method: 'delete', operationId: 'deleteActivity', summary: 'Delete an activity', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'activityId', description: 'Activity id.' }] },\n ],\n },\n leads: {\n tag: 'Leads',\n description: 'Unqualified interest, before it becomes a contact. A lead is a record of its own: the person and their company as text, until converting makes the contact and the company.',\n schemaName: 'Lead',\n required: ['id', 'object', 'siteId'],\n writable: ['siteId', 'email', 'name', 'status', 'ownerUid', 'ownerEmail', 'notes', 'unqualifiedReason', 'company', 'jobTitle', 'phone', 'website', 'address', 'tags', 'leadSource'],\n writeNote: '`email` and `name` are taken on a create only; a `PATCH` cannot change the address, which is the lead’s identity within its site.',\n writeRequired: ['email'],\n writeOnly: {\n siteId: stringField('The site the lead belongs to, instead of the `siteId` query parameter.'),\n status: stringField('`new`, `nurturing`, `working` or `unqualified` (`nurturing` and `unqualified` on a `PATCH` only). A lead becomes `qualified` by being converted.'),\n ownerEmail: stringField('A member’s address, resolved against the organization’s roster. Not with `ownerUid` in the same request.'),\n },\n fields: {\n id: stringField('Lead id.'),\n object: objectKindField('lead'),\n siteId: stringField('Site the lead arrived on.'),\n email: nullableField(stringField('Email — the lead’s identity within its site.')),\n name: nullableField(stringField('Name, when supplied.')),\n status: stringField('Lead status.'),\n ownerUid: nullableField(stringField('Owning user.')),\n notes: nullableField(stringField('Free-form notes.')),\n unqualifiedReason: nullableField(stringField('Why the lead was disqualified.')),\n company: nullableField(stringField('The company’s name, as text. Converting the lead is what links or creates the company record.')),\n jobTitle: nullableField(stringField('Job title.')),\n phone: nullableField(stringField('E.164 phone number.')),\n website: nullableField(stringField('An http(s) URL.')),\n address: nullableField(postalAddressField()),\n tags: stringListField('Lower-cased tags.'),\n leadSource: nullableField(\n stringField(\n \"Where the lead came from: one of the organization's active lead source values \" +\n '(CRM › Fields › Leads), matched without regard to case. Any other value is refused ' +\n 'with a 400 naming the values allowed; the value a lead already holds is kept even ' +\n \"after it is deactivated. A create that names none starts from the list's default.\",\n ),\n ),\n sources: stringListField('The surfaces that captured the lead: `signup`, `booking`, `form:{formId}`, `import`, `manual`, `api`.'),\n submissionCount: integerField('How many form submissions this lead has made.'),\n firstSeen: isoField('First interaction.'),\n lastSeen: isoField('Most recent interaction.'),\n marketingConsent: booleanField('Whether marketing consent was given.'),\n marketingConsentAt: nullableField(isoField('When consent was given.')),\n convertedContactId: nullableField(stringField('Contact created by conversion.')),\n convertedAt: nullableField(isoField('When it was converted.')),\n companyId: nullableField(stringField('Company created or matched by conversion.')),\n dealId: nullableField(stringField('Deal created by conversion.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/leads', method: 'get', operationId: 'listLeads', summary: 'List leads', list: true, returns: 'Lead', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('siteId', 'Site the lead arrived on.'), queryParam('status', 'Lead status.'), queryParam('ownerUid', 'Owning user.')] },\n { path: '/v1/leads', method: 'post', operationId: 'createLead', summary: 'Create a lead', accepts: 'LeadWrite', returns: 'Lead', entitlement: 'crm', creates: true, description: 'A lead sourced outside the site, entered before anyone has qualified it. One address is one lead per site: creating one the site already holds updates it and answers 200. No contact and no company are created — converting the lead does that — and no marketing consent is recorded.' },\n { path: '/v1/leads/{leadId}', method: 'get', operationId: 'getLead', summary: 'Retrieve a lead', returns: 'Lead', entitlement: 'crm', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n { path: '/v1/leads/{leadId}', method: 'patch', operationId: 'updateLead', summary: 'Update a lead', accepts: 'LeadWrite', returns: 'Lead', entitlement: 'crm', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n { path: '/v1/leads/{leadId}/convert', method: 'post', operationId: 'convertLead', summary: 'Convert a lead', returns: 'LeadConversion', entitlement: 'crm', description: 'Creates a contact, and optionally a company and a deal. Idempotent on the lead: converting an already-converted lead returns the existing ids.', pathParams: [{ name: 'leadId', description: 'Lead id.' }] },\n ],\n components: {\n LeadConversion: {\n type: 'object',\n description:\n 'What the conversion produced. Idempotent on the lead: converting an ' +\n 'already-converted lead returns the existing ids rather than creating ' +\n 'a second set.',\n properties: {\n object: { type: 'string', const: 'lead_conversion' },\n contactId: { type: 'string' },\n companyId: { type: ['string', 'null'] },\n dealId: { type: ['string', 'null'] },\n },\n additionalProperties: true,\n },\n },\n },\n 'email-templates': {\n tag: 'Email templates',\n description: 'Reusable email bodies.',\n schemaName: 'EmailTemplate',\n required: ['id', 'object', 'name'],\n writable: ['name', 'kind', 'visibility', 'ownerUid', 'subject', 'body', 'consentSiteId'],\n writeOnly: {\n consentSiteId: stringField('The site the template is created on behalf of.'),\n },\n writeNote: 'A create needs `name`, `body` and `consentSiteId`; a `PATCH` that names `consentSiteId` is a `400`.',\n fields: {\n id: stringField('Template id.'),\n object: objectKindField('email_template'),\n name: stringField('Template name.'),\n kind: stringField('Template kind.'),\n visibility: stringField('`org` or `private`.'),\n ownerUid: nullableField(stringField('Owner, for a private template.')),\n subject: stringField('Subject line.'),\n body: stringField('Body. May contain merge tokens.'),\n siteId: nullableField(stringField('Site the template belongs to, when scoped.')),\n ...RECORD_STAMPS,\n },\n ops: [\n { path: '/v1/email-templates', method: 'get', operationId: 'listEmailTemplates', summary: 'List email templates', list: true, returns: 'EmailTemplate', entitlement: 'crm', filters: [UPDATED_AFTER_PARAM, queryParam('kind', 'Template kind.'), queryParam('visibility', 'Visibility.')] },\n { path: '/v1/email-templates', method: 'post', operationId: 'createEmailTemplate', summary: 'Create a template', accepts: 'EmailTemplateWrite', returns: 'EmailTemplate', entitlement: 'crm', creates: true },\n { path: '/v1/email-templates/{templateId}', method: 'get', operationId: 'getEmailTemplate', summary: 'Retrieve a template', returns: 'EmailTemplate', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n { path: '/v1/email-templates/{templateId}', method: 'patch', operationId: 'updateEmailTemplate', summary: 'Update a template', accepts: 'EmailTemplateWrite', returns: 'EmailTemplate', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n { path: '/v1/email-templates/{templateId}', method: 'delete', operationId: 'deleteEmailTemplate', summary: 'Delete a template', returns: 'Deleted', entitlement: 'crm', pathParams: [{ name: 'templateId', description: 'Template id.' }] },\n ],\n },\n}\n"],"names":["booleanField","integerField","isoField","nullableField","objectKindField","openObjectField","postalAddressField","queryParam","RECORD_STAMPS","stringField","stringListField","UPDATED_AFTER_PARAM","CRM_API_V1_DESCRIPTIONS","contacts","tag","description","schemaName","required","writable","writeOnly","consentSiteId","consentGroupId","custom","mediaIds","writeNote","fields","id","object","email","name","tags","notes","marketingConsent","consentSites","sources","phone","jobTitle","companyId","address","ownerUid","lifecycleStage","companyIds","alternateEmails","ops","path","method","operationId","summary","list","returns","entitlement","filters","accepts","creates","pathParams","components","ContactMerge","type","properties","sourceContactId","additionalProperties","companies","domain","website","industry","nextTaskAt","siteId","pipelines","isDefault","archived","archivedAt","stages","items","order","deals","title","pipelineId","stageId","status","amountCents","currency","lineItems","expectedCloseAt","closedAt","stageChangedAt","contactId","lostReason","tasks","kind","priority","dueAt","remindAt","reminderSentAt","completedAt","assigneeUid","dealId","activities","writeRequired","body","at","byUid","outcome","durationMinutes","leads","ownerEmail","unqualifiedReason","company","leadSource","submissionCount","firstSeen","lastSeen","marketingConsentAt","convertedContactId","convertedAt","LeadConversion","const","visibility","subject"],"mappings":"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"}
|
|
@@ -70,7 +70,7 @@ export interface CampaignFilingActivitiesInput {
|
|
|
70
70
|
/** The document id a keyed entry files under: the CRM's namespace, never a raw key. */
|
|
71
71
|
export declare function campaignFilingActivityId(key: string): string;
|
|
72
72
|
/**
|
|
73
|
-
* The names of an org's campaigns
|
|
73
|
+
* The names of an org's campaigns — the `campaign` container kind — by id,
|
|
74
74
|
* for the entries that name them. A container that is gone answers its id,
|
|
75
75
|
* so the entry still says which.
|
|
76
76
|
*/
|