@aglyn/plugins-crm 1.0.0 → 1.2.0

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.
Files changed (166) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/activity-link-fields.d.ts +34 -0
  3. package/src/lib/components/activity-link-fields.js +141 -0
  4. package/src/lib/components/activity-link-fields.js.map +1 -0
  5. package/src/lib/components/activity-list.d.ts +8 -0
  6. package/src/lib/components/activity-list.js +4 -2
  7. package/src/lib/components/activity-list.js.map +1 -1
  8. package/src/lib/components/activity-queries.d.ts +7 -2
  9. package/src/lib/components/activity-queries.js +7 -3
  10. package/src/lib/components/activity-queries.js.map +1 -1
  11. package/src/lib/components/company-contact-roles-card.d.ts +42 -0
  12. package/src/lib/components/company-contact-roles-card.js +137 -0
  13. package/src/lib/components/company-contact-roles-card.js.map +1 -0
  14. package/src/lib/components/company-detail-page.js +12 -1
  15. package/src/lib/components/company-detail-page.js.map +1 -1
  16. package/src/lib/components/company-edit-drawer.js +9 -6
  17. package/src/lib/components/company-edit-drawer.js.map +1 -1
  18. package/src/lib/components/company-picker.js +4 -2
  19. package/src/lib/components/company-picker.js.map +1 -1
  20. package/src/lib/components/contact-associations-card.js +15 -1
  21. package/src/lib/components/contact-associations-card.js.map +1 -1
  22. package/src/lib/components/contact-detail-page.js +13 -14
  23. package/src/lib/components/contact-detail-page.js.map +1 -1
  24. package/src/lib/components/contact-known-by-card.d.ts +2 -1
  25. package/src/lib/components/contact-known-by-card.js +16 -3
  26. package/src/lib/components/contact-known-by-card.js.map +1 -1
  27. package/src/lib/components/contact-list-columns.js +5 -4
  28. package/src/lib/components/contact-list-columns.js.map +1 -1
  29. package/src/lib/components/contact-properties-card.js +41 -20
  30. package/src/lib/components/contact-properties-card.js.map +1 -1
  31. package/src/lib/components/contact-roles-editor.d.ts +54 -0
  32. package/src/lib/components/contact-roles-editor.js +270 -0
  33. package/src/lib/components/contact-roles-editor.js.map +1 -0
  34. package/src/lib/components/contacts-section.js +6 -4
  35. package/src/lib/components/contacts-section.js.map +1 -1
  36. package/src/lib/components/crm-console-sections.js +1 -1
  37. package/src/lib/components/crm-console-sections.js.map +1 -1
  38. package/src/lib/components/crm-record-picker.d.ts +19 -0
  39. package/src/lib/components/crm-record-picker.js +40 -11
  40. package/src/lib/components/crm-record-picker.js.map +1 -1
  41. package/src/lib/components/crm-related-to-picker.d.ts +33 -0
  42. package/src/lib/components/crm-related-to-picker.js +135 -0
  43. package/src/lib/components/crm-related-to-picker.js.map +1 -0
  44. package/src/lib/components/crm-site-picker.d.ts +13 -1
  45. package/src/lib/components/crm-site-picker.js +15 -6
  46. package/src/lib/components/crm-site-picker.js.map +1 -1
  47. package/src/lib/components/crm-transfer-buttons.d.ts +5 -0
  48. package/src/lib/components/crm-transfer-buttons.js +9 -6
  49. package/src/lib/components/crm-transfer-buttons.js.map +1 -1
  50. package/src/lib/components/deal-contact-roles-card.js +21 -239
  51. package/src/lib/components/deal-contact-roles-card.js.map +1 -1
  52. package/src/lib/components/deal-detail-page.js +2 -1
  53. package/src/lib/components/deal-detail-page.js.map +1 -1
  54. package/src/lib/components/deal-edit-drawer.js +11 -7
  55. package/src/lib/components/deal-edit-drawer.js.map +1 -1
  56. package/src/lib/components/deals-section.js +5 -4
  57. package/src/lib/components/deals-section.js.map +1 -1
  58. package/src/lib/components/log-activity-dialog.d.ts +27 -9
  59. package/src/lib/components/log-activity-dialog.js +96 -32
  60. package/src/lib/components/log-activity-dialog.js.map +1 -1
  61. package/src/lib/components/new-contact-drawer.d.ts +7 -3
  62. package/src/lib/components/new-contact-drawer.js +27 -15
  63. package/src/lib/components/new-contact-drawer.js.map +1 -1
  64. package/src/lib/components/pipeline-stages-dialog.js +5 -1
  65. package/src/lib/components/pipeline-stages-dialog.js.map +1 -1
  66. package/src/lib/components/record-activity-card.d.ts +11 -2
  67. package/src/lib/components/record-activity-card.js +90 -5
  68. package/src/lib/components/record-activity-card.js.map +1 -1
  69. package/src/lib/components/settings-section.d.ts +4 -2
  70. package/src/lib/components/settings-section.js +7 -5
  71. package/src/lib/components/settings-section.js.map +1 -1
  72. package/src/lib/hooks/crm-activity-client-reads.d.ts +8 -0
  73. package/src/lib/hooks/crm-activity-client-reads.js +50 -0
  74. package/src/lib/hooks/crm-activity-client-reads.js.map +1 -0
  75. package/src/lib/hooks/use-campaign-filing-log.d.ts +2 -1
  76. package/src/lib/hooks/use-campaign-filing-log.js +10 -3
  77. package/src/lib/hooks/use-campaign-filing-log.js.map +1 -1
  78. package/src/lib/hooks/use-crm-org-mount.d.ts +8 -6
  79. package/src/lib/hooks/use-crm-org-mount.js +2 -1
  80. package/src/lib/hooks/use-crm-org-mount.js.map +1 -1
  81. package/src/lib/hooks/use-crm-record-names.d.ts +3 -2
  82. package/src/lib/hooks/use-crm-record-names.js +4 -4
  83. package/src/lib/hooks/use-crm-record-names.js.map +1 -1
  84. package/src/lib/hooks/use-crm-scope.d.ts +17 -5
  85. package/src/lib/hooks/use-crm-scope.js +12 -5
  86. package/src/lib/hooks/use-crm-scope.js.map +1 -1
  87. package/src/lib/model/contact-holder.d.ts +12 -0
  88. package/src/lib/model/contact-holder.js +20 -1
  89. package/src/lib/model/contact-holder.js.map +1 -1
  90. package/src/lib/model/contact-record.d.ts +5 -0
  91. package/src/lib/model/contact-record.js +7 -1
  92. package/src/lib/model/contact-record.js.map +1 -1
  93. package/src/lib/model/contact-update.d.ts +6 -0
  94. package/src/lib/model/contact-update.js +1 -0
  95. package/src/lib/model/contact-update.js.map +1 -1
  96. package/src/lib/model/crm-activity-link.d.ts +117 -0
  97. package/src/lib/model/crm-activity-link.js +113 -0
  98. package/src/lib/model/crm-activity-link.js.map +1 -0
  99. package/src/lib/model/crm-api.d.ts +4 -1
  100. package/src/lib/model/crm-api.js +9 -5
  101. package/src/lib/model/crm-api.js.map +1 -1
  102. package/src/lib/model/deal-board-model.js +5 -3
  103. package/src/lib/model/deal-board-model.js.map +1 -1
  104. package/src/lib/model/deal-contact-roles.d.ts +25 -1
  105. package/src/lib/model/deal-contact-roles.js +30 -4
  106. package/src/lib/model/deal-contact-roles.js.map +1 -1
  107. package/src/lib/model/native-vocabulary.d.ts +9 -0
  108. package/src/lib/model/native-vocabulary.js +13 -0
  109. package/src/lib/model/native-vocabulary.js.map +1 -1
  110. package/src/lib/model/settle-stage-names.d.ts +25 -0
  111. package/src/lib/model/settle-stage-names.js +29 -0
  112. package/src/lib/model/settle-stage-names.js.map +1 -0
  113. package/src/lib/native-nav.js +1 -1
  114. package/src/lib/native-nav.js.map +1 -1
  115. package/src/lib/server/add-contact-by-hand.d.ts +84 -0
  116. package/src/lib/server/add-contact-by-hand.js +212 -0
  117. package/src/lib/server/add-contact-by-hand.js.map +1 -0
  118. package/src/lib/server/api-v1/contacts.js +61 -23
  119. package/src/lib/server/api-v1/contacts.js.map +1 -1
  120. package/src/lib/server/api-v1/crm-activities.js +13 -3
  121. package/src/lib/server/api-v1/crm-activities.js.map +1 -1
  122. package/src/lib/server/api-v1/crm-pipelines.d.ts +1 -1
  123. package/src/lib/server/api-v1/crm-pipelines.js +8 -2
  124. package/src/lib/server/api-v1/crm-pipelines.js.map +1 -1
  125. package/src/lib/server/api-v1/crm-shared.d.ts +16 -9
  126. package/src/lib/server/api-v1/crm-shared.js +23 -11
  127. package/src/lib/server/api-v1/crm-shared.js.map +1 -1
  128. package/src/lib/server/api-v1/openapi.js +3 -3
  129. package/src/lib/server/api-v1/openapi.js.map +1 -1
  130. package/src/lib/server/automation-steps.js +11 -9
  131. package/src/lib/server/automation-steps.js.map +1 -1
  132. package/src/lib/server/campaign-filing-activity.js +7 -2
  133. package/src/lib/server/campaign-filing-activity.js.map +1 -1
  134. package/src/lib/server/contact-update.d.ts +4 -0
  135. package/src/lib/server/contact-update.js +71 -10
  136. package/src/lib/server/contact-update.js.map +1 -1
  137. package/src/lib/server/convert-host-lead.js +13 -2
  138. package/src/lib/server/convert-host-lead.js.map +1 -1
  139. package/src/lib/server/convert-open-lead.js +8 -1
  140. package/src/lib/server/convert-open-lead.js.map +1 -1
  141. package/src/lib/server/crm-activity-company.d.ts +25 -0
  142. package/src/lib/server/crm-activity-company.js +34 -0
  143. package/src/lib/server/crm-activity-company.js.map +1 -0
  144. package/src/lib/server/crm-inbound-email.js +21 -15
  145. package/src/lib/server/crm-inbound-email.js.map +1 -1
  146. package/src/lib/server/crm-sharing.js +9 -2
  147. package/src/lib/server/crm-sharing.js.map +1 -1
  148. package/src/lib/server/deal-contact-roles.d.ts +2 -0
  149. package/src/lib/server/deal-contact-roles.js +38 -1
  150. package/src/lib/server/deal-contact-roles.js.map +1 -1
  151. package/src/lib/server/email-send.js +10 -8
  152. package/src/lib/server/email-send.js.map +1 -1
  153. package/src/lib/server/merge-contacts.js +6 -2
  154. package/src/lib/server/merge-contacts.js.map +1 -1
  155. package/src/lib/server/org-caller.d.ts +8 -0
  156. package/src/lib/server/org-caller.js +10 -0
  157. package/src/lib/server/org-caller.js.map +1 -1
  158. package/src/lib/server/record-timeline.d.ts +3 -1
  159. package/src/lib/server/record-timeline.js +11 -3
  160. package/src/lib/server/record-timeline.js.map +1 -1
  161. package/src/lib/server.js +152 -43
  162. package/src/lib/server.js.map +1 -1
  163. package/src/lib/transfer/contacts.js +83 -48
  164. package/src/lib/transfer/contacts.js.map +1 -1
  165. package/src/lib/transfer/fields.js +1 -2
  166. package/src/lib/transfer/fields.js.map +1 -1
