@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
@@ -15,7 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { nameSearchFields } from "@aglyn/aglyn/app-utils/name-search";
18
- import { CAPTURED_BY_HOST_FIELD, composeContactName, CONTACT_BIRTHDATE_REFUSAL, CONTACT_COMPANY_IDS_FIELD, CONTACT_EXTRA_PHONE_FIELDS, CONTACT_FACETS_FIELD, CONTACT_FIELDS_MAX_PER_ORG, CONTACT_LIFECYCLE_STAGES, CONTACT_NAME_PART_MAX, consentGroupForHost, contactFacetPath, createResourceUid, CRM_COLLECTIONS, CRM_LEAD_SOURCE_PICKLIST, CRM_MEDIA_IDS_MAX, CRM_SALUTATION_PICKLIST, isContactLifecycleStage, judgeCrmPicklistValue, MARKETING_CONSENT_BY_HOST_FIELD, marketingConsentFieldsForGroup, marketingConsentHostIds, normalizeAddress, normalizeContactBirthdate, normalizeContactEmail, normalizeCrmMediaIds, normalizePhone, ORG_SCOPE_TOKEN, planContactCompanyLink, readContactCompanyLink, readContactCustomInput, readContactFacet, soloConsentGroup } from "@aglyn/aglyn/server";
18
+ import { CAPTURED_BY_HOST_FIELD, composeContactName, CONTACT_BIRTHDATE_REFUSAL, CONTACT_COMPANY_IDS_FIELD, CONTACT_EXTRA_PHONE_FIELDS, CONTACT_FACETS_FIELD, ORG_CONTACT_HOLDER_ID, CONTACT_FIELDS_MAX_PER_ORG, CONTACT_LIFECYCLE_STAGES, CONTACT_NAME_PART_MAX, consentGroupForHost, crmDefaultScopeOf, crmScopeTokens, contactFacetPath, createResourceUid, CRM_COLLECTIONS, CRM_LEAD_SOURCE_PICKLIST, CRM_MEDIA_IDS_MAX, CRM_SALUTATION_PICKLIST, isContactLifecycleStage, judgeCrmPicklistValue, MARKETING_CONSENT_BY_HOST_FIELD, marketingConsentFieldsForGroup, marketingConsentHostIds, normalizeAddress, normalizeContactBirthdate, normalizeContactEmail, normalizeCrmMediaIds, normalizePhone, ORG_SCOPE_TOKEN, planContactCompanyLink, readContactCompanyLink, readContactCustomInput, readContactFacet, soloConsentGroup } from "@aglyn/aglyn/server";
19
19
  import { ApiErrors, apiJson, contactCompanyMirrorValue, crmRecordsQuotaForOrg, listResponse, restampCrmListFieldsAt, settleCompanyContactsCounts } from "@aglyn/tenant-data-admin";
20
20
  import { claimWrite, orgOwnsHost, paginate, readJsonBody, requireScope, serialize } from "@aglyn/tenant-data-admin/server/api-v1-kit";
21
21
  // The leaf, so a spec that stands a partial barrel in still reaches it.