@@ -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 */\n/** How a company picklist field reads in the description (AGL-3514). */\nfunction companyPicklistNote(label: string, plural: string): string {\n return (\n `${label}: one of the organization's active ${plural} (CRM › Fields › Companies), matched ` +\n 'without regard to case. Any other value is refused with a 400 naming the values allowed; ' +\n 'the value a company already holds is kept even after it is deactivated.'\n )\n}\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 'leadSource',\n // Salesforce's standard contact fields (AGL-3515).\n 'salutation', 'firstName', 'lastName', 'department', 'mobilePhone', 'homePhone', 'otherPhone',\n 'fax', 'birthdate', 'assistantName', 'assistantPhone', 'reportsToContactId', 'otherAddress', 'doNotCall',\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 leadSource: nullableField(stringField('Lead source: one of the organization’s Lead source values (CRM › Fields › Leads), stored on the named site’s profile. A value the list does not hold is a `400` naming the values it allows.')),\n // Salesforce's standard contact fields (AGL-3515).\n salutation: nullableField(stringField(\"One of the organization's salutation values (Mr., Ms., Mrs., Dr., Prof. and its own).\")),\n firstName: nullableField(stringField('First name. With `lastName`, it makes the name the named site shows.')),\n lastName: nullableField(stringField('Last name.')),\n department: nullableField(stringField('Department.')),\n mobilePhone: nullableField(stringField('Mobile phone, E.164.')),\n homePhone: nullableField(stringField('Home phone, E.164.')),\n otherPhone: nullableField(stringField('Other phone, E.164.')),\n fax: nullableField(stringField('Fax, E.164.')),\n birthdate: nullableField(stringField('Birthdate, `YYYY-MM-DD`, never in the future.')),\n assistantName: nullableField(stringField(\"Assistant's name.\")),\n assistantPhone: nullableField(stringField(\"Assistant's phone, E.164.\")),\n reportsToContactId: nullableField(stringField('The contact this person reports to. Never the contact itself, and never one that already reports to it.')),\n otherAddress: nullableField(postalAddressField()),\n doNotCall: booleanField('The person asked not to be phoned.'),\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: [\n 'name', 'domain', 'website', 'phone', 'address', 'industry', 'ownerUid', 'notes', 'custom', 'mediaIds', 'consentSiteId',\n // Salesforce's Account fields (AGL-3514).\n 'type', 'rating', 'ownership', 'accountSource', 'annualRevenueCents', 'currency', 'numberOfEmployees', 'fax',\n 'accountNumber', 'site', 'tickerSymbol', 'sicCode', 'shippingAddress', 'parentCompanyId',\n ],\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(), description: 'Billing address. Members are optional and free-form.' },\n industry: nullableField(stringField(companyPicklistNote('Industry', 'industries'))),\n // Salesforce's Account fields (AGL-3514).\n type: nullableField(stringField(companyPicklistNote('Account type', 'types'))),\n rating: nullableField(stringField(companyPicklistNote('Rating', 'ratings'))),\n ownership: nullableField(stringField(companyPicklistNote('Ownership', 'ownership values'))),\n accountSource: nullableField(\n stringField(\n \"Where the account 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 company already holds is kept.',\n ),\n ),\n annualRevenueCents: nullableField(integerField('Annual revenue in the minor unit of `currency`, 0 or more.')),\n currency: stringField('Lowercase ISO 4217 code of the annual revenue. `usd` when unset.'),\n numberOfEmployees: nullableField(integerField('Number of employees, 0 to 99,999,999.')),\n fax: nullableField(stringField('E.164 fax number.')),\n accountNumber: nullableField(stringField('Account number, at most 40 characters.')),\n site: nullableField(stringField('Which of the company’s locations this record is, at most 80 characters.')),\n tickerSymbol: nullableField(stringField('Stock ticker symbol, at most 20 characters.')),\n sicCode: nullableField(stringField('Standard Industrial Classification code, at most 20 characters.')),\n shippingAddress: { ...postalAddressField(), description: 'Shipping address. Members are optional and free-form.' },\n parentCompanyId: nullableField(\n stringField('The company this one sits under. Never itself or a company below it; deleting the parent clears it.'),\n ),\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: {\n id: { type: 'string' },\n name: { type: 'string' },\n order: { type: 'integer' },\n probability: { type: 'integer', description: 'Chance of closing from this stage, 0–100.' },\n kind: { type: 'string', enum: ['open', 'won', 'lost'] },\n forecastCategory: {\n type: 'string',\n enum: ['omitted', 'pipeline', 'bestCase', 'commit', 'closed'],\n description: 'The forecast category a deal takes on landing in this stage.',\n },\n },\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', 'type', 'leadSource', 'nextStep', 'probability', 'forecastCategory', 'campaignId', 'contactRoles'],\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:\n '`pipelineId` and `consentSiteId` are accepted on create only; a `PATCH` that names either is a `400`. ' +\n '`type` and `leadSource` must be active values of the organization’s lists (a deal keeps the value it holds). ' +\n 'A stage move sets `forecastCategory` from the new stage and clears `probability`, unless the same body sets them. ' +\n '`contactRoles` replaces the whole list: each contact once, at most 50, at most one `primary`, each `role` an active value of the organization’s contact roles. ' +\n 'The Primary is `contactId`: a body sending both must agree, and `contactId` alone makes that contact Primary.',\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 — the Primary of `contactRoles`.')),\n contactRoles: {\n type: 'array',\n description:\n 'Every contact on the deal and the part each plays (Salesforce’s Opportunity Contact Roles). At most one is `primary`, and it is `contactId`.',\n items: {\n type: 'object',\n required: ['contactId', 'primary'],\n properties: {\n contactId: stringField('The contact.'),\n role: nullableField(stringField('One of the organization’s contact roles (Decision Maker, Evaluator, …), or `null` for none.')),\n primary: booleanField('Whether this is the deal’s Primary contact.'),\n },\n },\n },\n companyId: nullableField(stringField('Associated company.')),\n lostReason: nullableField(stringField('Why it was lost.')),\n notes: nullableField(stringField('Free-form notes.')),\n type: nullableField(stringField('Type — one of the organization’s deal types (New Business, Existing Business, …).')),\n leadSource: nullableField(stringField('Lead source — one of the organization’s lead sources.')),\n nextStep: nullableField(stringField('What happens next. At most 255 characters.')),\n probability: nullableField(integerField('This deal’s own chance of closing, 0–100; `null` uses its stage’s. A stage move clears it.')),\n forecastCategory: nullableField(stringField('`omitted`, `pipeline`, `bestCase`, `commit` or `closed`. Every stage move sets it from the new stage.')),\n campaignId: nullableField(stringField('The campaign the deal is attributed to.')),\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.'), queryParam('type', 'Type, exactly as stored.'), queryParam('leadSource', 'Lead source, exactly as stored.'), queryParam('forecastCategory', 'Forecast category.'), queryParam('campaignId', 'Campaign.')] },\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 type as its meaning: `call`, `email`, `meeting` or `todo`. A write takes the meaning or a label of the Type picklist.'),\n typeLabel: stringField('The organization’s Type picklist label, e.g. `Call`. Read-only; set through `kind`.'),\n priority: stringField('`low`, `normal` or `high`. A write takes the meaning or a label of the Priority picklist.'),\n priorityLabel: stringField('The organization’s Priority picklist label, e.g. `High`. Read-only; set through `priority`.'),\n status: stringField('`open` or `done`. A write takes the meaning or a label of the Status picklist, e.g. `In Progress`.'),\n statusLabel: stringField('The organization’s Status picklist label, e.g. `Not Started`. Read-only; set through `status`.'),\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', 'direction', '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 direction: nullableField(stringField('Which way it went: `inbound`, `outbound` or `internal` for a call, `inbound` or `outbound` for an email.')),\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: [\n 'siteId', 'email', 'name', 'status', 'ownerUid', 'ownerEmail', 'notes', 'unqualifiedReason', 'company', 'jobTitle', 'phone', 'website', 'address', 'tags', 'leadSource',\n // Salesforce's standard lead fields (AGL-3513).\n 'salutation', 'firstName', 'lastName', 'mobilePhone', 'fax', 'doNotCall', 'industry', 'rating', 'annualRevenueCents', 'currency', 'numberOfEmployees',\n ],\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. While `firstName` or `lastName` is set, `name` is their composition, \"First Last\", and follows them.',\n writeRequired: ['email'],\n writeOnly: {\n siteId: stringField('The site the lead belongs to, instead of the `siteId` query parameter.'),\n status: stringField('One of the organization’s active lead status values by its label (CRM › Fields › Leads), or a meaning — `new`, `nurturing`, `working` or `unqualified` (`nurturing` and `unqualified` on a `PATCH` only). Stored as the meaning and the label together. 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('What the lead status means: `new`, `nurturing`, `working`, `qualified` or `unqualified`. What every filter and automation reads.'),\n statusLabel: stringField('The organization’s label for the lead status value the lead holds — one of `status`’s meaning. Read-only; set it through `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 // Salesforce's standard lead fields (AGL-3513).\n salutation: nullableField(stringField('Salutation: one of the organization’s active Salutation values (CRM › Fields › Contacts), matched without regard to case. Any other value is a `400` naming the values allowed; the value a lead already holds is kept.')),\n firstName: nullableField(stringField('First name, at most 59 characters. While it or `lastName` is set, `name` is \"First Last\".')),\n lastName: nullableField(stringField('Last name, at most 59 characters.')),\n mobilePhone: nullableField(stringField('E.164 mobile phone number.')),\n fax: nullableField(stringField('E.164 fax number.')),\n doNotCall: booleanField('The person asked not to be phoned. A hint on every call control, never a block. `false` clears it.'),\n industry: nullableField(stringField('Industry: one of the organization’s active Industry values (CRM › Fields › Companies) — the list companies keep, so converting carries it. Any other value is a `400` naming the values allowed; the value a lead already holds is kept.')),\n rating: nullableField(stringField('Rating: one of the organization’s active Rating values (CRM › Fields › Companies). Any other value is a `400` naming the values allowed.')),\n annualRevenueCents: nullableField(integerField('Annual revenue in the minor unit of `currency`, 0 or more.')),\n currency: stringField('Lowercase ISO 4217 code of the annual revenue. `usd` when unset.'),\n numberOfEmployees: nullableField(integerField('Number of employees, 0 to 99,999,999.')),\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","companyPicklistNote","label","plural","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","leadSource","salutation","firstName","lastName","department","mobilePhone","homePhone","otherPhone","fax","birthdate","assistantName","assistantPhone","reportsToContactId","otherAddress","doNotCall","companyIds","alternateEmails","ops","path","method","operationId","summary","list","returns","entitlement","filters","accepts","creates","pathParams","components","ContactMerge","type","properties","sourceContactId","additionalProperties","companies","domain","website","industry","rating","ownership","accountSource","annualRevenueCents","currency","numberOfEmployees","accountNumber","site","tickerSymbol","sicCode","shippingAddress","parentCompanyId","nextTaskAt","siteId","pipelines","isDefault","archived","archivedAt","stages","items","order","probability","kind","enum","forecastCategory","deals","title","pipelineId","stageId","status","amountCents","lineItems","expectedCloseAt","closedAt","stageChangedAt","contactId","contactRoles","role","primary","lostReason","nextStep","campaignId","tasks","typeLabel","priority","priorityLabel","statusLabel","dueAt","remindAt","reminderSentAt","completedAt","assigneeUid","dealId","activities","writeRequired","body","at","byUid","outcome","durationMinutes","direction","leads","ownerEmail","unqualifiedReason","company","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 */\n/** How a company picklist field reads in the description (AGL-3514). */\nfunction companyPicklistNote(label: string, plural: string): string {\n return (\n `${label}: one of the organization's active ${plural} (CRM › Fields › Companies), matched ` +\n 'without regard to case. Any other value is refused with a 400 naming the values allowed; ' +\n 'the value a company already holds is kept even after it is deactivated.'\n )\n}\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 'leadSource',\n // Salesforce's standard contact fields (AGL-3515).\n 'salutation', 'firstName', 'lastName', 'department', 'mobilePhone', 'homePhone', 'otherPhone',\n 'fax', 'birthdate', 'assistantName', 'assistantPhone', 'reportsToContactId', 'otherAddress', 'doNotCall',\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: 'A create needs an `email`, a `name`, a `phone`, a `mobilePhone`, or a `firstName` or `lastName`. `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: nullableField(\n stringField('Primary email, unique per organization, or `null` for a contact with no address yet.'),\n ),\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 leadSource: nullableField(stringField('Lead source: one of the organization’s Lead source values (CRM › Fields › Leads), stored on the named site’s profile. A value the list does not hold is a `400` naming the values it allows.')),\n // Salesforce's standard contact fields (AGL-3515).\n salutation: nullableField(stringField(\"One of the organization's salutation values (Mr., Ms., Mrs., Dr., Prof. and its own).\")),\n firstName: nullableField(stringField('First name. With `lastName`, it makes the name the named site shows.')),\n lastName: nullableField(stringField('Last name.')),\n department: nullableField(stringField('Department.')),\n mobilePhone: nullableField(stringField('Mobile phone, E.164.')),\n homePhone: nullableField(stringField('Home phone, E.164.')),\n otherPhone: nullableField(stringField('Other phone, E.164.')),\n fax: nullableField(stringField('Fax, E.164.')),\n birthdate: nullableField(stringField('Birthdate, `YYYY-MM-DD`, never in the future.')),\n assistantName: nullableField(stringField(\"Assistant's name.\")),\n assistantPhone: nullableField(stringField(\"Assistant's phone, E.164.\")),\n reportsToContactId: nullableField(stringField('The contact this person reports to. Never the contact itself, and never one that already reports to it.')),\n otherAddress: nullableField(postalAddressField()),\n doNotCall: booleanField('The person asked not to be phoned.'),\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: [\n 'name', 'domain', 'website', 'phone', 'address', 'industry', 'ownerUid', 'notes', 'custom', 'mediaIds', 'consentSiteId',\n // Salesforce's Account fields (AGL-3514).\n 'type', 'rating', 'ownership', 'accountSource', 'annualRevenueCents', 'currency', 'numberOfEmployees', 'fax',\n 'accountNumber', 'site', 'tickerSymbol', 'sicCode', 'shippingAddress', 'parentCompanyId',\n ],\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(), description: 'Billing address. Members are optional and free-form.' },\n industry: nullableField(stringField(companyPicklistNote('Industry', 'industries'))),\n // Salesforce's Account fields (AGL-3514).\n type: nullableField(stringField(companyPicklistNote('Account type', 'types'))),\n rating: nullableField(stringField(companyPicklistNote('Rating', 'ratings'))),\n ownership: nullableField(stringField(companyPicklistNote('Ownership', 'ownership values'))),\n accountSource: nullableField(\n stringField(\n \"Where the account 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 company already holds is kept.',\n ),\n ),\n annualRevenueCents: nullableField(integerField('Annual revenue in the minor unit of `currency`, 0 or more.')),\n currency: stringField('Lowercase ISO 4217 code of the annual revenue. `usd` when unset.'),\n numberOfEmployees: nullableField(integerField('Number of employees, 0 to 99,999,999.')),\n fax: nullableField(stringField('E.164 fax number.')),\n accountNumber: nullableField(stringField('Account number, at most 40 characters.')),\n site: nullableField(stringField('Which of the company’s locations this record is, at most 80 characters.')),\n tickerSymbol: nullableField(stringField('Stock ticker symbol, at most 20 characters.')),\n sicCode: nullableField(stringField('Standard Industrial Classification code, at most 20 characters.')),\n shippingAddress: { ...postalAddressField(), description: 'Shipping address. Members are optional and free-form.' },\n parentCompanyId: nullableField(\n stringField('The company this one sits under. Never itself or a company below it; deleting the parent clears it.'),\n ),\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: {\n id: { type: 'string' },\n name: { type: 'string' },\n order: { type: 'integer' },\n probability: { type: 'integer', description: 'Chance of closing from this stage, 0–100.' },\n kind: { type: 'string', enum: ['open', 'won', 'lost'] },\n forecastCategory: {\n type: 'string',\n enum: ['omitted', 'pipeline', 'bestCase', 'commit', 'closed'],\n description: 'The forecast category a deal takes on landing in this stage.',\n },\n },\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', 'type', 'leadSource', 'nextStep', 'probability', 'forecastCategory', 'campaignId', 'contactRoles'],\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:\n '`pipelineId` and `consentSiteId` are accepted on create only; a `PATCH` that names either is a `400`. ' +\n '`type` and `leadSource` must be active values of the organization’s lists (a deal keeps the value it holds). ' +\n 'A stage move sets `forecastCategory` from the new stage and clears `probability`, unless the same body sets them. ' +\n '`contactRoles` replaces the whole list: each contact once, at most 50, at most one `primary`, each `role` an active value of the organization’s contact roles. ' +\n 'The Primary is `contactId`: a body sending both must agree, and `contactId` alone makes that contact Primary.',\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 — the Primary of `contactRoles`.')),\n contactRoles: {\n type: 'array',\n description:\n 'Every contact on the deal and the part each plays (Salesforce’s Opportunity Contact Roles). At most one is `primary`, and it is `contactId`.',\n items: {\n type: 'object',\n required: ['contactId', 'primary'],\n properties: {\n contactId: stringField('The contact.'),\n role: nullableField(stringField('One of the organization’s contact roles (Decision Maker, Evaluator, …), or `null` for none.')),\n primary: booleanField('Whether this is the deal’s Primary contact.'),\n },\n },\n },\n companyId: nullableField(stringField('Associated company.')),\n lostReason: nullableField(stringField('Why it was lost.')),\n notes: nullableField(stringField('Free-form notes.')),\n type: nullableField(stringField('Type — one of the organization’s deal types (New Business, Existing Business, …).')),\n leadSource: nullableField(stringField('Lead source — one of the organization’s lead sources.')),\n nextStep: nullableField(stringField('What happens next. At most 255 characters.')),\n probability: nullableField(integerField('This deal’s own chance of closing, 0–100; `null` uses its stage’s. A stage move clears it.')),\n forecastCategory: nullableField(stringField('`omitted`, `pipeline`, `bestCase`, `commit` or `closed`. Every stage move sets it from the new stage.')),\n campaignId: nullableField(stringField('The campaign the deal is attributed to.')),\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.'), queryParam('type', 'Type, exactly as stored.'), queryParam('leadSource', 'Lead source, exactly as stored.'), queryParam('forecastCategory', 'Forecast category.'), queryParam('campaignId', 'Campaign.')] },\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 type as its meaning: `call`, `email`, `meeting` or `todo`. A write takes the meaning or a label of the Type picklist.'),\n typeLabel: stringField('The organization’s Type picklist label, e.g. `Call`. Read-only; set through `kind`.'),\n priority: stringField('`low`, `normal` or `high`. A write takes the meaning or a label of the Priority picklist.'),\n priorityLabel: stringField('The organization’s Priority picklist label, e.g. `High`. Read-only; set through `priority`.'),\n status: stringField('`open` or `done`. A write takes the meaning or a label of the Status picklist, e.g. `In Progress`.'),\n statusLabel: stringField('The organization’s Status picklist label, e.g. `Not Started`. Read-only; set through `status`.'),\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', 'direction', '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. When none is sent, the company of the deal, else of the contact.')),\n dealId: nullableField(stringField('Associated deal.')),\n outcome: nullableField(stringField('Outcome label.')),\n durationMinutes: nullableField(integerField('Duration, for calls and meetings.')),\n direction: nullableField(stringField('Which way it went: `inbound`, `outbound` or `internal` for a call, `inbound` or `outbound` for an email.')),\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: [\n 'siteId', 'email', 'name', 'status', 'ownerUid', 'ownerEmail', 'notes', 'unqualifiedReason', 'company', 'jobTitle', 'phone', 'website', 'address', 'tags', 'leadSource',\n // Salesforce's standard lead fields (AGL-3513).\n 'salutation', 'firstName', 'lastName', 'mobilePhone', 'fax', 'doNotCall', 'industry', 'rating', 'annualRevenueCents', 'currency', 'numberOfEmployees',\n ],\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. While `firstName` or `lastName` is set, `name` is their composition, \"First Last\", and follows them.',\n writeRequired: ['email'],\n writeOnly: {\n siteId: stringField('The site the lead belongs to, instead of the `siteId` query parameter.'),\n status: stringField('One of the organization’s active lead status values by its label (CRM › Fields › Leads), or a meaning — `new`, `nurturing`, `working` or `unqualified` (`nurturing` and `unqualified` on a `PATCH` only). Stored as the meaning and the label together. 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('What the lead status means: `new`, `nurturing`, `working`, `qualified` or `unqualified`. What every filter and automation reads.'),\n statusLabel: stringField('The organization’s label for the lead status value the lead holds — one of `status`’s meaning. Read-only; set it through `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 // Salesforce's standard lead fields (AGL-3513).\n salutation: nullableField(stringField('Salutation: one of the organization’s active Salutation values (CRM › Fields › Contacts), matched without regard to case. Any other value is a `400` naming the values allowed; the value a lead already holds is kept.')),\n firstName: nullableField(stringField('First name, at most 59 characters. While it or `lastName` is set, `name` is \"First Last\".')),\n lastName: nullableField(stringField('Last name, at most 59 characters.')),\n mobilePhone: nullableField(stringField('E.164 mobile phone number.')),\n fax: nullableField(stringField('E.164 fax number.')),\n doNotCall: booleanField('The person asked not to be phoned. A hint on every call control, never a block. `false` clears it.'),\n industry: nullableField(stringField('Industry: one of the organization’s active Industry values (CRM › Fields › Companies) — the list companies keep, so converting carries it. Any other value is a `400` naming the values allowed; the value a lead already holds is kept.')),\n rating: nullableField(stringField('Rating: one of the organization’s active Rating values (CRM › Fields › Companies). Any other value is a `400` naming the values allowed.')),\n annualRevenueCents: nullableField(integerField('Annual revenue in the minor unit of `currency`, 0 or more.')),\n currency: stringField('Lowercase ISO 4217 code of the annual revenue. `usd` when unset.'),\n numberOfEmployees: nullableField(integerField('Number of employees, 0 to 99,999,999.')),\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","companyPicklistNote","label","plural","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","leadSource","salutation","firstName","lastName","department","mobilePhone","homePhone","otherPhone","fax","birthdate","assistantName","assistantPhone","reportsToContactId","otherAddress","doNotCall","companyIds","alternateEmails","ops","path","method","operationId","summary","list","returns","entitlement","filters","accepts","creates","pathParams","components","ContactMerge","type","properties","sourceContactId","additionalProperties","companies","domain","website","industry","rating","ownership","accountSource","annualRevenueCents","currency","numberOfEmployees","accountNumber","site","tickerSymbol","sicCode","shippingAddress","parentCompanyId","nextTaskAt","siteId","pipelines","isDefault","archived","archivedAt","stages","items","order","probability","kind","enum","forecastCategory","deals","title","pipelineId","stageId","status","amountCents","lineItems","expectedCloseAt","closedAt","stageChangedAt","contactId","contactRoles","role","primary","lostReason","nextStep","campaignId","tasks","typeLabel","priority","priorityLabel","statusLabel","dueAt","remindAt","reminderSentAt","completedAt","assigneeUid","dealId","activities","writeRequired","body","at","byUid","outcome","durationMinutes","direction","leads","ownerEmail","unqualifiedReason","company","submissionCount","firstSeen","lastSeen","marketingConsentAt","convertedContactId","convertedAt","LeadConversion","const","visibility","subject"],"mappings":"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"}
@@ -28,6 +28,7 @@ import { CRM_SUITE_FEATURE } from "./suite-gate.js";
28
28
  import { crmTaskReminderAfterEdit } from "../model/crm-task-reminders.js";