@@ -163,12 +163,13 @@ function contactCrmProfile(data, groupId) {
163
163
  * @param groupId - the holder whose CRM profile to publish, or `null` for
164
164
  * the union — see {@link contactCrmProfile}.
165
165
  */ function contactView(doc, groupId = null) {
166
- var _doc_data, _data_email, _data_name, _data_tags, _data_notes, _serialize, _serialize1;
166
+ var _doc_data, _data_name, _data_tags, _data_notes, _serialize, _serialize1;
167
167
  const data = (_doc_data = doc.data()) != null ? _doc_data : {};
168
168
  return _extends({
169
169
  id: doc.id,
170
170
  object: 'contact',
171
- email: (_data_email = data.email) != null ? _data_email : null,
171
+ // Blank in the store so a sort lists them; no address to an integrator.
172
+ email: data.email || null,
172
173
  name: (_data_name = data.name) != null ? _data_name : null,
173
174
  tags: (_data_tags = data.tags) != null ? _data_tags : [],
174
175
  notes: (_data_notes = data.notes) != null ? _data_notes : null
@@ -259,9 +260,13 @@ function readContactInput(body, { partial }) {
259
260
  // The SAME normalizer every capture point uses. Re-implementing the
260
261
  // check here would let the API accept an address `upsertHostContact`
261
262
  // would reject, and the two would then disagree about who is a duplicate.
262
- const email = normalizeContactEmail(body.email);
263
- if (!email) errors.email = 'A valid email address is required';
264
- else values.email = email;
263
+ // Optional (AGL-4122): an address that was sent still has to be one, and
264
+ // a person with none is named below instead.
265
+ if (body.email !== undefined && body.email !== null && body.email !== '') {
266
+ const email = normalizeContactEmail(body.email);
267
+ if (!email) errors.email = 'A valid email address is required';
268
+ else values.email = email;
269
+ }
265
270
  }
266
271
  if (body.name !== undefined) {
267
272
  var _body_name;
@@ -449,9 +454,16 @@ function readContactInput(body, { partial }) {
449
454
  * sites is this write made on behalf of — and an integrator should not
450
455
  * learn two names for it. Beside a refusal the site is accepted, since the
451
456
  * refusal still applies to every site and the site now names the facet.
452
- */ if (writesCrm && !values.consentSiteId) {
457
+ */ if (writesCrm && !values.consentSiteId && partial) {
453
458
  errors.consentSiteId = 'Required with a CRM profile field — name the site whose profile of this person to write';
454
459
  }
460
+ /*
461
+ * WHO THIS IS (AGL-4122). A contact needs no address, so a create needs
462
+ * something else to call them by. With no site and no address the profile is
463
+ * the organization's own, and nothing here narrows that.
464
+ */ if (!partial && !values.email && !values.name && !values.crm.phone && !values.crm.mobilePhone && !values.crm.firstName && !values.crm.lastName && !errors.email) {
465
+ errors.email = 'Send an email, a name or a phone number';
466
+ }
455
467
  if (values.marketingConsent === false && values.consentSiteId && !writesCrm) {
456
468
  errors.consentSiteId = 'Not accepted with marketingConsent: false — a refusal applies to every site';
457
469
  }
@@ -580,10 +592,11 @@ function readContactInput(body, { partial }) {
580
592
  // The holder's name is the first and last names' when either was given.
581
593
  const composed = composeContactName(crm.firstName, crm.lastName);
582
594
  if (composed) stored['name'] = composed;
583
- const group = consentGroupForHost(ctx.org, siteId);
595
+ // The person's holder, or the organization when no site was named (AGL-4122).
596
+ const groupId = siteId ? consentGroupForHost(ctx.org, siteId).groupId : ORG_CONTACT_HOLDER_ID;
584
597
  return _extends({
585
598
  [CONTACT_FACETS_FIELD]: {
586
- [group.groupId]: _extends({
599
+ [groupId]: _extends({
587
600
  sources: {
588
601
  api: true
589
602
  },
@@ -717,10 +730,11 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
717
730
  * taking-and-releasing on a genuine refusal. AGL-2278 applies it to the two
718
731
  * older creates.
719
732
  *
720
- * `visibleTo` is stamped with `ORG_SCOPE_TOKEN`, as capture does. A contact
721
- * written without it matches no `array-contains-any` and is therefore visible
722
- * on NO site (AGL-1044) — the API would create data nobody can see, which is
723
- * worse than refusing.
733
+ * `visibleTo` is stamped with `ORG_SCOPE_TOKEN`, as capture does — or, in an
734
+ * org that keeps each site's records apart, the named site's group, which
735
+ * that org must then name. A contact written without one matches no
736
+ * `array-contains-any` and is therefore visible on NO site (AGL-1044) — the
737
+ * API would create data nobody can see, which is worse than refusing.
724
738
  */ async function createContact(request, ctx) {
725
739
  // Validation stays above the claim, as `createRecord` argues: a
726
740
  // deterministic 400 must never take the key at all, so an integrator fixes
@@ -747,6 +761,25 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
747
761
  headers: ctx.headers
748
762
  });
749
763
  }
764
+ /*
765
+ * Who may see the new contact: the whole org, unless the org keeps each
766
+ * site's records apart — then the named site's group, and a contact named
767
+ * by no site has nowhere to belong. A deterministic 400, so above the claim.
768
+ */ const orgDocument = ctx.org;
769
+ const keepsSitesApart = crmDefaultScopeOf(orgDocument) === 'host';
770
+ if (keepsSitesApart && !consentSiteId) {
771
+ return ApiErrors.badRequest({
772
+ message: 'Contact failed validation',
773
+ code: 'validation_failed',
774
+ fields: {
775
+ consentSiteId: "Required — this organization keeps each site's records apart, so name the site this contact belongs to"
776
+ },
777
+ headers: ctx.headers
778
+ });
779
+ }
780
+ const visibleTo = keepsSitesApart && consentSiteId ? crmScopeTokens(orgDocument, consentGroupForHost(orgDocument, consentSiteId)) : [
781
+ ORG_SCOPE_TOKEN
782
+ ];
750
783
  // Judged against the org's definitions, above the claim like every other
751
784
  // deterministic 400: an unknown key is named, never dropped (AGL-2601).
752
785
  const custom = parsed.values.custom ? readContactCustomInput(parsed.values.custom, await readOrgContactFieldDefinitions(ctx)) : null;
@@ -780,16 +813,20 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
780
813
  if ('replay' in claimed) return claimed.replay;
781
814
  const { claim } = claimed;
782
815
  try {
816
+ var _docs_;
783
817
  // Through the org's address index (AGL-2633): an address a merge folded
784
818
  // into another record is that record's, and a create on it would mint
785
819
  // the duplicate the merge removed.
786
- const existing = await findContactByEmail(collection, email);
820
+ // With no address the number is who they are, and with neither nobody is:
821
+ // two blank emails are not the same person (AGL-4122).
822
+ const phone = crm.phone || crm.mobilePhone || '';
823
+ const existing = email ? await findContactByEmail(collection, email) : phone ? (_docs_ = (await collection.where('phone', '==', phone).limit(1).get()).docs[0]) != null ? _docs_ : null : null;
787
824
  if (existing) {
788
825
  // Released: the conflict clears if that contact is deleted or merged
789
826
  // upstream, and the retry that should then succeed must not replay it.
790
827
  await claim.release();
791
828
  return ApiErrors.conflict({
792
- message: `A contact with this email already exists (${existing.id}). Update it instead.`,
829
+ message: `A contact with this ${email ? 'email' : 'phone number'} already exists (${existing.id}). Update it instead.`,
793
830
  code: 'contact_exists',
794
831
  headers: ctx.headers
795
832
  });
@@ -813,8 +850,11 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
813
850
  }
814
851
  const id = createResourceUid();
815
852
  await collection.doc(id).create(_extends({
816
- email
817
- }, canonicalName ? nameSearchFields(canonicalName) : {}, {
853
+ // Blank rather than absent, so a sort by email still lists them.
854
+ email: email != null ? email : ''
855
+ }, !email && phone ? {
856
+ phone
857
+ } : {}, canonicalName ? nameSearchFields(canonicalName) : {}, {
818
858
  tags: tags != null ? tags : []
819
859
  }, notes ? {
820
860
  notes
@@ -826,7 +866,7 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
826
866
  ] : []
827
867
  }, marketingConsent === false ? {
828
868
  marketingConsent: false
829
- } : {}, consentSiteId ? contactCrmCreateFields(ctx, crm, consentSiteId) : {}, {
869
+ } : {}, contactCrmCreateFields(ctx, crm, consentSiteId), {
830
870
  // `sources.api` — a first-class provenance value beside `form`,
831
871
  // `member`, `order` and `booking`, so a merchant reading the console
832
872
  // can see which people an integration put there.
@@ -838,9 +878,7 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
838
878
  // "No next activity" filter asks as `null` (AGL-3321).
839
879
  nextTaskAtMs: null,
840
880
  // AGL-1044/AGL-1037: without this the contact matches no scoped read.
841
- visibleTo: [
842
- ORG_SCOPE_TOKEN
843
- ],
881
+ visibleTo,
844
882
  createdAt: Timestamp.now(),
845
883
  updatedAt: Timestamp.now()
846
884
  }));
@@ -849,14 +887,14 @@ const contactsCollection = (ctx)=>ctx.firestore.collection('orgs').doc(ctx.orgId
849
887
  await restampCrmListFieldsAt(collection.doc(id), 'contacts');
850
888
  // The address's domain, checked after the response (AGL-3328): a domain
851
889
  // that takes no mail makes the new contact read "Would bounce".
852
- scheduleCapturedEmailCheck({
890
+ if (email) scheduleCapturedEmailCheck({
853
891
  orgId: ctx.orgId,
854
892
  email
855
893
  });
856
894
  // The company the body named has one more contact naming it (AGL-2613);
857
895
  // a fresh row's plan is the trivial one, and `crmRefErrors` has already
858
896
  // required the company to exist.
859
- if (consentSiteId && crm.companyId) {
897
+ if (crm.companyId) {
860
898
  await settleCompanyContactsCounts(companiesCollection(ctx), planContactCompanyLink({
861
899
  companyId: null,
862
900
  companyIds: [],