29
29
  import { recomputeCrmNextTaskAt } from "./crm-next-activity.js";
30
30
  import { newCrmActivityRef, writeCrmEmailActivity } from "./crm-email-activity.js";
31
+ import { stampCrmActivityCompany } from "../model/crm-activity-link.js";
31
32
  const DAY_MS = 24 * 60 * 60 * 1000;
32
33
  /**
33
34
  * The contact an event names, as this site may see it.
@@ -294,11 +295,12 @@ const DAY_MS = 24 * 60 * 60 * 1000;
294
295
  };
295
296
  const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(env.orgId);
296
297
  const visibleTo = crmScopeTokens((_env_org = env.org) != null ? _env_org : null, group);
297
- const links = _extends({
298
+ const links = stampCrmActivityCompany({
298
299
  contactId: contact.id
299
- }, facet.companyId ? {
300
- companyId: facet.companyId
301
- } : {});
300
+ }, {
301
+ contact: contact.data,
302
+ groupId: group.groupId
303
+ });
302
304
  if (step.type === 'createCrmTask') {
303
305
  var _step_title, _facet_ownerUid;
304
306
  var _step_assigneeUid, _step_assigneeEmail;
@@ -617,14 +619,14 @@ const DAY_MS = 24 * 60 * 60 * 1000;
617
619
  return null;
618
620
  }
619
621
  const group = await consentGroupForSite(hostId);
620
- const facet = person.kind === 'contact' ? readContactFacet(person.data, group.groupId) : null;
621
622
  const link = person.kind === 'lead' ? {
622
623
  leadId: person.id
623
- } : _extends({
624
+ } : stampCrmActivityCompany({
624
625
  contactId: person.id
625
- }, (facet == null ? void 0 : facet.companyId) ? {
626
- companyId: facet.companyId
627
- } : {});
626
+ }, {
627
+ contact: person.data,
628
+ groupId: group.groupId
629
+ });
628
630
  if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) {
629
631
  return null;
630
632
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/automation-steps.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 buildCrmEmailActivity,\n checkEntitlement,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_TAG_MAX_LENGTH,\n contactFacetPath,\n CRM_ACTIVITY_LOG_FULL_MESSAGE,\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActivity,\n type CrmActivityDirection,\n crmActivityDirection,\n type CrmActivityLink,\n type CrmTask,\n type CrmTaskPriority,\n crmEmailDeliveryTags,\n crmScopeTokens,\n crmTaskLabelsForNew,\n crmTaskListFields,\n normalizeContactEmail,\n parseCrmMemberRef,\n personKey,\n planLabelGrantingFeature,\n readContactFacet,\n visibleToHost,\n} from '@aglyn/aglyn/server'\nimport type {\n PluginRecordEmailPrepareRequest,\n PluginRecordPreparedEmail,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport type {\n ServerStepAnswer,\n ServerStepRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport {\n consentGroupForSite,\n countCrmActivitiesForRecord,\n firebaseAdmin,\n orgDataQueryForHost,\n restampCrmListFieldsAt,\n} from '@aglyn/tenant-data-admin'\n// The leaf, not the barrel: the specs substitute the barrel wholesale, and\n// the lookup must reach the real index logic under them.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { FieldValue } from 'firebase-admin/firestore'\n// The leaf, not the runtime barrel: the specs substitute it.\nimport {\n OWNER_ASSIGNMENT_REFUSALS,\n reassignContactOwner,\n reassignLeadOwner,\n} from './assign-contact-owner'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { InteractionStepGuard } from '@aglyn/aglyn/app-utils/site-interactions'\nimport type { ContactLifecycleStage, CrmActivityKind, CrmTaskKind } from '@aglyn/aglyn/app-utils/crm-kinds'\nimport type { CRM_STEP_TYPES } from '../constants/bundle-common'\nimport { readCrmTaskPicklists } from './read-picklist'\nimport { CRM_SUITE_FEATURE } from './suite-gate'\nimport { crmTaskReminderAfterEdit } from '../model/crm-task-reminders'\nimport { recomputeCrmNextTaskAt } from './crm-next-activity'\nimport { newCrmActivityRef, writeCrmEmailActivity } from './crm-email-activity'\n\n/**\n * THE AUTOMATION STEPS THAT WRITE THE CRM (AGL-2605), run for the automation\n * engine through the platform's server-step seam (`plugin-server-steps`,\n * AGL-3080): `setContactStage`, `addContactTag`, `assignContactOwner`,\n * `createCrmTask` and `logCrmActivity`.\n *\n * The engine keeps the step's guard, the run's order, its history and the\n * nesting cap; this answers what the step did. A refusal is ANSWERED as\n * `error`, never thrown, and its words are the line the run history carries —\n * the same words the engine wrote when these steps were its own.\n *\n * The five share one shape no other step has: each begins by finding THE\n * PERSON the event is about, and each writes either inside the site's facet on\n * that person or a record stamped with the site's scope. One resolver and one\n * scope expression, used five times, rather than five copies drifting apart.\n *\n * ## The person is the event's, never the step's\n *\n * A step carries no contact reference. The contact is whoever the event\n * names — by `contactId` when the emitting door knew the document, and by\n * `email` otherwise, which is what every pre-CRM event carries. Both lookups\n * are scoped to what THIS site may see: a document the id names but the site\n * cannot read is treated as absent, exactly as the scoped query would have\n * treated it, so an event replayed against the wrong site reaches nobody.\n *\n * ## A missing person is a reported no-op\n *\n * Nothing is written and the step's error names the reason, which lands in\n * the run summary beside the trigger — the same place \"why didn't it fire?\"\n * is answered for every other step. Silence here would make a mis-wired\n * trigger (a form with no email field) indistinguishable from a working one.\n *\n * ## The plan gate is the CRM's\n *\n * Every step is refused on a workspace whose plan does not carry the CRM,\n * into the run history with the tier that carries it, so a Free workspace\n * whose flow names a CRM step reads why the step did nothing rather than a\n * log that says it ran. The org document is the one the run's gate already\n * read, handed over with the step, so the check costs no read.\n *\n * The same module answers the record timeline's `prepareEmail` (AGL-2615): an\n * automation's email addressed to the contact the event is about is filed on\n * that contact's timeline — see {@link prepareCrmRecordEmail}.\n */\n\n/** One of the steps this module runs. */\nexport type CrmStepType = (typeof CRM_STEP_TYPES)[number]\n\n/**\n * A CRM step, as it is stored: the CRM's own shapes of the steps it runs. The\n * automation editor writes them and checks them; `apps/console/specs/\n * crm-automation-steps.spec.ts` runs what it writes through this module.\n */\nexport type CrmAutomationStep = (\n | { type: 'setContactStage'; lifecycleStage: ContactLifecycleStage }\n | { type: 'addContactTag'; tag: string }\n | { type: 'assignContactOwner'; ownerUid?: string; ownerEmail?: string; roundRobin?: boolean }\n | {\n type: 'createCrmTask'\n title: string\n kind: CrmTaskKind\n /** `normal` when absent — the steps written before priorities were (AGL-3517). */\n priority?: CrmTaskPriority\n dueInDays: number\n assigneeUid?: string\n assigneeEmail?: string\n }\n | {\n type: 'logCrmActivity'\n kind: CrmActivityKind\n body: string\n /** Which way a call or an email went (AGL-3517). */\n direction?: CrmActivityDirection\n }\n) & { when?: InteractionStepGuard | null } & { type: CrmStepType }\n\n/** The site, the plan and the org a step or an email is answered for. */\ninterface CrmStepEnv {\n hostId: string\n /** The owning org's billing doc, already read by the run's gate. */\n org: unknown\n orgId: string | null\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\ninterface ResolvedContact {\n id: string\n ref: FirebaseFirestore.DocumentReference\n data: Record<string, unknown>\n}\n\n/**\n * The person a CRM step acts on: a contact, or — when the workspace holds\n * none for them — the LEAD the site filed (AGL-3458).\n *\n * A lead-routed form files a lead and no contact (AGL-3232), and announces it\n * with the `lead` event. An automation welcoming that person — assign an\n * owner, book a call, tag them — has only the lead to act on, so each step\n * that has a lead's equivalent writes it there: the lead's own `ownerUid`,\n * its own `tags`, a task or an activity filed under `leadId`. A stage is a\n * contact's alone, and a step setting one on a lead says so.\n */\ntype ResolvedPerson =\n | ({ kind: 'contact' } & ResolvedContact)\n | ({ kind: 'lead' } & ResolvedContact)\n\n/**\n * The contact an event names, as this site may see it.\n *\n * Id first, because a CRM event carries the document the door just wrote and\n * a lookup by id is a read, not a query. The address second, through the\n * org's address index narrowed to this site (AGL-2633), for every event that\n * predates the CRM and knows only who filled in the form — so an address a\n * merge folded into another record still reaches the person who now holds it.\n */\nasync function resolveEventContact(\n hostId: string,\n named: { contactId?: unknown; email?: unknown },\n): Promise<ResolvedContact | null> {\n const { ref: contactsRef } = await orgDataQueryForHost(hostId, 'contacts')\n const contactId = String(named.contactId ?? '').trim()\n if (contactId) {\n const doc = await contactsRef.doc(contactId).get()\n if (doc.exists && visibleToHost(doc.get('visibleTo'), hostId)) {\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n }\n }\n const hit = await findContactByEmail(contactsRef, named.email, { hostId })\n return hit ? { id: hit.id, ref: hit.ref, data: hit.data() ?? {} } : null\n}\n\n/**\n * The lead an event names, as this site may see it: by `leadId` — the `lead`\n * event carries it, and it is the person's key — else by the key of the\n * `email`. A row this site cannot see is absent, exactly as a contact's is.\n */\nasync function resolveEventLead(\n hostId: string,\n named: { leadId?: unknown; email?: unknown },\n): Promise<ResolvedContact | null> {\n const key = String(named.leadId ?? '').trim() || personKey(named.email) || ''\n if (!key) return null\n const { ref: leadsRef } = await orgDataQueryForHost(hostId, 'leads')\n const doc = await leadsRef.doc(key).get()\n if (!doc.exists || !visibleToHost(doc.get('visibleTo'), hostId)) return null\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n}\n\n/** The contact the event names, else its lead — see {@link ResolvedPerson}. */\nasync function resolveEventPerson(\n hostId: string,\n named: { contactId?: unknown; leadId?: unknown; email?: unknown },\n): Promise<ResolvedPerson | null> {\n const contact = await resolveEventContact(hostId, named)\n if (contact) return { kind: 'contact', ...contact }\n const lead = await resolveEventLead(hostId, named)\n return lead ? { kind: 'lead', ...lead } : null\n}\n\n/**\n * The uid a step names for somebody on the team — an owner, an assignee —\n * resolved against the org's roster, whichever way the step named them.\n *\n * A step names a member by uid when a picker wrote it and by address when a\n * person typed it, and the editor's one text field accepts either — so both\n * fields are read through `parseCrmMemberRef`, and an address typed into the\n * uid slot or a uid typed into the address slot still names the person.\n *\n * Both are RESOLVED, not trusted. `orgs/{orgId}/members/{uid}` is keyed by the\n * uid, so a uid costs one document read and must exist: `ownerUid` and\n * `assigneeUid` are fields every reader resolves as a member, and a stranger's\n * uid in one is a task nobody on the team can find. An address is matched on\n * the roster's own `email` field, and two production paths create a member\n * document WITHOUT one (a host-access re-grant, and an add whose auth record\n * carried none) — such a member is named by uid, which is the reason the\n * editor takes one.\n *\n * The roster is the only directory consulted. A project-level Auth lookup by\n * address would resolve people who are not on this organization at all\n * (AGL-1122). A reference that resolves neither way is an error, not a stored\n * string.\n */\nasync function resolveMemberUid(\n orgId: string | null,\n named: { uid?: string; email?: string },\n role: 'owner' | 'assignee',\n): Promise<{ uid: string; detail: string } | { error: string }> {\n const ref = parseCrmMemberRef(named.uid) ?? parseCrmMemberRef(named.email)\n if (!ref) return { error: `no ${role} named on the step` }\n if (!orgId) return { error: 'this site has no organization' }\n const members = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('members')\n if (ref.kind === 'uid') {\n const member = await members.doc(ref.uid).get()\n if (member.exists) return { uid: ref.uid, detail: ref.uid }\n return { error: `no team member with the id ${ref.uid}` }\n }\n const byField = (await members.where('email', '==', ref.email).limit(1).get()).docs[0]\n if (byField) return { uid: byField.id, detail: ref.email }\n return { error: `no team member with the address ${ref.email}` }\n}\n\n/**\n * Runs one CRM step for the person the event names: the executor the CRM\n * registers on the server-step seam for every type in `CRM_STEP_TYPES`.\n *\n * Never throws for a plan without the CRM, a missing person, an unknown owner\n * or a site with no org — those are answered as `error` so the run records\n * them; a Firestore failure propagates, and the engine records it the same\n * way.\n */\nexport async function runCrmAutomationStep(\n request: ServerStepRequest,\n nowMs = Date.now(),\n): Promise<ServerStepAnswer> {\n if (!checkEntitlement(request.org as Parameters<typeof checkEntitlement>[0], CRM_SUITE_FEATURE)) {\n return { error: `CRM steps require the ${planLabelGrantingFeature(CRM_SUITE_FEATURE)} plan` }\n }\n const env: CrmStepEnv = { hostId: request.hostId, org: request.org, orgId: request.orgId }\n const step = request.step as unknown as CrmAutomationStep\n const actionId = request.run.id\n const { hostId } = env\n const person = await resolveEventPerson(hostId, request.payload)\n if (!person) {\n const named = String(request.payload['contactId'] ?? request.payload['email'] ?? '').trim()\n return {\n error: named\n ? `no contact or lead this site can see for ${named}`\n : 'the event names no contact — no contactId or email in its payload',\n }\n }\n if (person.kind === 'lead') return runLeadStep(env, actionId, step, person, nowMs)\n const contact = person\n /*\n * THE HOLDER whose facet the write addresses — the site's consent group,\n * which is the site alone unless the org declared the site one of a set\n * that presents as one sender. Same resolution the capture doors use, so a\n * stage set here lands in the facet the console reads.\n */\n const group = await consentGroupForSite(hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const email = String(contact.data['email'] ?? '')\n\n if (step.type === 'setContactStage') {\n const previous = facet.lifecycleStage ?? ''\n const label = CONTACT_LIFECYCLE_STAGE_LABELS[step.lifecycleStage]\n /*\n * A stage set to what it already is changes nothing and ANNOUNCES\n * nothing. The write is skipped so the row's `updatedAt` does not move for\n * a non-event, and the absent emit is what stops an action that sets the\n * stage it listens for from re-running on its own write — the engine's\n * nesting cap bounds that chain, this ends it at once.\n */\n if (previous === step.lifecycleStage) {\n return { detail: `${label} (already)` }\n }\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'lifecycleStage')]: step.lifecycleStage,\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The stage is what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n /*\n * A stage set by an automation IS a stage change, and the action\n * listening for it must run — but through the engine's nesting cap, so\n * the event is answered back for the engine to raise, never raised here.\n */\n return {\n detail: label,\n emit: {\n event: 'contactStageChanged',\n payload: {\n contactId: contact.id,\n email,\n lifecycleStage: step.lifecycleStage,\n previousStage: previous,\n },\n },\n }\n }\n\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n // `arrayUnion`, so the tag a merchant already applied by hand is not\n // duplicated and the tags beside it are kept.\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'tags')]: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The tags are what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return { detail: tag }\n }\n\n if (step.type === 'assignContactOwner') {\n /*\n * Through the one server assignment (AGL-2618), in both of the step's\n * modes, rather than a facet write of its own: the helper is what moves\n * the round-robin pointer inside the transaction that writes the owner,\n * mirrors the owner onto the site's lead, and tells the new owner. A\n * member named on the step is still resolved here — by address against\n * the roster, as before — and handed over by uid; the helper checks the\n * roster document again, which is one read and the same answer.\n */\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignContactOwner({\n hostId,\n contactId: contact.id,\n email,\n assign,\n })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') {\n return { detail: `${named} (already)` }\n }\n return {\n detail: step.roundRobin === true ? `round robin → ${verdict.ownerUid}` : named,\n }\n }\n\n // The two record creators: a task and an activity are documents of their\n // own beside the contact, stamped with the scope a contact captured on this\n // site would carry — `crmScopeTokens` is the create path's own expression,\n // so a record an automation made is visible to exactly the sites a record a\n // person made would be.\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(env.orgId)\n const visibleTo = crmScopeTokens((env.org ?? null) as Record<string, unknown> | null, group)\n const links = {\n contactId: contact.id,\n ...(facet.companyId ? { companyId: facet.companyId } : {}),\n }\n\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n /*\n * The assignee the step names, else the person who OWNS the contact, else\n * nobody. A follow-up task belongs to whoever holds the relationship, and\n * an automation that had to name one person for every contact it touches\n * would assign the whole list to one rep. A named assignee who cannot be\n * resolved is an error rather than a fallback to the owner: the author\n * named somebody on purpose.\n */\n let assignee = facet.ownerUid ?? ''\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const priority: CrmTaskPriority =\n step.priority === 'high' || step.priority === 'low' ? step.priority : 'normal'\n // The org's labels for the meanings the step names (AGL-3517).\n const labels = crmTaskLabelsForNew(\n await readCrmTaskPicklists(firebaseAdmin.app().firestore(), env.orgId),\n { kind: step.kind, priority },\n )\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority,\n status: 'open',\n ...labels,\n dueAtMs,\n // The reminder a person's task gets (AGL-2659): the due time.\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n // What the Tasks list searches by (AGL-3321), beside the task.\n await orgRef.collection(CRM_COLLECTIONS.tasks).add({ ...task, ...crmTaskListFields(task) })\n // The contact and company the task names carry `nextTaskAtMs` (AGL-2661);\n // a figure that could not move is the Fields section's recompute's.\n await recomputeCrmNextTaskAt(firebaseAdmin.app().firestore(), env.orgId, [links]).catch(\n (error: unknown) => {\n console.error('[crm] next activity could not be recomputed', env.orgId, error)\n },\n )\n return { detail: title.slice(0, 60) }\n }\n\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n /*\n * The per-record ceiling (AGL-2611), and this step is the writer it exists\n * for: a flow that logs on every page view fills one person's log in an\n * afternoon. Refused into the run history as an error, the way an\n * unresolvable assignee is, so the merchant reads why the flow stopped\n * writing rather than finding a log that silently stopped.\n */\n const logged = await countCrmActivitiesForRecord(orgRef, links)\n if (!crmActivityLogHasRoom(logged)) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const direction = crmActivityDirection(step.kind, step.direction)\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n ...(direction ? { direction } : {}),\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).add(activity)\n return { detail: step.kind }\n }\n\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * A CRM step on a LEAD (AGL-3458) — the person is not a contact yet, so each\n * step writes the lead's equivalent of what it writes on a contact. See\n * {@link ResolvedPerson}.\n */\nasync function runLeadStep(\n env: CrmStepEnv,\n actionId: string,\n step: CrmAutomationStep,\n lead: ResolvedContact,\n nowMs: number,\n): Promise<ServerStepAnswer> {\n const { hostId } = env\n if (step.type === 'setContactStage') {\n return {\n error:\n 'the event names a lead, and a lead has no lifecycle stage until it is ' +\n 'converted to a contact',\n }\n }\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n await lead.ref.update({\n tags: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // A lead's tags are what the Leads list searches (AGL-3321).\n await restampCrmListFieldsAt(lead.ref, 'leads')\n return { detail: `${tag} (lead)` }\n }\n if (step.type === 'assignContactOwner') {\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignLeadOwner({ hostId, leadId: lead.id, assign })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') return { detail: `${named} (already, lead)` }\n return {\n detail:\n step.roundRobin === true\n ? `round robin → ${verdict.ownerUid} (lead)`\n : `${named} (lead)`,\n }\n }\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(env.orgId)\n const group = await consentGroupForSite(hostId)\n const visibleTo = crmScopeTokens((env.org ?? null) as Record<string, unknown> | null, group)\n // Filed under the lead, as a sequence's call step files one (AGL-3233); a\n // conversion stamps the contact beside it, and the task follows the person.\n const links = { leadId: lead.id }\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n // The named assignee, else whoever owns the lead NOW — an owner step\n // earlier in this same run wrote the lead after it was resolved, so it\n // is read again — else nobody.\n let assignee: string\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n } else {\n const fresh = await lead.ref.get()\n assignee = String((fresh.data() ?? lead.data)['ownerUid'] ?? '')\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority: 'normal',\n status: 'open',\n dueAtMs,\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef\n .collection(CRM_COLLECTIONS.tasks)\n .doc(createResourceUid())\n .set({ ...task, ...crmTaskListFields(task) })\n return { detail: `${title.slice(0, 60)} (lead)` }\n }\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, links))) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).doc(createResourceUid()).set(activity)\n return { detail: `${step.kind} (lead)` }\n }\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * Whether — and where — an email a plugin is about to send lands on a\n * timeline: the CRM's answer to the record timeline's `prepareEmail`.\n *\n * An automation's `sendEmail` step is not a CRM step: it mails whatever\n * address the event carries, to a person who may be nobody the CRM knows. It\n * earns a row on exactly one condition, that the message is ADDRESSED TO THE\n * PERSON the sender means — a contact, else the lead a lead-routed form filed\n * (AGL-3458), whose row lands on the lead's timeline — the person the request's link finds, at the\n * address the row holds. A welcome sequence to a new contact is that; an\n * internal alert routed to a merchant's own address through `toField` is not,\n * and a row for it would put the merchant's inbox on a customer's history.\n *\n * Prepared ahead of the send, for the reason the console route mints its id\n * first: the delivery webhook has nothing but the tags on the message to find\n * the row with. Nothing is written until `file` is called, once the provider\n * accepted the message. A workspace whose plan does not carry the CRM, a\n * record at the activity ceiling and a site with no org earn no row — the\n * message still goes, because the ceiling bounds the log and not the mail.\n *\n * **Never throws**, and neither does `file`. The row is bookkeeping beside a\n * send, and a lookup that fails must not become a message that never left —\n * the posture every meter beside a send takes. A failure is logged and the\n * message goes out untagged.\n */\nexport async function prepareCrmRecordEmail(\n request: PluginRecordEmailPrepareRequest,\n): Promise<PluginRecordPreparedEmail | null> {\n const orgId = String(request.orgId ?? '').trim()\n const hostId = String(request.hostId ?? '').trim()\n if (!orgId || !hostId) return null\n const address = normalizeContactEmail(request.to)\n if (!address) return null\n try {\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n // The org the caller already read, or the one read here once.\n const org =\n request.org !== undefined ? request.org : ((await orgRef.get()).data() ?? null)\n if (!checkEntitlement(org as Parameters<typeof checkEntitlement>[0], CRM_SUITE_FEATURE)) {\n return null\n }\n const person = await resolveEventPerson(hostId, request.link)\n if (!person || normalizeContactEmail(person.data['email']) !== address) {\n return null\n }\n const group = await consentGroupForSite(hostId)\n const facet = person.kind === 'contact' ? readContactFacet(person.data, group.groupId) : null\n const link: CrmActivityLink =\n person.kind === 'lead'\n ? { leadId: person.id }\n : {\n contactId: person.id,\n ...(facet?.companyId ? { companyId: facet.companyId } : {}),\n }\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) {\n return null\n }\n const ref = newCrmActivityRef(firestore, orgId)\n const visibleTo = crmScopeTokens((org ?? null) as Record<string, unknown> | null, group)\n return {\n tags: crmEmailDeliveryTags({ orgId, hostId, activityId: ref.id }),\n /*\n * The same shape the console route logs — `buildCrmEmailActivity` is the\n * one builder — with the sender's id as its source and nobody as its\n * author. Never throws: the message has left, and a row that could not\n * be written is a gap on the timeline rather than a failed send.\n */\n async file(sent) {\n try {\n await writeCrmEmailActivity(\n ref,\n buildCrmEmailActivity({\n subject: sent.subject,\n body: sent.body,\n to: normalizeContactEmail(sent.to) ?? sent.to,\n atMs: sent.atMs ?? Date.now(),\n byUid: '',\n sourceActionId: sent.sourceRef,\n link,\n hostId,\n visibleTo,\n }),\n )\n } catch (error) {\n console.error('[crm] automation email activity write failed', ref.id, error)\n }\n },\n }\n } catch (error) {\n console.error('[crm] automation email activity could not be prepared', hostId, error)\n return null\n }\n}\n"],"names":["buildCrmEmailActivity","checkEntitlement","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_TAG_MAX_LENGTH","contactFacetPath","CRM_ACTIVITY_LOG_FULL_MESSAGE","CRM_COLLECTIONS","crmActivityLogHasRoom","crmActivityDirection","crmEmailDeliveryTags","crmScopeTokens","crmTaskLabelsForNew","crmTaskListFields","normalizeContactEmail","parseCrmMemberRef","personKey","planLabelGrantingFeature","readContactFacet","visibleToHost","consentGroupForSite","countCrmActivitiesForRecord","firebaseAdmin","orgDataQueryForHost","restampCrmListFieldsAt","findContactByEmail","FieldValue","OWNER_ASSIGNMENT_REFUSALS","reassignContactOwner","reassignLeadOwner","createResourceUid","readCrmTaskPicklists","CRM_SUITE_FEATURE","crmTaskReminderAfterEdit","recomputeCrmNextTaskAt","newCrmActivityRef","writeCrmEmailActivity","DAY_MS","resolveEventContact","hostId","named","hit","ref","contactsRef","contactId","String","trim","doc","get","exists","id","data","email","resolveEventLead","key","leadId","leadsRef","resolveEventPerson","contact","kind","lead","resolveMemberUid","orgId","role","uid","error","members","app","firestore","collection","member","detail","byField","where","limit","docs","runCrmAutomationStep","request","nowMs","Date","now","env","org","step","actionId","run","person","payload","runLeadStep","group","facet","groupId","type","previous","lifecycleStage","label","update","updatedAt","serverTimestamp","emit","event","previousStage","tag","slice","arrayUnion","assign","roundRobin","owner","ownerUid","ownerEmail","memberUid","verdict","outcome","reason","orgRef","visibleTo","links","companyId","title","dueInDays","Math","max","round","Number","assignee","assigneeUid","assigneeEmail","dueAtMs","priority","labels","task","status","remindAtMs","createdByUid","sourceActionId","createdAt","tasks","add","catch","console","body","logged","direction","activity","atMs","byUid","activities","tags","fresh","set","prepareCrmRecordEmail","address","to","undefined","link","activityId","file","sent","subject","sourceRef"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,qBAAqB,EACrBC,gBAAgB,EAChBC,8BAA8B,EAC9BC,sBAAsB,EACtBC,gBAAgB,EAChBC,6BAA6B,EAC7BC,eAAe,EACfC,qBAAqB,EAGrBC,oBAAoB,EAIpBC,oBAAoB,EACpBC,cAAc,EACdC,mBAAmB,EACnBC,iBAAiB,EACjBC,qBAAqB,EACrBC,iBAAiB,EACjBC,SAAS,EACTC,wBAAwB,EACxBC,gBAAgB,EAChBC,aAAa,QACR,sBAAqB;AAS5B,SACEC,mBAAmB,EACnBC,2BAA2B,EAC3BC,aAAa,EACbC,mBAAmB,EACnBC,sBAAsB,QACjB,2BAA0B;AACjC,2EAA2E;AAC3E,yDAAyD;AACzD,SAASC,kBAAkB,QAAQ,sDAAqD;AACxF,SAASC,UAAU,QAAQ,2BAA0B;AACrD,6DAA6D;AAC7D,SACEC,yBAAyB,EACzBC,oBAAoB,EACpBC,iBAAiB,QACZ,4BAAwB;AAC/B,SAASC,iBAAiB,QAAQ,6CAA4C;AAI9E,SAASC,oBAAoB,QAAQ,qBAAiB;AACtD,SAASC,iBAAiB,QAAQ,kBAAc;AAChD,SAASC,wBAAwB,QAAQ,iCAA6B;AACtE,SAASC,sBAAsB,QAAQ,yBAAqB;AAC5D,SAASC,iBAAiB,EAAEC,qBAAqB,QAAQ,0BAAsB;AAsF/E,MAAMC,SAAS,KAAK,KAAK,KAAK;AAuB9B;;;;;;;;CAQC,GACD,eAAeC,oBACbC,MAAc,EACdC,KAA+C;QAGtBA,kBAQsBC;IAT/C,MAAM,EAAEC,KAAKC,WAAW,EAAE,GAAG,MAAMpB,oBAAoBgB,QAAQ;IAC/D,MAAMK,YAAYC,QAAOL,mBAAAA,MAAMI,SAAS,YAAfJ,mBAAmB,IAAIM,IAAI;IACpD,IAAIF,WAAW;QACb,MAAMG,MAAM,MAAMJ,YAAYI,GAAG,CAACH,WAAWI,GAAG;QAChD,IAAID,IAAIE,MAAM,IAAI9B,cAAc4B,IAAIC,GAAG,CAAC,cAAcT,SAAS;gBACpBQ;YAAzC,OAAO;gBAAEG,IAAIH,IAAIG,EAAE;gBAAER,KAAKK,IAAIL,GAAG;gBAAES,IAAI,GAAEJ,YAAAA,IAAII,IAAI,cAARJ,YAAc,CAAC;YAAE;QAC5D;IACF;IACA,MAAMN,MAAM,MAAMhB,mBAAmBkB,aAAaH,MAAMY,KAAK,EAAE;QAAEb;IAAO;IACxE,OAAOE,MAAM;QAAES,IAAIT,IAAIS,EAAE;QAAER,KAAKD,IAAIC,GAAG;QAAES,IAAI,GAAEV,YAAAA,IAAIU,IAAI,cAARV,YAAc,CAAC;IAAE,IAAI;AACtE;AAEA;;;;CAIC,GACD,eAAeY,iBACbd,MAAc,EACdC,KAA4C;QAEzBA,eAKsBO;IALzC,MAAMO,MAAMT,QAAOL,gBAAAA,MAAMe,MAAM,YAAZf,gBAAgB,IAAIM,IAAI,MAAM9B,UAAUwB,MAAMY,KAAK,KAAK;IAC3E,IAAI,CAACE,KAAK,OAAO;IACjB,MAAM,EAAEZ,KAAKc,QAAQ,EAAE,GAAG,MAAMjC,oBAAoBgB,QAAQ;IAC5D,MAAMQ,MAAM,MAAMS,SAAST,GAAG,CAACO,KAAKN,GAAG;IACvC,IAAI,CAACD,IAAIE,MAAM,IAAI,CAAC9B,cAAc4B,IAAIC,GAAG,CAAC,cAAcT,SAAS,OAAO;IACxE,OAAO;QAAEW,IAAIH,IAAIG,EAAE;QAAER,KAAKK,IAAIL,GAAG;QAAES,IAAI,GAAEJ,YAAAA,IAAII,IAAI,cAARJ,YAAc,CAAC;IAAE;AAC5D;AAEA,6EAA6E,GAC7E,eAAeU,mBACblB,MAAc,EACdC,KAAiE;IAEjE,MAAMkB,UAAU,MAAMpB,oBAAoBC,QAAQC;IAClD,IAAIkB,SAAS,OAAO;QAAEC,MAAM;OAAcD;IAC1C,MAAME,OAAO,MAAMP,iBAAiBd,QAAQC;IAC5C,OAAOoB,OAAO;QAAED,MAAM;OAAWC,QAAS;AAC5C;AAEA;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,eAAeC,iBACbC,KAAoB,EACpBtB,KAAuC,EACvCuB,IAA0B;QAEdhD;IAAZ,MAAM2B,OAAM3B,qBAAAA,kBAAkByB,MAAMwB,GAAG,aAA3BjD,qBAAgCA,kBAAkByB,MAAMY,KAAK;IACzE,IAAI,CAACV,KAAK,OAAO;QAAEuB,OAAO,CAAC,GAAG,EAAEF,KAAK,kBAAkB,CAAC;IAAC;IACzD,IAAI,CAACD,OAAO,OAAO;QAAEG,OAAO;IAAgC;IAC5D,MAAMC,UAAU5C,cACb6C,GAAG,GACHC,SAAS,GACTC,UAAU,CAAC,QACXtB,GAAG,CAACe,OACJO,UAAU,CAAC;IACd,IAAI3B,IAAIiB,IAAI,KAAK,OAAO;QACtB,MAAMW,SAAS,MAAMJ,QAAQnB,GAAG,CAACL,IAAIsB,GAAG,EAAEhB,GAAG;QAC7C,IAAIsB,OAAOrB,MAAM,EAAE,OAAO;YAAEe,KAAKtB,IAAIsB,GAAG;YAAEO,QAAQ7B,IAAIsB,GAAG;QAAC;QAC1D,OAAO;YAAEC,OAAO,CAAC,2BAA2B,EAAEvB,IAAIsB,GAAG,EAAE;QAAC;IAC1D;IACA,MAAMQ,UAAU,AAAC,CAAA,MAAMN,QAAQO,KAAK,CAAC,SAAS,MAAM/B,IAAIU,KAAK,EAAEsB,KAAK,CAAC,GAAG1B,GAAG,EAAC,EAAG2B,IAAI,CAAC,EAAE;IACtF,IAAIH,SAAS,OAAO;QAAER,KAAKQ,QAAQtB,EAAE;QAAEqB,QAAQ7B,IAAIU,KAAK;IAAC;IACzD,OAAO;QAAEa,OAAO,CAAC,gCAAgC,EAAEvB,IAAIU,KAAK,EAAE;IAAC;AACjE;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAewB,qBACpBC,OAA0B,EAC1BC,QAAQC,KAAKC,GAAG,EAAE;QA4BGtB,qBAwGauB;IAlIlC,IAAI,CAAC/E,iBAAiB2E,QAAQK,GAAG,EAA4ClD,oBAAoB;QAC/F,OAAO;YAAEiC,OAAO,CAAC,sBAAsB,EAAEhD,yBAAyBe,mBAAmB,KAAK,CAAC;QAAC;IAC9F;IACA,MAAMiD,MAAkB;QAAE1C,QAAQsC,QAAQtC,MAAM;QAAE2C,KAAKL,QAAQK,GAAG;QAAEpB,OAAOe,QAAQf,KAAK;IAAC;IACzF,MAAMqB,OAAON,QAAQM,IAAI;IACzB,MAAMC,WAAWP,QAAQQ,GAAG,CAACnC,EAAE;IAC/B,MAAM,EAAEX,MAAM,EAAE,GAAG0C;IACnB,MAAMK,SAAS,MAAM7B,mBAAmBlB,QAAQsC,QAAQU,OAAO;IAC/D,IAAI,CAACD,QAAQ;YACUT,MAAAA;QAArB,MAAMrC,QAAQK,QAAOgC,QAAAA,6BAAAA,QAAQU,OAAO,CAAC,YAAY,YAA5BV,6BAAgCA,QAAQU,OAAO,CAAC,QAAQ,YAAxDV,OAA4D,IAAI/B,IAAI;QACzF,OAAO;YACLmB,OAAOzB,QACH,CAAC,yCAAyC,EAAEA,OAAO,GACnD;QACN;IACF;IACA,IAAI8C,OAAO3B,IAAI,KAAK,QAAQ,OAAO6B,YAAYP,KAAKG,UAAUD,MAAMG,QAAQR;IAC5E,MAAMpB,UAAU4B;IAChB;;;;;GAKC,GACD,MAAMG,QAAQ,MAAMrE,oBAAoBmB;IACxC,MAAMmD,QAAQxE,iBAAiBwC,QAAQP,IAAI,EAAEsC,MAAME,OAAO;IAC1D,MAAMvC,QAAQP,QAAOa,sBAAAA,QAAQP,IAAI,CAAC,QAAQ,YAArBO,sBAAyB;IAE9C,IAAIyB,KAAKS,IAAI,KAAK,mBAAmB;YAClBF;QAAjB,MAAMG,YAAWH,wBAAAA,MAAMI,cAAc,YAApBJ,wBAAwB;QACzC,MAAMK,QAAQ5F,8BAA8B,CAACgF,KAAKW,cAAc,CAAC;QACjE;;;;;;KAMC,GACD,IAAID,aAAaV,KAAKW,cAAc,EAAE;YACpC,OAAO;gBAAEvB,QAAQ,GAAGwB,MAAM,UAAU,CAAC;YAAC;QACxC;QACA,MAAMrC,QAAQhB,GAAG,CAACsD,MAAM,CAAC;YACvB,CAAC3F,iBAAiBoF,MAAME,OAAO,EAAE,kBAAkB,EAAER,KAAKW,cAAc;YACxEG,WAAWvE,WAAWwE,eAAe;QACvC;QACA,6DAA6D;QAC7D,MAAM1E,uBAAuBkC,QAAQhB,GAAG,EAAE;QAC1C;;;;KAIC,GACD,OAAO;YACL6B,QAAQwB;YACRI,MAAM;gBACJC,OAAO;gBACPb,SAAS;oBACP3C,WAAWc,QAAQR,EAAE;oBACrBE;oBACA0C,gBAAgBX,KAAKW,cAAc;oBACnCO,eAAeR;gBACjB;YACF;QACF;IACF;IAEA,IAAIV,KAAKS,IAAI,KAAK,iBAAiB;YACdT;QAAnB,MAAMmB,MAAMzD,QAAOsC,YAAAA,KAAKmB,GAAG,YAARnB,YAAY,IAC5BrC,IAAI,GACJyD,KAAK,CAAC,GAAGnG;QACZ,IAAI,CAACkG,KAAK,OAAO;YAAErC,OAAO;QAAsB;QAChD,qEAAqE;QACrE,8CAA8C;QAC9C,MAAMP,QAAQhB,GAAG,CAACsD,MAAM,CAAC;YACvB,CAAC3F,iBAAiBoF,MAAME,OAAO,EAAE,QAAQ,EAAEjE,WAAW8E,UAAU,CAACF;YACjEL,WAAWvE,WAAWwE,eAAe;QACvC;QACA,6DAA6D;QAC7D,MAAM1E,uBAAuBkC,QAAQhB,GAAG,EAAE;QAC1C,OAAO;YAAE6B,QAAQ+B;QAAI;IACvB;IAEA,IAAInB,KAAKS,IAAI,KAAK,sBAAsB;QACtC;;;;;;;;KAQC,GACD,IAAIa;QACJ,IAAIjE,QAAQ;QACZ,IAAI2C,KAAKuB,UAAU,KAAK,MAAM;YAC5BD,SAAS;gBAAEC,YAAY;YAAK;QAC9B,OAAO;YACL,MAAMC,QAAQ,MAAM9C,iBAClBoB,IAAInB,KAAK,EACT;gBAAEE,KAAKmB,KAAKyB,QAAQ;gBAAExD,OAAO+B,KAAK0B,UAAU;YAAC,GAC7C;YAEF,IAAI,WAAWF,OAAO,OAAO;gBAAE1C,OAAO0C,MAAM1C,KAAK;YAAC;YAClDwC,SAAS;gBAAEK,WAAWH,MAAM3C,GAAG;YAAC;YAChCxB,QAAQmE,MAAMpC,MAAM;QACtB;QACA,MAAMwC,UAAU,MAAMnF,qBAAqB;YACzCW;YACAK,WAAWc,QAAQR,EAAE;YACrBE;YACAqD;QACF;QACA,IAAIM,QAAQC,OAAO,KAAK,QAAQ;YAC9B,OAAO;gBAAE/C,OAAOtC,yBAAyB,CAACoF,QAAQE,MAAM,CAAC;YAAC;QAC5D;QACA,IAAIF,QAAQC,OAAO,KAAK,aAAa;YACnC,OAAO;gBAAEzC,QAAQ,GAAG/B,MAAM,UAAU,CAAC;YAAC;QACxC;QACA,OAAO;YACL+B,QAAQY,KAAKuB,UAAU,KAAK,OAAO,CAAC,cAAc,EAAEK,QAAQH,QAAQ,EAAE,GAAGpE;QAC3E;IACF;IAEA,yEAAyE;IACzE,4EAA4E;IAC5E,2EAA2E;IAC3E,4EAA4E;IAC5E,wBAAwB;IACxB,IAAI,CAACyC,IAAInB,KAAK,EAAE,OAAO;QAAEG,OAAO;IAAgC;IAChE,MAAMiD,SAAS5F,cAAc6C,GAAG,GAAGC,SAAS,GAAGC,UAAU,CAAC,QAAQtB,GAAG,CAACkC,IAAInB,KAAK;IAC/E,MAAMqD,YAAYxG,gBAAgBsE,WAAAA,IAAIC,GAAG,YAAPD,WAAW,MAAyCQ;IACtF,MAAM2B,QAAQ;QACZxE,WAAWc,QAAQR,EAAE;OACjBwC,MAAM2B,SAAS,GAAG;QAAEA,WAAW3B,MAAM2B,SAAS;IAAC,IAAI,CAAC;IAG1D,IAAIlC,KAAKS,IAAI,KAAK,iBAAiB;YACZT,aAaNO;YACXP,mBAA4BA;QAdhC,MAAMmC,QAAQzE,QAAOsC,cAAAA,KAAKmC,KAAK,YAAVnC,cAAc,IAChCrC,IAAI,GACJyD,KAAK,CAAC,GAAG;QACZ,IAAI,CAACe,OAAO,OAAO;YAAErD,OAAO;QAAwB;QACpD,MAAMsD,YAAYC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,OAAOxC,KAAKoC,SAAS,KAAK;QACnE;;;;;;;KAOC,GACD,IAAIK,YAAWlC,kBAAAA,MAAMkB,QAAQ,YAAdlB,kBAAkB;QACjC,IAAIP,EAAAA,oBAAAA,KAAK0C,WAAW,qBAAhB1C,kBAAkBrC,IAAI,SAAMqC,sBAAAA,KAAK2C,aAAa,qBAAlB3C,oBAAoBrC,IAAI,KAAI;YAC1D,MAAMN,QAAQ,MAAMqB,iBAClBoB,IAAInB,KAAK,EACT;gBAAEE,KAAKmB,KAAK0C,WAAW;gBAAEzE,OAAO+B,KAAK2C,aAAa;YAAC,GACnD;YAEF,IAAI,WAAWtF,OAAO,OAAO;gBAAEyB,OAAOzB,MAAMyB,KAAK;YAAC;YAClD2D,WAAWpF,MAAMwB,GAAG;QACtB;QACA,MAAM+D,UAAUjD,QAAQyC,YAAYlF;QACpC,MAAM2F,WACJ7C,KAAK6C,QAAQ,KAAK,UAAU7C,KAAK6C,QAAQ,KAAK,QAAQ7C,KAAK6C,QAAQ,GAAG;QACxE,+DAA+D;QAC/D,MAAMC,SAASrH,oBACb,MAAMmB,qBAAqBT,cAAc6C,GAAG,GAAGC,SAAS,IAAIa,IAAInB,KAAK,GACrE;YAAEH,MAAMwB,KAAKxB,IAAI;YAAEqE;QAAS;QAE9B,MAAME,OAAgB;YACpBZ;YACA3D,MAAMwB,KAAKxB,IAAI;YACfqE;YACAG,QAAQ;WACLF;YACHF;YACA,8DAA8D;YAC9DK,YAAYnG,yBAAyB;gBAAE8F;gBAASlC,UAAU;YAAK;WAC3D+B,WAAW;YAAEC,aAAaD;QAAS,IAAI,CAAC;YAC5CS,cAAc;YACdC,gBAAgBlD;WACbgC;YACH7E;YACA4E;YACAoB,WAAW7G,WAAWwE,eAAe;YACrCD,WAAWvE,WAAWwE,eAAe;;QAEvC,+DAA+D;QAC/D,MAAMgB,OAAO7C,UAAU,CAAC9D,gBAAgBiI,KAAK,EAAEC,GAAG,CAAC,aAAKP,MAASrH,kBAAkBqH;QACnF,0EAA0E;QAC1E,oEAAoE;QACpE,MAAMhG,uBAAuBZ,cAAc6C,GAAG,GAAGC,SAAS,IAAIa,IAAInB,KAAK,EAAE;YAACsD;SAAM,EAAEsB,KAAK,CACrF,CAACzE;YACC0E,QAAQ1E,KAAK,CAAC,+CAA+CgB,IAAInB,KAAK,EAAEG;QAC1E;QAEF,OAAO;YAAEM,QAAQ+C,MAAMf,KAAK,CAAC,GAAG;QAAI;IACtC;IAEA,IAAIpB,KAAKS,IAAI,KAAK,kBAAkB;YACdT;QAApB,MAAMyD,OAAO/F,QAAOsC,aAAAA,KAAKyD,IAAI,YAATzD,aAAa,IAC9BrC,IAAI,GACJyD,KAAK,CAAC,GAAG;QACZ,IAAI,CAACqC,MAAM,OAAO;YAAE3E,OAAO;QAA2B;QACtD;;;;;;KAMC,GACD,MAAM4E,SAAS,MAAMxH,4BAA4B6F,QAAQE;QACzD,IAAI,CAAC5G,sBAAsBqI,SAAS;YAClC,OAAO;gBAAE5E,OAAO3D;YAA8B;QAChD;QACA,MAAMwI,YAAYrI,qBAAqB0E,KAAKxB,IAAI,EAAEwB,KAAK2D,SAAS;QAChE,MAAMC,WAAwB;YAC5BpF,MAAMwB,KAAKxB,IAAI;YACfiF;WACIE,YAAY;YAAEA;QAAU,IAAI,CAAC;YACjCE,MAAMlE;YACNmE,OAAO;YACPX,gBAAgBlD;WACbgC;YACH7E;YACA4E;YACAoB,WAAW7G,WAAWwE,eAAe;YACrCD,WAAWvE,WAAWwE,eAAe;;QAEvC,MAAMgB,OAAO7C,UAAU,CAAC9D,gBAAgB2I,UAAU,EAAET,GAAG,CAACM;QACxD,OAAO;YAAExE,QAAQY,KAAKxB,IAAI;QAAC;IAC7B;IAEA,OAAO;QAAEM,OAAO,CAAC,kBAAkB,EAAE,AAACkB,KAA0BS,IAAI,CAAC,CAAC,CAAC;IAAC;AAC1E;AAEA;;;;CAIC,GACD,eAAeJ,YACbP,GAAe,EACfG,QAAgB,EAChBD,IAAuB,EACvBvB,IAAqB,EACrBkB,KAAa;QAqDqBG;IAnDlC,MAAM,EAAE1C,MAAM,EAAE,GAAG0C;IACnB,IAAIE,KAAKS,IAAI,KAAK,mBAAmB;QACnC,OAAO;YACL3B,OACE,2EACA;QACJ;IACF;IACA,IAAIkB,KAAKS,IAAI,KAAK,iBAAiB;YACdT;QAAnB,MAAMmB,MAAMzD,QAAOsC,YAAAA,KAAKmB,GAAG,YAARnB,YAAY,IAC5BrC,IAAI,GACJyD,KAAK,CAAC,GAAGnG;QACZ,IAAI,CAACkG,KAAK,OAAO;YAAErC,OAAO;QAAsB;QAChD,MAAML,KAAKlB,GAAG,CAACsD,MAAM,CAAC;YACpBmD,MAAMzH,WAAW8E,UAAU,CAACF;YAC5BL,WAAWvE,WAAWwE,eAAe;QACvC;QACA,6DAA6D;QAC7D,MAAM1E,uBAAuBoC,KAAKlB,GAAG,EAAE;QACvC,OAAO;YAAE6B,QAAQ,GAAG+B,IAAI,OAAO,CAAC;QAAC;IACnC;IACA,IAAInB,KAAKS,IAAI,KAAK,sBAAsB;QACtC,IAAIa;QACJ,IAAIjE,QAAQ;QACZ,IAAI2C,KAAKuB,UAAU,KAAK,MAAM;YAC5BD,SAAS;gBAAEC,YAAY;YAAK;QAC9B,OAAO;YACL,MAAMC,QAAQ,MAAM9C,iBAClBoB,IAAInB,KAAK,EACT;gBAAEE,KAAKmB,KAAKyB,QAAQ;gBAAExD,OAAO+B,KAAK0B,UAAU;YAAC,GAC7C;YAEF,IAAI,WAAWF,OAAO,OAAO;gBAAE1C,OAAO0C,MAAM1C,KAAK;YAAC;YAClDwC,SAAS;gBAAEK,WAAWH,MAAM3C,GAAG;YAAC;YAChCxB,QAAQmE,MAAMpC,MAAM;QACtB;QACA,MAAMwC,UAAU,MAAMlF,kBAAkB;YAAEU;YAAQgB,QAAQK,KAAKV,EAAE;YAAEuD;QAAO;QAC1E,IAAIM,QAAQC,OAAO,KAAK,QAAQ;YAC9B,OAAO;gBAAE/C,OAAOtC,yBAAyB,CAACoF,QAAQE,MAAM,CAAC;YAAC;QAC5D;QACA,IAAIF,QAAQC,OAAO,KAAK,aAAa,OAAO;YAAEzC,QAAQ,GAAG/B,MAAM,gBAAgB,CAAC;QAAC;QACjF,OAAO;YACL+B,QACEY,KAAKuB,UAAU,KAAK,OAChB,CAAC,cAAc,EAAEK,QAAQH,QAAQ,CAAC,OAAO,CAAC,GAC1C,GAAGpE,MAAM,OAAO,CAAC;QACzB;IACF;IACA,IAAI,CAACyC,IAAInB,KAAK,EAAE,OAAO;QAAEG,OAAO;IAAgC;IAChE,MAAMiD,SAAS5F,cAAc6C,GAAG,GAAGC,SAAS,GAAGC,UAAU,CAAC,QAAQtB,GAAG,CAACkC,IAAInB,KAAK;IAC/E,MAAM2B,QAAQ,MAAMrE,oBAAoBmB;IACxC,MAAM4E,YAAYxG,gBAAgBsE,WAAAA,IAAIC,GAAG,YAAPD,WAAW,MAAyCQ;IACtF,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM2B,QAAQ;QAAE7D,QAAQK,KAAKV,EAAE;IAAC;IAChC,IAAIiC,KAAKS,IAAI,KAAK,iBAAiB;YACZT;YASjBA,mBAA4BA;QAThC,MAAMmC,QAAQzE,QAAOsC,cAAAA,KAAKmC,KAAK,YAAVnC,cAAc,IAChCrC,IAAI,GACJyD,KAAK,CAAC,GAAG;QACZ,IAAI,CAACe,OAAO,OAAO;YAAErD,OAAO;QAAwB;QACpD,MAAMsD,YAAYC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,OAAOxC,KAAKoC,SAAS,KAAK;QACnE,qEAAqE;QACrE,uEAAuE;QACvE,+BAA+B;QAC/B,IAAIK;QACJ,IAAIzC,EAAAA,oBAAAA,KAAK0C,WAAW,qBAAhB1C,kBAAkBrC,IAAI,SAAMqC,sBAAAA,KAAK2C,aAAa,qBAAlB3C,oBAAoBrC,IAAI,KAAI;YAC1D,MAAMN,QAAQ,MAAMqB,iBAClBoB,IAAInB,KAAK,EACT;gBAAEE,KAAKmB,KAAK0C,WAAW;gBAAEzE,OAAO+B,KAAK2C,aAAa;YAAC,GACnD;YAEF,IAAI,WAAWtF,OAAO,OAAO;gBAAEyB,OAAOzB,MAAMyB,KAAK;YAAC;YAClD2D,WAAWpF,MAAMwB,GAAG;QACtB,OAAO;gBAEa,WAACoF;YADnB,MAAMA,QAAQ,MAAMxF,KAAKlB,GAAG,CAACM,GAAG;YAChC4E,WAAW/E,QAAO,YAAA,EAACuG,cAAAA,MAAMjG,IAAI,cAAViG,cAAgBxF,KAAKT,IAAI,CAAC,CAAC,WAAW,YAAvC,YAA2C;QAC/D;QACA,MAAM4E,UAAUjD,QAAQyC,YAAYlF;QACpC,MAAM6F,OAAgB;YACpBZ;YACA3D,MAAMwB,KAAKxB,IAAI;YACfqE,UAAU;YACVG,QAAQ;YACRJ;YACAK,YAAYnG,yBAAyB;gBAAE8F;gBAASlC,UAAU;YAAK;WAC3D+B,WAAW;YAAEC,aAAaD;QAAS,IAAI,CAAC;YAC5CS,cAAc;YACdC,gBAAgBlD;WACbgC;YACH7E;YACA4E;YACAoB,WAAW7G,WAAWwE,eAAe;YACrCD,WAAWvE,WAAWwE,eAAe;;QAEvC,MAAMgB,OACH7C,UAAU,CAAC9D,gBAAgBiI,KAAK,EAChCzF,GAAG,CAACjB,qBACJuH,GAAG,CAAC,aAAKnB,MAASrH,kBAAkBqH;QACvC,OAAO;YAAE3D,QAAQ,GAAG+C,MAAMf,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC;QAAC;IAClD;IACA,IAAIpB,KAAKS,IAAI,KAAK,kBAAkB;YACdT;QAApB,MAAMyD,OAAO/F,QAAOsC,aAAAA,KAAKyD,IAAI,YAATzD,aAAa,IAC9BrC,IAAI,GACJyD,KAAK,CAAC,GAAG;QACZ,IAAI,CAACqC,MAAM,OAAO;YAAE3E,OAAO;QAA2B;QACtD,IAAI,CAACzD,sBAAsB,MAAMa,4BAA4B6F,QAAQE,SAAS;YAC5E,OAAO;gBAAEnD,OAAO3D;YAA8B;QAChD;QACA,MAAMyI,WAAwB;YAC5BpF,MAAMwB,KAAKxB,IAAI;YACfiF;YACAI,MAAMlE;YACNmE,OAAO;YACPX,gBAAgBlD;WACbgC;YACH7E;YACA4E;YACAoB,WAAW7G,WAAWwE,eAAe;YACrCD,WAAWvE,WAAWwE,eAAe;;QAEvC,MAAMgB,OAAO7C,UAAU,CAAC9D,gBAAgB2I,UAAU,EAAEnG,GAAG,CAACjB,qBAAqBuH,GAAG,CAACN;QACjF,OAAO;YAAExE,QAAQ,GAAGY,KAAKxB,IAAI,CAAC,OAAO,CAAC;QAAC;IACzC;IACA,OAAO;QAAEM,OAAO,CAAC,kBAAkB,EAAE,AAACkB,KAA0BS,IAAI,CAAC,CAAC,CAAC;IAAC;AAC1E;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,eAAe0D,sBACpBzE,OAAwC;QAEnBA,gBACCA;IADtB,MAAMf,QAAQjB,QAAOgC,iBAAAA,QAAQf,KAAK,YAAbe,iBAAiB,IAAI/B,IAAI;IAC9C,MAAMP,SAASM,QAAOgC,kBAAAA,QAAQtC,MAAM,YAAdsC,kBAAkB,IAAI/B,IAAI;IAChD,IAAI,CAACgB,SAAS,CAACvB,QAAQ,OAAO;IAC9B,MAAMgH,UAAUzI,sBAAsB+D,QAAQ2E,EAAE;IAChD,IAAI,CAACD,SAAS,OAAO;IACrB,IAAI;YAK2C;QAJ7C,MAAMnF,YAAY9C,cAAc6C,GAAG,GAAGC,SAAS;QAC/C,MAAM8C,SAAS9C,UAAUC,UAAU,CAAC,QAAQtB,GAAG,CAACe;QAChD,8DAA8D;QAC9D,MAAMoB,MACJL,QAAQK,GAAG,KAAKuE,YAAY5E,QAAQK,GAAG,IAAI,QAAA,AAAC,CAAA,MAAMgC,OAAOlE,GAAG,EAAC,EAAGG,IAAI,cAAzB,QAA+B;QAC5E,IAAI,CAACjD,iBAAiBgF,KAA+ClD,oBAAoB;YACvF,OAAO;QACT;QACA,MAAMsD,SAAS,MAAM7B,mBAAmBlB,QAAQsC,QAAQ6E,IAAI;QAC5D,IAAI,CAACpE,UAAUxE,sBAAsBwE,OAAOnC,IAAI,CAAC,QAAQ,MAAMoG,SAAS;YACtE,OAAO;QACT;QACA,MAAM9D,QAAQ,MAAMrE,oBAAoBmB;QACxC,MAAMmD,QAAQJ,OAAO3B,IAAI,KAAK,YAAYzC,iBAAiBoE,OAAOnC,IAAI,EAAEsC,MAAME,OAAO,IAAI;QACzF,MAAM+D,OACJpE,OAAO3B,IAAI,KAAK,SACZ;YAAEJ,QAAQ+B,OAAOpC,EAAE;QAAC,IACpB;YACEN,WAAW0C,OAAOpC,EAAE;WAChBwC,CAAAA,yBAAAA,MAAO2B,SAAS,IAAG;YAAEA,WAAW3B,MAAM2B,SAAS;QAAC,IAAI,CAAC;QAEjE,IAAI,CAAC7G,sBAAsB,MAAMa,4BAA4B6F,QAAQwC,QAAQ;YAC3E,OAAO;QACT;QACA,MAAMhH,MAAMP,kBAAkBiC,WAAWN;QACzC,MAAMqD,YAAYxG,eAAgBuE,cAAAA,MAAO,MAAyCO;QAClF,OAAO;YACL0D,MAAMzI,qBAAqB;gBAAEoD;gBAAOvB;gBAAQoH,YAAYjH,IAAIQ,EAAE;YAAC;YAC/D;;;;;OAKC,GACD,MAAM0G,MAAKC,IAAI;gBACb,IAAI;wBAMM/I,wBACE+I;oBANV,MAAMzH,sBACJM,KACAzC,sBAAsB;wBACpB6J,SAASD,KAAKC,OAAO;wBACrBlB,MAAMiB,KAAKjB,IAAI;wBACfY,EAAE,GAAE1I,yBAAAA,sBAAsB+I,KAAKL,EAAE,aAA7B1I,yBAAkC+I,KAAKL,EAAE;wBAC7CR,IAAI,GAAEa,aAAAA,KAAKb,IAAI,YAATa,aAAa9E,KAAKC,GAAG;wBAC3BiE,OAAO;wBACPX,gBAAgBuB,KAAKE,SAAS;wBAC9BL;wBACAnH;wBACA4E;oBACF;gBAEJ,EAAE,OAAOlD,OAAO;oBACd0E,QAAQ1E,KAAK,CAAC,gDAAgDvB,IAAIQ,EAAE,EAAEe;gBACxE;YACF;QACF;IACF,EAAE,OAAOA,OAAO;QACd0E,QAAQ1E,KAAK,CAAC,yDAAyD1B,QAAQ0B;QAC/E,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/crm/src/lib/server/automation-steps.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 buildCrmEmailActivity,\n checkEntitlement,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_TAG_MAX_LENGTH,\n contactFacetPath,\n CRM_ACTIVITY_LOG_FULL_MESSAGE,\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActivity,\n type CrmActivityDirection,\n crmActivityDirection,\n type CrmActivityLink,\n type CrmTask,\n type CrmTaskPriority,\n crmEmailDeliveryTags,\n crmScopeTokens,\n crmTaskLabelsForNew,\n crmTaskListFields,\n normalizeContactEmail,\n parseCrmMemberRef,\n personKey,\n planLabelGrantingFeature,\n readContactFacet,\n visibleToHost,\n} from '@aglyn/aglyn/server'\nimport type {\n PluginRecordEmailPrepareRequest,\n PluginRecordPreparedEmail,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-timeline'\nimport type {\n ServerStepAnswer,\n ServerStepRequest,\n} from '@aglyn/aglyn/plugin-manager/plugin-server-steps'\nimport {\n consentGroupForSite,\n countCrmActivitiesForRecord,\n firebaseAdmin,\n orgDataQueryForHost,\n restampCrmListFieldsAt,\n} from '@aglyn/tenant-data-admin'\n// The leaf, not the barrel: the specs substitute the barrel wholesale, and\n// the lookup must reach the real index logic under them.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { FieldValue } from 'firebase-admin/firestore'\n// The leaf, not the runtime barrel: the specs substitute it.\nimport {\n OWNER_ASSIGNMENT_REFUSALS,\n reassignContactOwner,\n reassignLeadOwner,\n} from './assign-contact-owner'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { InteractionStepGuard } from '@aglyn/aglyn/app-utils/site-interactions'\nimport type { ContactLifecycleStage, CrmActivityKind, CrmTaskKind } from '@aglyn/aglyn/app-utils/crm-kinds'\nimport type { CRM_STEP_TYPES } from '../constants/bundle-common'\nimport { readCrmTaskPicklists } from './read-picklist'\nimport { CRM_SUITE_FEATURE } from './suite-gate'\nimport { crmTaskReminderAfterEdit } from '../model/crm-task-reminders'\nimport { recomputeCrmNextTaskAt } from './crm-next-activity'\nimport { newCrmActivityRef, writeCrmEmailActivity } from './crm-email-activity'\nimport { stampCrmActivityCompany } from '../model/crm-activity-link'\n\n/**\n * THE AUTOMATION STEPS THAT WRITE THE CRM (AGL-2605), run for the automation\n * engine through the platform's server-step seam (`plugin-server-steps`,\n * AGL-3080): `setContactStage`, `addContactTag`, `assignContactOwner`,\n * `createCrmTask` and `logCrmActivity`.\n *\n * The engine keeps the step's guard, the run's order, its history and the\n * nesting cap; this answers what the step did. A refusal is ANSWERED as\n * `error`, never thrown, and its words are the line the run history carries —\n * the same words the engine wrote when these steps were its own.\n *\n * The five share one shape no other step has: each begins by finding THE\n * PERSON the event is about, and each writes either inside the site's facet on\n * that person or a record stamped with the site's scope. One resolver and one\n * scope expression, used five times, rather than five copies drifting apart.\n *\n * ## The person is the event's, never the step's\n *\n * A step carries no contact reference. The contact is whoever the event\n * names — by `contactId` when the emitting door knew the document, and by\n * `email` otherwise, which is what every pre-CRM event carries. Both lookups\n * are scoped to what THIS site may see: a document the id names but the site\n * cannot read is treated as absent, exactly as the scoped query would have\n * treated it, so an event replayed against the wrong site reaches nobody.\n *\n * ## A missing person is a reported no-op\n *\n * Nothing is written and the step's error names the reason, which lands in\n * the run summary beside the trigger — the same place \"why didn't it fire?\"\n * is answered for every other step. Silence here would make a mis-wired\n * trigger (a form with no email field) indistinguishable from a working one.\n *\n * ## The plan gate is the CRM's\n *\n * Every step is refused on a workspace whose plan does not carry the CRM,\n * into the run history with the tier that carries it, so a Free workspace\n * whose flow names a CRM step reads why the step did nothing rather than a\n * log that says it ran. The org document is the one the run's gate already\n * read, handed over with the step, so the check costs no read.\n *\n * The same module answers the record timeline's `prepareEmail` (AGL-2615): an\n * automation's email addressed to the contact the event is about is filed on\n * that contact's timeline — see {@link prepareCrmRecordEmail}.\n */\n\n/** One of the steps this module runs. */\nexport type CrmStepType = (typeof CRM_STEP_TYPES)[number]\n\n/**\n * A CRM step, as it is stored: the CRM's own shapes of the steps it runs. The\n * automation editor writes them and checks them; `apps/console/specs/\n * crm-automation-steps.spec.ts` runs what it writes through this module.\n */\nexport type CrmAutomationStep = (\n | { type: 'setContactStage'; lifecycleStage: ContactLifecycleStage }\n | { type: 'addContactTag'; tag: string }\n | { type: 'assignContactOwner'; ownerUid?: string; ownerEmail?: string; roundRobin?: boolean }\n | {\n type: 'createCrmTask'\n title: string\n kind: CrmTaskKind\n /** `normal` when absent — the steps written before priorities were (AGL-3517). */\n priority?: CrmTaskPriority\n dueInDays: number\n assigneeUid?: string\n assigneeEmail?: string\n }\n | {\n type: 'logCrmActivity'\n kind: CrmActivityKind\n body: string\n /** Which way a call or an email went (AGL-3517). */\n direction?: CrmActivityDirection\n }\n) & { when?: InteractionStepGuard | null } & { type: CrmStepType }\n\n/** The site, the plan and the org a step or an email is answered for. */\ninterface CrmStepEnv {\n hostId: string\n /** The owning org's billing doc, already read by the run's gate. */\n org: unknown\n orgId: string | null\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\ninterface ResolvedContact {\n id: string\n ref: FirebaseFirestore.DocumentReference\n data: Record<string, unknown>\n}\n\n/**\n * The person a CRM step acts on: a contact, or — when the workspace holds\n * none for them — the LEAD the site filed (AGL-3458).\n *\n * A lead-routed form files a lead and no contact (AGL-3232), and announces it\n * with the `lead` event. An automation welcoming that person — assign an\n * owner, book a call, tag them — has only the lead to act on, so each step\n * that has a lead's equivalent writes it there: the lead's own `ownerUid`,\n * its own `tags`, a task or an activity filed under `leadId`. A stage is a\n * contact's alone, and a step setting one on a lead says so.\n */\ntype ResolvedPerson =\n | ({ kind: 'contact' } & ResolvedContact)\n | ({ kind: 'lead' } & ResolvedContact)\n\n/**\n * The contact an event names, as this site may see it.\n *\n * Id first, because a CRM event carries the document the door just wrote and\n * a lookup by id is a read, not a query. The address second, through the\n * org's address index narrowed to this site (AGL-2633), for every event that\n * predates the CRM and knows only who filled in the form — so an address a\n * merge folded into another record still reaches the person who now holds it.\n */\nasync function resolveEventContact(\n hostId: string,\n named: { contactId?: unknown; email?: unknown },\n): Promise<ResolvedContact | null> {\n const { ref: contactsRef } = await orgDataQueryForHost(hostId, 'contacts')\n const contactId = String(named.contactId ?? '').trim()\n if (contactId) {\n const doc = await contactsRef.doc(contactId).get()\n if (doc.exists && visibleToHost(doc.get('visibleTo'), hostId)) {\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n }\n }\n const hit = await findContactByEmail(contactsRef, named.email, { hostId })\n return hit ? { id: hit.id, ref: hit.ref, data: hit.data() ?? {} } : null\n}\n\n/**\n * The lead an event names, as this site may see it: by `leadId` — the `lead`\n * event carries it, and it is the person's key — else by the key of the\n * `email`. A row this site cannot see is absent, exactly as a contact's is.\n */\nasync function resolveEventLead(\n hostId: string,\n named: { leadId?: unknown; email?: unknown },\n): Promise<ResolvedContact | null> {\n const key = String(named.leadId ?? '').trim() || personKey(named.email) || ''\n if (!key) return null\n const { ref: leadsRef } = await orgDataQueryForHost(hostId, 'leads')\n const doc = await leadsRef.doc(key).get()\n if (!doc.exists || !visibleToHost(doc.get('visibleTo'), hostId)) return null\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n}\n\n/** The contact the event names, else its lead — see {@link ResolvedPerson}. */\nasync function resolveEventPerson(\n hostId: string,\n named: { contactId?: unknown; leadId?: unknown; email?: unknown },\n): Promise<ResolvedPerson | null> {\n const contact = await resolveEventContact(hostId, named)\n if (contact) return { kind: 'contact', ...contact }\n const lead = await resolveEventLead(hostId, named)\n return lead ? { kind: 'lead', ...lead } : null\n}\n\n/**\n * The uid a step names for somebody on the team — an owner, an assignee —\n * resolved against the org's roster, whichever way the step named them.\n *\n * A step names a member by uid when a picker wrote it and by address when a\n * person typed it, and the editor's one text field accepts either — so both\n * fields are read through `parseCrmMemberRef`, and an address typed into the\n * uid slot or a uid typed into the address slot still names the person.\n *\n * Both are RESOLVED, not trusted. `orgs/{orgId}/members/{uid}` is keyed by the\n * uid, so a uid costs one document read and must exist: `ownerUid` and\n * `assigneeUid` are fields every reader resolves as a member, and a stranger's\n * uid in one is a task nobody on the team can find. An address is matched on\n * the roster's own `email` field, and two production paths create a member\n * document WITHOUT one (a host-access re-grant, and an add whose auth record\n * carried none) — such a member is named by uid, which is the reason the\n * editor takes one.\n *\n * The roster is the only directory consulted. A project-level Auth lookup by\n * address would resolve people who are not on this organization at all\n * (AGL-1122). A reference that resolves neither way is an error, not a stored\n * string.\n */\nasync function resolveMemberUid(\n orgId: string | null,\n named: { uid?: string; email?: string },\n role: 'owner' | 'assignee',\n): Promise<{ uid: string; detail: string } | { error: string }> {\n const ref = parseCrmMemberRef(named.uid) ?? parseCrmMemberRef(named.email)\n if (!ref) return { error: `no ${role} named on the step` }\n if (!orgId) return { error: 'this site has no organization' }\n const members = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('members')\n if (ref.kind === 'uid') {\n const member = await members.doc(ref.uid).get()\n if (member.exists) return { uid: ref.uid, detail: ref.uid }\n return { error: `no team member with the id ${ref.uid}` }\n }\n const byField = (await members.where('email', '==', ref.email).limit(1).get()).docs[0]\n if (byField) return { uid: byField.id, detail: ref.email }\n return { error: `no team member with the address ${ref.email}` }\n}\n\n/**\n * Runs one CRM step for the person the event names: the executor the CRM\n * registers on the server-step seam for every type in `CRM_STEP_TYPES`.\n *\n * Never throws for a plan without the CRM, a missing person, an unknown owner\n * or a site with no org — those are answered as `error` so the run records\n * them; a Firestore failure propagates, and the engine records it the same\n * way.\n */\nexport async function runCrmAutomationStep(\n request: ServerStepRequest,\n nowMs = Date.now(),\n): Promise<ServerStepAnswer> {\n if (!checkEntitlement(request.org as Parameters<typeof checkEntitlement>[0], CRM_SUITE_FEATURE)) {\n return { error: `CRM steps require the ${planLabelGrantingFeature(CRM_SUITE_FEATURE)} plan` }\n }\n const env: CrmStepEnv = { hostId: request.hostId, org: request.org, orgId: request.orgId }\n const step = request.step as unknown as CrmAutomationStep\n const actionId = request.run.id\n const { hostId } = env\n const person = await resolveEventPerson(hostId, request.payload)\n if (!person) {\n const named = String(request.payload['contactId'] ?? request.payload['email'] ?? '').trim()\n return {\n error: named\n ? `no contact or lead this site can see for ${named}`\n : 'the event names no contact — no contactId or email in its payload',\n }\n }\n if (person.kind === 'lead') return runLeadStep(env, actionId, step, person, nowMs)\n const contact = person\n /*\n * THE HOLDER whose facet the write addresses — the site's consent group,\n * which is the site alone unless the org declared the site one of a set\n * that presents as one sender. Same resolution the capture doors use, so a\n * stage set here lands in the facet the console reads.\n */\n const group = await consentGroupForSite(hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const email = String(contact.data['email'] ?? '')\n\n if (step.type === 'setContactStage') {\n const previous = facet.lifecycleStage ?? ''\n const label = CONTACT_LIFECYCLE_STAGE_LABELS[step.lifecycleStage]\n /*\n * A stage set to what it already is changes nothing and ANNOUNCES\n * nothing. The write is skipped so the row's `updatedAt` does not move for\n * a non-event, and the absent emit is what stops an action that sets the\n * stage it listens for from re-running on its own write — the engine's\n * nesting cap bounds that chain, this ends it at once.\n */\n if (previous === step.lifecycleStage) {\n return { detail: `${label} (already)` }\n }\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'lifecycleStage')]: step.lifecycleStage,\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The stage is what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n /*\n * A stage set by an automation IS a stage change, and the action\n * listening for it must run — but through the engine's nesting cap, so\n * the event is answered back for the engine to raise, never raised here.\n */\n return {\n detail: label,\n emit: {\n event: 'contactStageChanged',\n payload: {\n contactId: contact.id,\n email,\n lifecycleStage: step.lifecycleStage,\n previousStage: previous,\n },\n },\n }\n }\n\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n // `arrayUnion`, so the tag a merchant already applied by hand is not\n // duplicated and the tags beside it are kept.\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'tags')]: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The tags are what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return { detail: tag }\n }\n\n if (step.type === 'assignContactOwner') {\n /*\n * Through the one server assignment (AGL-2618), in both of the step's\n * modes, rather than a facet write of its own: the helper is what moves\n * the round-robin pointer inside the transaction that writes the owner,\n * mirrors the owner onto the site's lead, and tells the new owner. A\n * member named on the step is still resolved here — by address against\n * the roster, as before — and handed over by uid; the helper checks the\n * roster document again, which is one read and the same answer.\n */\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignContactOwner({\n hostId,\n contactId: contact.id,\n email,\n assign,\n })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') {\n return { detail: `${named} (already)` }\n }\n return {\n detail: step.roundRobin === true ? `round robin → ${verdict.ownerUid}` : named,\n }\n }\n\n // The two record creators: a task and an activity are documents of their\n // own beside the contact, stamped with the scope a contact captured on this\n // site would carry — `crmScopeTokens` is the create path's own expression,\n // so a record an automation made is visible to exactly the sites a record a\n // person made would be.\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(env.orgId)\n const visibleTo = crmScopeTokens((env.org ?? null) as Record<string, unknown> | null, group)\n const links = stampCrmActivityCompany(\n { contactId: contact.id },\n { contact: contact.data, groupId: group.groupId },\n )\n\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n /*\n * The assignee the step names, else the person who OWNS the contact, else\n * nobody. A follow-up task belongs to whoever holds the relationship, and\n * an automation that had to name one person for every contact it touches\n * would assign the whole list to one rep. A named assignee who cannot be\n * resolved is an error rather than a fallback to the owner: the author\n * named somebody on purpose.\n */\n let assignee = facet.ownerUid ?? ''\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const priority: CrmTaskPriority =\n step.priority === 'high' || step.priority === 'low' ? step.priority : 'normal'\n // The org's labels for the meanings the step names (AGL-3517).\n const labels = crmTaskLabelsForNew(\n await readCrmTaskPicklists(firebaseAdmin.app().firestore(), env.orgId),\n { kind: step.kind, priority },\n )\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority,\n status: 'open',\n ...labels,\n dueAtMs,\n // The reminder a person's task gets (AGL-2659): the due time.\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n // What the Tasks list searches by (AGL-3321), beside the task.\n await orgRef.collection(CRM_COLLECTIONS.tasks).add({ ...task, ...crmTaskListFields(task) })\n // The contact and company the task names carry `nextTaskAtMs` (AGL-2661);\n // a figure that could not move is the Fields section's recompute's.\n await recomputeCrmNextTaskAt(firebaseAdmin.app().firestore(), env.orgId, [links]).catch(\n (error: unknown) => {\n console.error('[crm] next activity could not be recomputed', env.orgId, error)\n },\n )\n return { detail: title.slice(0, 60) }\n }\n\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n /*\n * The per-record ceiling (AGL-2611), and this step is the writer it exists\n * for: a flow that logs on every page view fills one person's log in an\n * afternoon. Refused into the run history as an error, the way an\n * unresolvable assignee is, so the merchant reads why the flow stopped\n * writing rather than finding a log that silently stopped.\n */\n const logged = await countCrmActivitiesForRecord(orgRef, links)\n if (!crmActivityLogHasRoom(logged)) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const direction = crmActivityDirection(step.kind, step.direction)\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n ...(direction ? { direction } : {}),\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).add(activity)\n return { detail: step.kind }\n }\n\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * A CRM step on a LEAD (AGL-3458) — the person is not a contact yet, so each\n * step writes the lead's equivalent of what it writes on a contact. See\n * {@link ResolvedPerson}.\n */\nasync function runLeadStep(\n env: CrmStepEnv,\n actionId: string,\n step: CrmAutomationStep,\n lead: ResolvedContact,\n nowMs: number,\n): Promise<ServerStepAnswer> {\n const { hostId } = env\n if (step.type === 'setContactStage') {\n return {\n error:\n 'the event names a lead, and a lead has no lifecycle stage until it is ' +\n 'converted to a contact',\n }\n }\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n await lead.ref.update({\n tags: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // A lead's tags are what the Leads list searches (AGL-3321).\n await restampCrmListFieldsAt(lead.ref, 'leads')\n return { detail: `${tag} (lead)` }\n }\n if (step.type === 'assignContactOwner') {\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignLeadOwner({ hostId, leadId: lead.id, assign })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') return { detail: `${named} (already, lead)` }\n return {\n detail:\n step.roundRobin === true\n ? `round robin → ${verdict.ownerUid} (lead)`\n : `${named} (lead)`,\n }\n }\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin.app().firestore().collection('orgs').doc(env.orgId)\n const group = await consentGroupForSite(hostId)\n const visibleTo = crmScopeTokens((env.org ?? null) as Record<string, unknown> | null, group)\n // Filed under the lead, as a sequence's call step files one (AGL-3233); a\n // conversion stamps the contact beside it, and the task follows the person.\n const links = { leadId: lead.id }\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n // The named assignee, else whoever owns the lead NOW — an owner step\n // earlier in this same run wrote the lead after it was resolved, so it\n // is read again — else nobody.\n let assignee: string\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n } else {\n const fresh = await lead.ref.get()\n assignee = String((fresh.data() ?? lead.data)['ownerUid'] ?? '')\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority: 'normal',\n status: 'open',\n dueAtMs,\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef\n .collection(CRM_COLLECTIONS.tasks)\n .doc(createResourceUid())\n .set({ ...task, ...crmTaskListFields(task) })\n return { detail: `${title.slice(0, 60)} (lead)` }\n }\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, links))) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).doc(createResourceUid()).set(activity)\n return { detail: `${step.kind} (lead)` }\n }\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * Whether — and where — an email a plugin is about to send lands on a\n * timeline: the CRM's answer to the record timeline's `prepareEmail`.\n *\n * An automation's `sendEmail` step is not a CRM step: it mails whatever\n * address the event carries, to a person who may be nobody the CRM knows. It\n * earns a row on exactly one condition, that the message is ADDRESSED TO THE\n * PERSON the sender means — a contact, else the lead a lead-routed form filed\n * (AGL-3458), whose row lands on the lead's timeline — the person the request's link finds, at the\n * address the row holds. A welcome sequence to a new contact is that; an\n * internal alert routed to a merchant's own address through `toField` is not,\n * and a row for it would put the merchant's inbox on a customer's history.\n *\n * Prepared ahead of the send, for the reason the console route mints its id\n * first: the delivery webhook has nothing but the tags on the message to find\n * the row with. Nothing is written until `file` is called, once the provider\n * accepted the message. A workspace whose plan does not carry the CRM, a\n * record at the activity ceiling and a site with no org earn no row — the\n * message still goes, because the ceiling bounds the log and not the mail.\n *\n * **Never throws**, and neither does `file`. The row is bookkeeping beside a\n * send, and a lookup that fails must not become a message that never left —\n * the posture every meter beside a send takes. A failure is logged and the\n * message goes out untagged.\n */\nexport async function prepareCrmRecordEmail(\n request: PluginRecordEmailPrepareRequest,\n): Promise<PluginRecordPreparedEmail | null> {\n const orgId = String(request.orgId ?? '').trim()\n const hostId = String(request.hostId ?? '').trim()\n if (!orgId || !hostId) return null\n const address = normalizeContactEmail(request.to)\n if (!address) return null\n try {\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(orgId)\n // The org the caller already read, or the one read here once.\n const org =\n request.org !== undefined ? request.org : ((await orgRef.get()).data() ?? null)\n if (!checkEntitlement(org as Parameters<typeof checkEntitlement>[0], CRM_SUITE_FEATURE)) {\n return null\n }\n const person = await resolveEventPerson(hostId, request.link)\n if (!person || normalizeContactEmail(person.data['email']) !== address) {\n return null\n }\n const group = await consentGroupForSite(hostId)\n const link: CrmActivityLink =\n person.kind === 'lead'\n ? { leadId: person.id }\n : stampCrmActivityCompany(\n { contactId: person.id },\n { contact: person.data, groupId: group.groupId },\n )\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) {\n return null\n }\n const ref = newCrmActivityRef(firestore, orgId)\n const visibleTo = crmScopeTokens((org ?? null) as Record<string, unknown> | null, group)\n return {\n tags: crmEmailDeliveryTags({ orgId, hostId, activityId: ref.id }),\n /*\n * The same shape the console route logs — `buildCrmEmailActivity` is the\n * one builder — with the sender's id as its source and nobody as its\n * author. Never throws: the message has left, and a row that could not\n * be written is a gap on the timeline rather than a failed send.\n */\n async file(sent) {\n try {\n await writeCrmEmailActivity(\n ref,\n buildCrmEmailActivity({\n subject: sent.subject,\n body: sent.body,\n to: normalizeContactEmail(sent.to) ?? sent.to,\n atMs: sent.atMs ?? Date.now(),\n byUid: '',\n sourceActionId: sent.sourceRef,\n link,\n hostId,\n visibleTo,\n }),\n )\n } catch (error) {\n console.error('[crm] automation email activity write failed', ref.id, error)\n }\n },\n }\n } catch (error) {\n console.error('[crm] automation email activity could not be prepared', hostId, error)\n return null\n }\n}\n"],"names":["buildCrmEmailActivity","checkEntitlement","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_TAG_MAX_LENGTH","contactFacetPath","CRM_ACTIVITY_LOG_FULL_MESSAGE","CRM_COLLECTIONS","crmActivityLogHasRoom","crmActivityDirection","crmEmailDeliveryTags","crmScopeTokens","crmTaskLabelsForNew","crmTaskListFields","normalizeContactEmail","parseCrmMemberRef","personKey","planLabelGrantingFeature","readContactFacet","visibleToHost","consentGroupForSite","countCrmActivitiesForRecord","firebaseAdmin","orgDataQueryForHost","restampCrmListFieldsAt","findContactByEmail","FieldValue","OWNER_ASSIGNMENT_REFUSALS","reassignContactOwner","reassignLeadOwner","createResourceUid","readCrmTaskPicklists","CRM_SUITE_FEATURE","crmTaskReminderAfterEdit","recomputeCrmNextTaskAt","newCrmActivityRef","writeCrmEmailActivity","stampCrmActivityCompany","DAY_MS","resolveEventContact","hostId","named","hit","ref","contactsRef","contactId","String","trim","doc","get","exists","id","data","email","resolveEventLead","key","leadId","leadsRef","resolveEventPerson","contact","kind","lead","resolveMemberUid","orgId","role","uid","error","members","app","firestore","collection","member","detail","byField","where","limit","docs","runCrmAutomationStep","request","nowMs","Date","now","env","org","step","actionId","run","person","payload","runLeadStep","group","facet","groupId","type","previous","lifecycleStage","label","update","updatedAt","serverTimestamp","emit","event","previousStage","tag","slice","arrayUnion","assign","roundRobin","owner","ownerUid","ownerEmail","memberUid","verdict","outcome","reason","orgRef","visibleTo","links","title","dueInDays","Math","max","round","Number","assignee","assigneeUid","assigneeEmail","dueAtMs","priority","labels","task","status","remindAtMs","createdByUid","sourceActionId","createdAt","tasks","add","catch","console","body","logged","direction","activity","atMs","byUid","activities","tags","fresh","set","prepareCrmRecordEmail","address","to","undefined","link","activityId","file","sent","subject","sourceRef"],"mappings":"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"}