@kelpie/server 0.8.0 → 0.10.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.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ids.d.ts +11 -0
- package/dist/lib/ids.d.ts.map +1 -1
- package/dist/lib/ids.js +11 -0
- package/dist/lib/ids.js.map +1 -1
- package/dist/modules/activities/repository.d.ts.map +1 -1
- package/dist/modules/activities/repository.js +19 -20
- package/dist/modules/activities/repository.js.map +1 -1
- package/dist/modules/activities/wording.d.ts +12 -0
- package/dist/modules/activities/wording.d.ts.map +1 -1
- package/dist/modules/activities/wording.js +16 -0
- package/dist/modules/activities/wording.js.map +1 -1
- package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
- package/dist/modules/agent-tasks/catalog.js +62 -0
- package/dist/modules/agent-tasks/catalog.js.map +1 -1
- package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
- package/dist/modules/agent-tasks/resolve.js +42 -9
- package/dist/modules/agent-tasks/resolve.js.map +1 -1
- package/dist/modules/agent-tasks/routes.d.ts +2 -0
- package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
- package/dist/modules/attachedRecords.d.ts +6 -1
- package/dist/modules/attachedRecords.d.ts.map +1 -1
- package/dist/modules/attachedRecords.js +15 -1
- package/dist/modules/attachedRecords.js.map +1 -1
- package/dist/modules/companies/index.d.ts.map +1 -1
- package/dist/modules/companies/index.js +3 -1
- package/dist/modules/companies/index.js.map +1 -1
- package/dist/modules/companies/routes.d.ts +14 -0
- package/dist/modules/companies/routes.d.ts.map +1 -1
- package/dist/modules/companies/routes.js +7 -0
- package/dist/modules/companies/routes.js.map +1 -1
- package/dist/modules/companies/schema.d.ts +20 -0
- package/dist/modules/companies/schema.d.ts.map +1 -1
- package/dist/modules/companies/schema.js +6 -1
- package/dist/modules/companies/schema.js.map +1 -1
- package/dist/modules/companies/service.d.ts +5 -0
- package/dist/modules/companies/service.d.ts.map +1 -1
- package/dist/modules/companies/service.js +24 -12
- package/dist/modules/companies/service.js.map +1 -1
- package/dist/modules/consent-purposes/capabilities.d.ts +7 -0
- package/dist/modules/consent-purposes/capabilities.d.ts.map +1 -0
- package/dist/modules/consent-purposes/capabilities.js +10 -0
- package/dist/modules/consent-purposes/capabilities.js.map +1 -0
- package/dist/modules/consent-purposes/events.d.ts +38 -0
- package/dist/modules/consent-purposes/events.d.ts.map +1 -0
- package/dist/modules/consent-purposes/events.js +17 -0
- package/dist/modules/consent-purposes/events.js.map +1 -0
- package/dist/modules/consent-purposes/index.d.ts +13 -0
- package/dist/modules/consent-purposes/index.d.ts.map +1 -0
- package/dist/modules/consent-purposes/index.js +41 -0
- package/dist/modules/consent-purposes/index.js.map +1 -0
- package/dist/modules/consent-purposes/repository.d.ts +21 -0
- package/dist/modules/consent-purposes/repository.d.ts.map +1 -0
- package/dist/modules/consent-purposes/repository.js +98 -0
- package/dist/modules/consent-purposes/repository.js.map +1 -0
- package/dist/modules/consent-purposes/routes.d.ts +39 -0
- package/dist/modules/consent-purposes/routes.d.ts.map +1 -0
- package/dist/modules/consent-purposes/routes.js +79 -0
- package/dist/modules/consent-purposes/routes.js.map +1 -0
- package/dist/modules/consent-purposes/schema.d.ts +175 -0
- package/dist/modules/consent-purposes/schema.d.ts.map +1 -0
- package/dist/modules/consent-purposes/schema.js +35 -0
- package/dist/modules/consent-purposes/schema.js.map +1 -0
- package/dist/modules/consent-purposes/service.d.ts +50 -0
- package/dist/modules/consent-purposes/service.d.ts.map +1 -0
- package/dist/modules/consent-purposes/service.js +263 -0
- package/dist/modules/consent-purposes/service.js.map +1 -0
- package/dist/modules/consent-purposes/starters.d.ts +17 -0
- package/dist/modules/consent-purposes/starters.d.ts.map +1 -0
- package/dist/modules/consent-purposes/starters.js +15 -0
- package/dist/modules/consent-purposes/starters.js.map +1 -0
- package/dist/modules/consent-purposes/tools.d.ts +4 -0
- package/dist/modules/consent-purposes/tools.d.ts.map +1 -0
- package/dist/modules/consent-purposes/tools.js +29 -0
- package/dist/modules/consent-purposes/tools.js.map +1 -0
- package/dist/modules/core.d.ts.map +1 -1
- package/dist/modules/core.js +10 -0
- package/dist/modules/core.js.map +1 -1
- package/dist/modules/custom-fields/capabilities.d.ts +12 -0
- package/dist/modules/custom-fields/capabilities.d.ts.map +1 -0
- package/dist/modules/custom-fields/capabilities.js +15 -0
- package/dist/modules/custom-fields/capabilities.js.map +1 -0
- package/dist/modules/custom-fields/events.d.ts +49 -0
- package/dist/modules/custom-fields/events.d.ts.map +1 -0
- package/dist/modules/custom-fields/events.js +25 -0
- package/dist/modules/custom-fields/events.js.map +1 -0
- package/dist/modules/custom-fields/index.d.ts +19 -0
- package/dist/modules/custom-fields/index.d.ts.map +1 -0
- package/dist/modules/custom-fields/index.js +46 -0
- package/dist/modules/custom-fields/index.js.map +1 -0
- package/dist/modules/custom-fields/repository.d.ts +37 -0
- package/dist/modules/custom-fields/repository.d.ts.map +1 -0
- package/dist/modules/custom-fields/repository.js +144 -0
- package/dist/modules/custom-fields/repository.js.map +1 -0
- package/dist/modules/custom-fields/routes.d.ts +44 -0
- package/dist/modules/custom-fields/routes.d.ts.map +1 -0
- package/dist/modules/custom-fields/routes.js +103 -0
- package/dist/modules/custom-fields/routes.js.map +1 -0
- package/dist/modules/custom-fields/schema.d.ts +215 -0
- package/dist/modules/custom-fields/schema.d.ts.map +1 -0
- package/dist/modules/custom-fields/schema.js +43 -0
- package/dist/modules/custom-fields/schema.js.map +1 -0
- package/dist/modules/custom-fields/service.d.ts +57 -0
- package/dist/modules/custom-fields/service.d.ts.map +1 -0
- package/dist/modules/custom-fields/service.js +273 -0
- package/dist/modules/custom-fields/service.js.map +1 -0
- package/dist/modules/custom-fields/tools.d.ts +4 -0
- package/dist/modules/custom-fields/tools.d.ts.map +1 -0
- package/dist/modules/custom-fields/tools.js +38 -0
- package/dist/modules/custom-fields/tools.js.map +1 -0
- package/dist/modules/custom-fields/values.d.ts +49 -0
- package/dist/modules/custom-fields/values.d.ts.map +1 -0
- package/dist/modules/custom-fields/values.js +249 -0
- package/dist/modules/custom-fields/values.js.map +1 -0
- package/dist/modules/custom-fields/wire.d.ts +12 -0
- package/dist/modules/custom-fields/wire.d.ts.map +1 -0
- package/dist/modules/custom-fields/wire.js +30 -0
- package/dist/modules/custom-fields/wire.js.map +1 -0
- package/dist/modules/dashboard/repository.d.ts.map +1 -1
- package/dist/modules/dashboard/repository.js +4 -3
- package/dist/modules/dashboard/repository.js.map +1 -1
- package/dist/modules/deals/index.d.ts +2 -1
- package/dist/modules/deals/index.d.ts.map +1 -1
- package/dist/modules/deals/index.js +5 -2
- package/dist/modules/deals/index.js.map +1 -1
- package/dist/modules/deals/repository.d.ts +0 -16
- package/dist/modules/deals/repository.d.ts.map +1 -1
- package/dist/modules/deals/repository.js +6 -76
- package/dist/modules/deals/repository.js.map +1 -1
- package/dist/modules/deals/routes.d.ts +14 -0
- package/dist/modules/deals/routes.d.ts.map +1 -1
- package/dist/modules/deals/routes.js +7 -0
- package/dist/modules/deals/routes.js.map +1 -1
- package/dist/modules/deals/schema.d.ts +20 -42
- package/dist/modules/deals/schema.d.ts.map +1 -1
- package/dist/modules/deals/schema.js +6 -11
- package/dist/modules/deals/schema.js.map +1 -1
- package/dist/modules/deals/service.d.ts +13 -2
- package/dist/modules/deals/service.d.ts.map +1 -1
- package/dist/modules/deals/service.js +43 -20
- package/dist/modules/deals/service.js.map +1 -1
- package/dist/modules/decisions/repository.d.ts.map +1 -1
- package/dist/modules/decisions/repository.js +2 -0
- package/dist/modules/decisions/repository.js.map +1 -1
- package/dist/modules/decisions/routes.d.ts +1 -0
- package/dist/modules/decisions/routes.d.ts.map +1 -1
- package/dist/modules/enquiries/events.d.ts +44 -0
- package/dist/modules/enquiries/events.d.ts.map +1 -0
- package/dist/modules/enquiries/events.js +19 -0
- package/dist/modules/enquiries/events.js.map +1 -0
- package/dist/modules/enquiries/index.d.ts +13 -0
- package/dist/modules/enquiries/index.d.ts.map +1 -0
- package/dist/modules/enquiries/index.js +44 -0
- package/dist/modules/enquiries/index.js.map +1 -0
- package/dist/modules/enquiries/repository.d.ts +30 -0
- package/dist/modules/enquiries/repository.d.ts.map +1 -0
- package/dist/modules/enquiries/repository.js +94 -0
- package/dist/modules/enquiries/repository.js.map +1 -0
- package/dist/modules/enquiries/routes.d.ts +52 -0
- package/dist/modules/enquiries/routes.d.ts.map +1 -0
- package/dist/modules/enquiries/routes.js +127 -0
- package/dist/modules/enquiries/routes.js.map +1 -0
- package/dist/modules/enquiries/schema.d.ts +290 -0
- package/dist/modules/enquiries/schema.d.ts.map +1 -0
- package/dist/modules/enquiries/schema.js +52 -0
- package/dist/modules/enquiries/schema.js.map +1 -0
- package/dist/modules/enquiries/service.d.ts +85 -0
- package/dist/modules/enquiries/service.d.ts.map +1 -0
- package/dist/modules/enquiries/service.js +370 -0
- package/dist/modules/enquiries/service.js.map +1 -0
- package/dist/modules/enquiries/tools.d.ts +4 -0
- package/dist/modules/enquiries/tools.d.ts.map +1 -0
- package/dist/modules/enquiries/tools.js +50 -0
- package/dist/modules/enquiries/tools.js.map +1 -0
- package/dist/modules/eventCatalogs.d.ts +3 -0
- package/dist/modules/eventCatalogs.d.ts.map +1 -1
- package/dist/modules/eventCatalogs.js +3 -0
- package/dist/modules/eventCatalogs.js.map +1 -1
- package/dist/modules/forms/embed.d.ts +20 -6
- package/dist/modules/forms/embed.d.ts.map +1 -1
- package/dist/modules/forms/embed.js +359 -43
- package/dist/modules/forms/embed.js.map +1 -1
- package/dist/modules/forms/events.d.ts +20 -7
- package/dist/modules/forms/events.d.ts.map +1 -1
- package/dist/modules/forms/events.js +20 -2
- package/dist/modules/forms/events.js.map +1 -1
- package/dist/modules/forms/fields.d.ts +14 -1
- package/dist/modules/forms/fields.d.ts.map +1 -1
- package/dist/modules/forms/fields.js +87 -3
- package/dist/modules/forms/fields.js.map +1 -1
- package/dist/modules/forms/index.d.ts.map +1 -1
- package/dist/modules/forms/index.js +12 -1
- package/dist/modules/forms/index.js.map +1 -1
- package/dist/modules/forms/mapping.d.ts +62 -5
- package/dist/modules/forms/mapping.d.ts.map +1 -1
- package/dist/modules/forms/mapping.js +139 -5
- package/dist/modules/forms/mapping.js.map +1 -1
- package/dist/modules/forms/publicRoutes.d.ts.map +1 -1
- package/dist/modules/forms/publicRoutes.js +29 -1
- package/dist/modules/forms/publicRoutes.js.map +1 -1
- package/dist/modules/forms/repository.d.ts +31 -0
- package/dist/modules/forms/repository.d.ts.map +1 -1
- package/dist/modules/forms/repository.js +90 -1
- package/dist/modules/forms/repository.js.map +1 -1
- package/dist/modules/forms/routes.d.ts +83 -1
- package/dist/modules/forms/routes.d.ts.map +1 -1
- package/dist/modules/forms/routes.js +185 -2
- package/dist/modules/forms/routes.js.map +1 -1
- package/dist/modules/forms/schema.d.ts +672 -1
- package/dist/modules/forms/schema.d.ts.map +1 -1
- package/dist/modules/forms/schema.js +146 -3
- package/dist/modules/forms/schema.js.map +1 -1
- package/dist/modules/forms/service.d.ts +54 -1
- package/dist/modules/forms/service.d.ts.map +1 -1
- package/dist/modules/forms/service.js +338 -28
- package/dist/modules/forms/service.js.map +1 -1
- package/dist/modules/forms/submission.d.ts +15 -0
- package/dist/modules/forms/submission.d.ts.map +1 -1
- package/dist/modules/forms/submission.js +418 -40
- package/dist/modules/forms/submission.js.map +1 -1
- package/dist/modules/import-export/drafts.d.ts +10 -0
- package/dist/modules/import-export/drafts.d.ts.map +1 -1
- package/dist/modules/import-export/drafts.js +16 -2
- package/dist/modules/import-export/drafts.js.map +1 -1
- package/dist/modules/import-export/exportRows.d.ts +7 -4
- package/dist/modules/import-export/exportRows.d.ts.map +1 -1
- package/dist/modules/import-export/exportRows.js +20 -5
- package/dist/modules/import-export/exportRows.js.map +1 -1
- package/dist/modules/import-export/plan.d.ts +12 -1
- package/dist/modules/import-export/plan.d.ts.map +1 -1
- package/dist/modules/import-export/plan.js +29 -2
- package/dist/modules/import-export/plan.js.map +1 -1
- package/dist/modules/import-export/presets.d.ts.map +1 -1
- package/dist/modules/import-export/presets.js +10 -0
- package/dist/modules/import-export/presets.js.map +1 -1
- package/dist/modules/import-export/repository.d.ts +11 -1
- package/dist/modules/import-export/repository.d.ts.map +1 -1
- package/dist/modules/import-export/repository.js +42 -9
- package/dist/modules/import-export/repository.js.map +1 -1
- package/dist/modules/import-export/routes.d.ts.map +1 -1
- package/dist/modules/import-export/routes.js +4 -0
- package/dist/modules/import-export/routes.js.map +1 -1
- package/dist/modules/import-export/schema.d.ts +17 -0
- package/dist/modules/import-export/schema.d.ts.map +1 -1
- package/dist/modules/import-export/schema.js +11 -0
- package/dist/modules/import-export/schema.js.map +1 -1
- package/dist/modules/import-export/service.d.ts +6 -0
- package/dist/modules/import-export/service.d.ts.map +1 -1
- package/dist/modules/import-export/service.js +14 -2
- package/dist/modules/import-export/service.js.map +1 -1
- package/dist/modules/import-export/streams.d.ts.map +1 -1
- package/dist/modules/import-export/streams.js +3 -3
- package/dist/modules/import-export/streams.js.map +1 -1
- package/dist/modules/import-export/tools.d.ts.map +1 -1
- package/dist/modules/import-export/tools.js +8 -0
- package/dist/modules/import-export/tools.js.map +1 -1
- package/dist/modules/import-export/validation.d.ts.map +1 -1
- package/dist/modules/import-export/validation.js +22 -1
- package/dist/modules/import-export/validation.js.map +1 -1
- package/dist/modules/import-export/writes.d.ts.map +1 -1
- package/dist/modules/import-export/writes.js +37 -4
- package/dist/modules/import-export/writes.js.map +1 -1
- package/dist/modules/lists/index.d.ts +38 -0
- package/dist/modules/lists/index.d.ts.map +1 -1
- package/dist/modules/lists/index.js +27 -0
- package/dist/modules/lists/index.js.map +1 -1
- package/dist/modules/lists/repository.d.ts +26 -0
- package/dist/modules/lists/repository.d.ts.map +1 -1
- package/dist/modules/lists/repository.js +32 -0
- package/dist/modules/lists/repository.js.map +1 -1
- package/dist/modules/lists/routes.d.ts +4 -0
- package/dist/modules/lists/routes.d.ts.map +1 -1
- package/dist/modules/lists/routes.js +8 -0
- package/dist/modules/lists/routes.js.map +1 -1
- package/dist/modules/lists/schema.d.ts +17 -0
- package/dist/modules/lists/schema.d.ts.map +1 -1
- package/dist/modules/lists/schema.js +10 -0
- package/dist/modules/lists/schema.js.map +1 -1
- package/dist/modules/lists/service.d.ts +14 -2
- package/dist/modules/lists/service.d.ts.map +1 -1
- package/dist/modules/lists/service.js +55 -7
- package/dist/modules/lists/service.js.map +1 -1
- package/dist/modules/mcp/index.d.ts.map +1 -1
- package/dist/modules/mcp/index.js +5 -0
- package/dist/modules/mcp/index.js.map +1 -1
- package/dist/modules/notes/routes.d.ts +1 -0
- package/dist/modules/notes/routes.d.ts.map +1 -1
- package/dist/modules/opportunities/index.d.ts.map +1 -1
- package/dist/modules/opportunities/index.js +3 -1
- package/dist/modules/opportunities/index.js.map +1 -1
- package/dist/modules/opportunities/repository.d.ts +2 -0
- package/dist/modules/opportunities/repository.d.ts.map +1 -1
- package/dist/modules/opportunities/repository.js +4 -0
- package/dist/modules/opportunities/repository.js.map +1 -1
- package/dist/modules/opportunities/routes.d.ts +16 -0
- package/dist/modules/opportunities/routes.d.ts.map +1 -1
- package/dist/modules/opportunities/routes.js +13 -0
- package/dist/modules/opportunities/routes.js.map +1 -1
- package/dist/modules/opportunities/schema.d.ts +20 -0
- package/dist/modules/opportunities/schema.d.ts.map +1 -1
- package/dist/modules/opportunities/schema.js +6 -1
- package/dist/modules/opportunities/schema.js.map +1 -1
- package/dist/modules/opportunities/service.d.ts +20 -3
- package/dist/modules/opportunities/service.d.ts.map +1 -1
- package/dist/modules/opportunities/service.js +99 -18
- package/dist/modules/opportunities/service.js.map +1 -1
- package/dist/modules/opportunities/tools.d.ts.map +1 -1
- package/dist/modules/opportunities/tools.js +2 -0
- package/dist/modules/opportunities/tools.js.map +1 -1
- package/dist/modules/partnerships/index.d.ts.map +1 -1
- package/dist/modules/partnerships/index.js +3 -1
- package/dist/modules/partnerships/index.js.map +1 -1
- package/dist/modules/partnerships/repository.d.ts +0 -17
- package/dist/modules/partnerships/repository.d.ts.map +1 -1
- package/dist/modules/partnerships/repository.js +6 -82
- package/dist/modules/partnerships/repository.js.map +1 -1
- package/dist/modules/partnerships/routes.d.ts +14 -0
- package/dist/modules/partnerships/routes.d.ts.map +1 -1
- package/dist/modules/partnerships/routes.js +7 -0
- package/dist/modules/partnerships/routes.js.map +1 -1
- package/dist/modules/partnerships/schema.d.ts +20 -41
- package/dist/modules/partnerships/schema.d.ts.map +1 -1
- package/dist/modules/partnerships/schema.js +6 -10
- package/dist/modules/partnerships/schema.js.map +1 -1
- package/dist/modules/partnerships/service.d.ts +11 -1
- package/dist/modules/partnerships/service.d.ts.map +1 -1
- package/dist/modules/partnerships/service.js +43 -20
- package/dist/modules/partnerships/service.js.map +1 -1
- package/dist/modules/people/consentHydration.d.ts +26 -0
- package/dist/modules/people/consentHydration.d.ts.map +1 -0
- package/dist/modules/people/consentHydration.js +100 -0
- package/dist/modules/people/consentHydration.js.map +1 -0
- package/dist/modules/people/index.d.ts.map +1 -1
- package/dist/modules/people/index.js +3 -1
- package/dist/modules/people/index.js.map +1 -1
- package/dist/modules/people/personConsentWrites.d.ts +40 -0
- package/dist/modules/people/personConsentWrites.d.ts.map +1 -0
- package/dist/modules/people/personConsentWrites.js +98 -0
- package/dist/modules/people/personConsentWrites.js.map +1 -0
- package/dist/modules/people/repository.d.ts.map +1 -1
- package/dist/modules/people/repository.js +5 -1
- package/dist/modules/people/repository.js.map +1 -1
- package/dist/modules/people/routes.d.ts +57 -4
- package/dist/modules/people/routes.d.ts.map +1 -1
- package/dist/modules/people/routes.js +104 -5
- package/dist/modules/people/routes.js.map +1 -1
- package/dist/modules/people/schema.d.ts +366 -1
- package/dist/modules/people/schema.d.ts.map +1 -1
- package/dist/modules/people/schema.js +92 -2
- package/dist/modules/people/schema.js.map +1 -1
- package/dist/modules/people/service.d.ts +30 -1
- package/dist/modules/people/service.d.ts.map +1 -1
- package/dist/modules/people/service.js +115 -38
- package/dist/modules/people/service.js.map +1 -1
- package/dist/modules/people/tools.d.ts.map +1 -1
- package/dist/modules/people/tools.js +4 -1
- package/dist/modules/people/tools.js.map +1 -1
- package/dist/modules/personLinks.d.ts +79 -0
- package/dist/modules/personLinks.d.ts.map +1 -0
- package/dist/modules/personLinks.js +155 -0
- package/dist/modules/personLinks.js.map +1 -0
- package/dist/modules/pipelines/routes.d.ts +1 -0
- package/dist/modules/pipelines/routes.d.ts.map +1 -1
- package/dist/modules/plans/routes.d.ts +1 -0
- package/dist/modules/plans/routes.d.ts.map +1 -1
- package/dist/modules/raises/index.d.ts.map +1 -1
- package/dist/modules/raises/index.js +3 -1
- package/dist/modules/raises/index.js.map +1 -1
- package/dist/modules/raises/repository.d.ts +0 -17
- package/dist/modules/raises/repository.d.ts.map +1 -1
- package/dist/modules/raises/repository.js +6 -77
- package/dist/modules/raises/repository.js.map +1 -1
- package/dist/modules/raises/routes.d.ts +14 -0
- package/dist/modules/raises/routes.d.ts.map +1 -1
- package/dist/modules/raises/routes.js +7 -0
- package/dist/modules/raises/routes.js.map +1 -1
- package/dist/modules/raises/schema.d.ts +20 -41
- package/dist/modules/raises/schema.d.ts.map +1 -1
- package/dist/modules/raises/schema.js +6 -10
- package/dist/modules/raises/schema.js.map +1 -1
- package/dist/modules/raises/service.d.ts +11 -1
- package/dist/modules/raises/service.d.ts.map +1 -1
- package/dist/modules/raises/service.js +41 -18
- package/dist/modules/raises/service.js.map +1 -1
- package/dist/modules/recordTargets.d.ts.map +1 -1
- package/dist/modules/recordTargets.js +2 -0
- package/dist/modules/recordTargets.js.map +1 -1
- package/dist/modules/references.d.ts +10 -0
- package/dist/modules/references.d.ts.map +1 -1
- package/dist/modules/references.js +27 -4
- package/dist/modules/references.js.map +1 -1
- package/dist/modules/sample-data/fixture.d.ts +21 -0
- package/dist/modules/sample-data/fixture.d.ts.map +1 -1
- package/dist/modules/sample-data/fixture.js +40 -0
- package/dist/modules/sample-data/fixture.js.map +1 -1
- package/dist/modules/sample-data/index.d.ts.map +1 -1
- package/dist/modules/sample-data/index.js +1 -0
- package/dist/modules/sample-data/index.js.map +1 -1
- package/dist/modules/sample-data/routes.d.ts.map +1 -1
- package/dist/modules/sample-data/routes.js +1 -0
- package/dist/modules/sample-data/routes.js.map +1 -1
- package/dist/modules/sample-data/service.d.ts +1 -0
- package/dist/modules/sample-data/service.d.ts.map +1 -1
- package/dist/modules/sample-data/service.js +77 -8
- package/dist/modules/sample-data/service.js.map +1 -1
- package/dist/modules/search/index.d.ts +1 -1
- package/dist/modules/search/index.d.ts.map +1 -1
- package/dist/modules/search/index.js +2 -1
- package/dist/modules/search/index.js.map +1 -1
- package/dist/modules/search/repository.d.ts +1 -0
- package/dist/modules/search/repository.d.ts.map +1 -1
- package/dist/modules/search/repository.js +23 -0
- package/dist/modules/search/repository.js.map +1 -1
- package/dist/modules/search/service.d.ts +3 -3
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +5 -4
- package/dist/modules/search/service.js.map +1 -1
- package/dist/modules/stagedRecords.d.ts +3 -3
- package/dist/modules/stagedRecords.d.ts.map +1 -1
- package/dist/modules/stagedRecords.js +9 -0
- package/dist/modules/stagedRecords.js.map +1 -1
- package/dist/modules/webhooks/payloads.d.ts.map +1 -1
- package/dist/modules/webhooks/payloads.js +15 -2
- package/dist/modules/webhooks/payloads.js.map +1 -1
- package/dist/modules/workspace/repository.d.ts +2 -0
- package/dist/modules/workspace/repository.d.ts.map +1 -1
- package/dist/modules/workspace/repository.js +7 -0
- package/dist/modules/workspace/repository.js.map +1 -1
- package/dist/modules/workspace/service.d.ts.map +1 -1
- package/dist/modules/workspace/service.js +10 -0
- package/dist/modules/workspace/service.js.map +1 -1
- package/dist/modules/workspace/starters.d.ts.map +1 -1
- package/dist/modules/workspace/starters.js +5 -0
- package/dist/modules/workspace/starters.js.map +1 -1
- package/dist/runtime/events.d.ts +1 -1
- package/dist/runtime/events.d.ts.map +1 -1
- package/dist/runtime/events.js +1 -0
- package/dist/runtime/events.js.map +1 -1
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/database.d.ts +5 -1
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +81 -10
- package/dist/testing/database.js.map +1 -1
- package/migrations/0024_third_king_cobra.sql +46 -0
- package/migrations/0025_worthless_chimera.sql +3 -0
- package/migrations/0026_late_aqueduct.sql +46 -0
- package/migrations/0027_brainy_silver_sable.sql +3 -0
- package/migrations/0028_freezing_scrambler.sql +19 -0
- package/migrations/0029_custom_fields.sql +26 -0
- package/migrations/0030_enquiries.sql +79 -0
- package/migrations/0031_consent.sql +45 -0
- package/migrations/0032_form_field_statement.sql +1 -0
- package/migrations/0033_form_field_consent_purpose_ids.sql +5 -0
- package/migrations/0034_drop_form_field_consent_purpose_id.sql +1 -0
- package/migrations/0035_form_field_consent_purpose_labels.sql +1 -0
- package/migrations/0036_form_field_notice_type.sql +2 -0
- package/migrations/meta/0024_snapshot.json +5516 -0
- package/migrations/meta/0025_snapshot.json +5336 -0
- package/migrations/meta/0026_snapshot.json +5710 -0
- package/migrations/meta/0027_snapshot.json +5716 -0
- package/migrations/meta/0028_snapshot.json +5740 -0
- package/migrations/meta/0029_snapshot.json +5935 -0
- package/migrations/meta/0030_snapshot.json +6245 -0
- package/migrations/meta/0031_snapshot.json +6572 -0
- package/migrations/meta/0032_snapshot.json +6578 -0
- package/migrations/meta/0033_snapshot.json +6572 -0
- package/migrations/meta/0034_snapshot.json +6566 -0
- package/migrations/meta/0035_snapshot.json +6573 -0
- package/migrations/meta/0036_snapshot.json +6573 -0
- package/migrations/meta/_journal.json +91 -0
- package/package.json +2 -2
- package/src/index.ts +6 -0
- package/src/lib/ids.ts +11 -0
- package/src/modules/activities/repository.ts +22 -23
- package/src/modules/activities/wording.ts +18 -0
- package/src/modules/agent-tasks/catalog.ts +63 -0
- package/src/modules/agent-tasks/resolve.ts +64 -24
- package/src/modules/attachedRecords.ts +29 -2
- package/src/modules/companies/index.ts +3 -1
- package/src/modules/companies/routes.ts +7 -0
- package/src/modules/companies/schema.ts +7 -1
- package/src/modules/companies/service.ts +48 -13
- package/src/modules/consent-purposes/capabilities.ts +12 -0
- package/src/modules/consent-purposes/events.ts +38 -0
- package/src/modules/consent-purposes/index.ts +48 -0
- package/src/modules/consent-purposes/repository.ts +163 -0
- package/src/modules/consent-purposes/routes.ts +122 -0
- package/src/modules/consent-purposes/schema.ts +46 -0
- package/src/modules/consent-purposes/service.ts +423 -0
- package/src/modules/consent-purposes/starters.ts +32 -0
- package/src/modules/consent-purposes/tools.ts +44 -0
- package/src/modules/core.ts +10 -0
- package/src/modules/custom-fields/capabilities.ts +17 -0
- package/src/modules/custom-fields/events.ts +51 -0
- package/src/modules/custom-fields/index.ts +61 -0
- package/src/modules/custom-fields/repository.ts +225 -0
- package/src/modules/custom-fields/routes.ts +155 -0
- package/src/modules/custom-fields/schema.ts +61 -0
- package/src/modules/custom-fields/service.ts +439 -0
- package/src/modules/custom-fields/tools.ts +53 -0
- package/src/modules/custom-fields/values.ts +352 -0
- package/src/modules/custom-fields/wire.ts +32 -0
- package/src/modules/dashboard/repository.ts +4 -3
- package/src/modules/deals/index.ts +5 -2
- package/src/modules/deals/repository.ts +6 -106
- package/src/modules/deals/routes.ts +7 -0
- package/src/modules/deals/schema.ts +7 -15
- package/src/modules/deals/service.ts +92 -23
- package/src/modules/decisions/repository.ts +2 -0
- package/src/modules/enquiries/events.ts +45 -0
- package/src/modules/enquiries/index.ts +50 -0
- package/src/modules/enquiries/repository.ts +164 -0
- package/src/modules/enquiries/routes.ts +172 -0
- package/src/modules/enquiries/schema.ts +59 -0
- package/src/modules/enquiries/service.ts +644 -0
- package/src/modules/enquiries/tools.ts +66 -0
- package/src/modules/eventCatalogs.ts +3 -0
- package/src/modules/forms/embed.ts +391 -44
- package/src/modules/forms/events.ts +30 -2
- package/src/modules/forms/fields.ts +102 -2
- package/src/modules/forms/index.ts +12 -1
- package/src/modules/forms/mapping.ts +183 -6
- package/src/modules/forms/publicRoutes.ts +43 -1
- package/src/modules/forms/repository.ts +162 -1
- package/src/modules/forms/routes.ts +212 -2
- package/src/modules/forms/schema.ts +166 -4
- package/src/modules/forms/service.ts +565 -41
- package/src/modules/forms/submission.ts +718 -44
- package/src/modules/import-export/drafts.ts +20 -1
- package/src/modules/import-export/exportRows.ts +21 -5
- package/src/modules/import-export/plan.ts +47 -2
- package/src/modules/import-export/presets.ts +10 -0
- package/src/modules/import-export/repository.ts +73 -8
- package/src/modules/import-export/routes.ts +4 -0
- package/src/modules/import-export/schema.ts +11 -0
- package/src/modules/import-export/service.ts +28 -2
- package/src/modules/import-export/streams.ts +3 -1
- package/src/modules/import-export/tools.ts +10 -0
- package/src/modules/import-export/validation.ts +28 -0
- package/src/modules/import-export/writes.ts +70 -5
- package/src/modules/lists/index.ts +71 -0
- package/src/modules/lists/repository.ts +64 -0
- package/src/modules/lists/routes.ts +8 -0
- package/src/modules/lists/schema.ts +10 -0
- package/src/modules/lists/service.ts +110 -9
- package/src/modules/mcp/index.ts +5 -0
- package/src/modules/opportunities/index.ts +3 -1
- package/src/modules/opportunities/repository.ts +11 -0
- package/src/modules/opportunities/routes.ts +13 -0
- package/src/modules/opportunities/schema.ts +7 -1
- package/src/modules/opportunities/service.ts +188 -22
- package/src/modules/opportunities/tools.ts +2 -0
- package/src/modules/partnerships/index.ts +3 -1
- package/src/modules/partnerships/repository.ts +6 -117
- package/src/modules/partnerships/routes.ts +7 -0
- package/src/modules/partnerships/schema.ts +7 -14
- package/src/modules/partnerships/service.ts +93 -22
- package/src/modules/people/consentHydration.ts +167 -0
- package/src/modules/people/index.ts +3 -1
- package/src/modules/people/personConsentWrites.ts +177 -0
- package/src/modules/people/repository.ts +5 -0
- package/src/modules/people/routes.ts +127 -18
- package/src/modules/people/schema.ts +109 -3
- package/src/modules/people/service.ts +186 -40
- package/src/modules/people/tools.ts +5 -1
- package/src/modules/personLinks.ts +278 -0
- package/src/modules/raises/index.ts +3 -1
- package/src/modules/raises/repository.ts +6 -107
- package/src/modules/raises/routes.ts +7 -0
- package/src/modules/raises/schema.ts +7 -14
- package/src/modules/raises/service.ts +91 -20
- package/src/modules/recordTargets.ts +2 -0
- package/src/modules/references.ts +43 -4
- package/src/modules/sample-data/fixture.ts +63 -0
- package/src/modules/sample-data/index.ts +1 -0
- package/src/modules/sample-data/routes.ts +1 -0
- package/src/modules/sample-data/service.ts +97 -8
- package/src/modules/search/index.ts +2 -1
- package/src/modules/search/repository.ts +33 -1
- package/src/modules/search/service.ts +7 -5
- package/src/modules/stagedRecords.ts +18 -3
- package/src/modules/webhooks/payloads.ts +20 -2
- package/src/modules/workspace/repository.ts +12 -0
- package/src/modules/workspace/service.ts +14 -0
- package/src/modules/workspace/starters.ts +5 -0
- package/src/runtime/events.ts +1 -0
- package/src/schema/index.ts +3 -0
- package/src/testing/database.ts +97 -11
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { FormSubmissionActionEntry, PipelineKind } from '@kelpie/schemas'
|
|
2
|
+
|
|
3
|
+
import { UNIQUE_VIOLATION, postgresErrorCode } from '../../lib/database.ts'
|
|
1
4
|
import type { Database } from '../../lib/database.ts'
|
|
2
5
|
import { AppError } from '../../lib/errors.ts'
|
|
3
6
|
import type { IdFactory } from '../../lib/ids.ts'
|
|
@@ -12,26 +15,40 @@ import * as companyRepository from '../companies/repository.ts'
|
|
|
12
15
|
import type { CompanyRecord } from '../companies/repository.ts'
|
|
13
16
|
import * as dealRepository from '../deals/repository.ts'
|
|
14
17
|
import '../deals/events.ts'
|
|
18
|
+
import * as enquiryRepository from '../enquiries/repository.ts'
|
|
19
|
+
import '../enquiries/events.ts'
|
|
20
|
+
import '../lists/events.ts'
|
|
21
|
+
import * as listsRepository from '../lists/repository.ts'
|
|
22
|
+
import * as opportunityRepository from '../opportunities/repository.ts'
|
|
23
|
+
import '../opportunities/events.ts'
|
|
24
|
+
import * as partnershipRepository from '../partnerships/repository.ts'
|
|
25
|
+
import '../partnerships/events.ts'
|
|
26
|
+
import * as consentPurposesRepository from '../consent-purposes/repository.ts'
|
|
15
27
|
import '../people/events.ts'
|
|
28
|
+
import { upsertPersonConsent } from '../people/personConsentWrites.ts'
|
|
16
29
|
import * as peopleRepository from '../people/repository.ts'
|
|
17
30
|
import type { PersonRecord } from '../people/repository.ts'
|
|
31
|
+
import * as personLinks from '../personLinks.ts'
|
|
18
32
|
import * as pipelineRepository from '../pipelines/repository.ts'
|
|
19
33
|
import '../positions/events.ts'
|
|
20
34
|
import * as positionRepository from '../positions/repository.ts'
|
|
35
|
+
import { targetExists } from '../recordTargets.ts'
|
|
21
36
|
import * as workspaceRepository from '../workspace/repository.ts'
|
|
22
37
|
import './events.ts'
|
|
23
38
|
import {
|
|
24
39
|
DEAL_CLOSE_HORIZON_DAYS,
|
|
25
40
|
companyNameFrom,
|
|
26
41
|
describeAnswers,
|
|
27
|
-
|
|
42
|
+
expandNameTemplate,
|
|
28
43
|
expectedCloseFrom,
|
|
29
44
|
fillBlank,
|
|
30
45
|
findAnswerProblems,
|
|
31
46
|
mapAnswers,
|
|
47
|
+
mergeTags,
|
|
48
|
+
readConsentGrants,
|
|
32
49
|
readIntent,
|
|
33
50
|
} from './mapping.ts'
|
|
34
|
-
import type { Answers, SubmitIntent } from './mapping.ts'
|
|
51
|
+
import type { Answers, ConsentGrant, SubmitIntent } from './mapping.ts'
|
|
35
52
|
import * as repository from './repository.ts'
|
|
36
53
|
import type { FormFieldRecord, FormRecord } from './repository.ts'
|
|
37
54
|
|
|
@@ -105,9 +122,19 @@ export interface SubmitOutcome {
|
|
|
105
122
|
readonly companyId: string | null
|
|
106
123
|
readonly positionId: string | null
|
|
107
124
|
readonly dealId: string | null
|
|
125
|
+
readonly opportunityId: string | null
|
|
126
|
+
readonly partnershipId: string | null
|
|
127
|
+
readonly enquiryId: string | null
|
|
108
128
|
readonly submittedAt: Date
|
|
109
129
|
/** Echoed so an embed can render it without a second request. */
|
|
110
130
|
readonly thankYouMessage: string
|
|
131
|
+
/**
|
|
132
|
+
* Per-action outcome from the post-submit runner, in the order attempted.
|
|
133
|
+
* Empty for a form with no post-actions configured. Never crosses the public
|
|
134
|
+
* wire (see `publicRoutes.ts`); the authenticated Submissions read carries
|
|
135
|
+
* it in full.
|
|
136
|
+
*/
|
|
137
|
+
readonly actionLog: readonly FormSubmissionActionEntry[]
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
export interface FormSubmitService {
|
|
@@ -115,6 +142,26 @@ export interface FormSubmitService {
|
|
|
115
142
|
submit(publicKey: string, answers: Answers): Promise<SubmitOutcome>
|
|
116
143
|
}
|
|
117
144
|
|
|
145
|
+
/**
|
|
146
|
+
* One post-submit action's outcome, as its `work` returns it. `skipped`
|
|
147
|
+
* means the action was configured but its precondition was absent (a
|
|
148
|
+
* company-tag merge on a submit that resolved no company); the runner logs
|
|
149
|
+
* it and moves on. `ok` means the action ran; the runner forwards every
|
|
150
|
+
* event the action emitted.
|
|
151
|
+
*/
|
|
152
|
+
interface ActionResult<T> {
|
|
153
|
+
readonly status: 'ok' | 'skipped'
|
|
154
|
+
readonly detail: string
|
|
155
|
+
readonly value?: T
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** One event pending forwarding after a savepoint's `ok` return. */
|
|
159
|
+
interface PendingEvent {
|
|
160
|
+
readonly name: Parameters<BufferedEvents['emit']>[0]
|
|
161
|
+
readonly target: Parameters<BufferedEvents['emit']>[1]
|
|
162
|
+
readonly data: unknown
|
|
163
|
+
}
|
|
164
|
+
|
|
118
165
|
/** A record, and whether this submit is what put it there. */
|
|
119
166
|
interface Upserted<TRecord extends { readonly id: string }> {
|
|
120
167
|
readonly record: TRecord
|
|
@@ -133,29 +180,26 @@ interface TouchedRecords {
|
|
|
133
180
|
readonly person: Upserted<PersonRecord>
|
|
134
181
|
readonly company: Upserted<CompanyRecord> | undefined
|
|
135
182
|
readonly position: Upserted<PositionLink> | undefined
|
|
136
|
-
readonly dealId: string | null
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
/**
|
|
140
|
-
* One `record.created` per record this submit invented,
|
|
141
|
-
* `record.updated` per record it filled a blank on.
|
|
186
|
+
* One `record.created` per record this submit invented (person, company,
|
|
187
|
+
* position), and one `record.updated` per record it filled a blank on. The
|
|
188
|
+
* pipeline creates (deal, opportunity, partnership) publish their own
|
|
189
|
+
* `*.created` events from inside their action's shim so a rolled-back
|
|
190
|
+
* savepoint drops the event alongside the row.
|
|
142
191
|
*
|
|
143
|
-
* A submit that matched an existing person and changed nothing about them
|
|
144
|
-
* neither: a consumer mirroring the CRM has nothing to mirror, and
|
|
192
|
+
* A submit that matched an existing person and changed nothing about them
|
|
193
|
+
* emits neither: a consumer mirroring the CRM has nothing to mirror, and
|
|
145
194
|
* `form.submitted` already says the submission happened.
|
|
146
|
-
*
|
|
147
|
-
* The Deal has no `Upserted` because it is only ever created, and its people
|
|
148
|
-
* link rides along with it rather than as a separate `record.updated`, the same
|
|
149
|
-
* way the deals service treats a create.
|
|
150
195
|
*/
|
|
151
196
|
function emitRecordEvents(
|
|
152
197
|
events: BufferedEvents,
|
|
153
198
|
workspaceId: string,
|
|
154
199
|
touched: TouchedRecords,
|
|
155
200
|
): void {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
201
|
+
emitUpsertEvent(events, 'person', touched.person)
|
|
202
|
+
|
|
159
203
|
if (touched.company !== undefined) {
|
|
160
204
|
emitUpsertEvent(events, 'company', touched.company)
|
|
161
205
|
}
|
|
@@ -163,9 +207,6 @@ function emitRecordEvents(
|
|
|
163
207
|
emitUpsertEvent(events, 'position', touched.position)
|
|
164
208
|
}
|
|
165
209
|
|
|
166
|
-
if (touched.dealId !== null) {
|
|
167
|
-
events.emit('deals.deal.created', { type: 'deal', id: touched.dealId }, {})
|
|
168
|
-
}
|
|
169
210
|
// workspaceId is used by the transaction scope's envelope stamping; the
|
|
170
211
|
// parameter stays on the signature so future call sites keep the same shape.
|
|
171
212
|
void workspaceId
|
|
@@ -253,6 +294,8 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
253
294
|
id: dependencies.createId('person'),
|
|
254
295
|
workspaceId,
|
|
255
296
|
name: intent.personName,
|
|
297
|
+
firstName: intent.personFirstName ?? null,
|
|
298
|
+
lastName: intent.personLastName ?? null,
|
|
256
299
|
email: intent.email,
|
|
257
300
|
lastContactedAt: now,
|
|
258
301
|
...NEW_PERSON_DEFAULTS,
|
|
@@ -261,9 +304,19 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
261
304
|
return { record: created, created: true, filled: [] }
|
|
262
305
|
}
|
|
263
306
|
|
|
264
|
-
|
|
307
|
+
// Each part fills its own blank. A stored first name is what the team has
|
|
308
|
+
// learned and an inbound one is what a visitor typed, so the rule that keeps
|
|
309
|
+
// an inbound "Alex" off a stored "Alex Rivera" covers these unchanged.
|
|
310
|
+
const filledName = fillBlank(existing.name, intent.personName)
|
|
311
|
+
const filledFirstName = fillBlank(existing.firstName, intent.personFirstName)
|
|
312
|
+
const filledLastName = fillBlank(existing.lastName, intent.personLastName)
|
|
313
|
+
const changes = {
|
|
314
|
+
...(filledName === undefined ? {} : { name: filledName }),
|
|
315
|
+
...(filledFirstName === undefined ? {} : { firstName: filledFirstName }),
|
|
316
|
+
...(filledLastName === undefined ? {} : { lastName: filledLastName }),
|
|
317
|
+
}
|
|
265
318
|
const updated = await peopleRepository.updatePerson(tx, workspaceId, existing.id, {
|
|
266
|
-
...
|
|
319
|
+
...changes,
|
|
267
320
|
lastContactedAt: now,
|
|
268
321
|
updatedAt: now,
|
|
269
322
|
})
|
|
@@ -272,7 +325,7 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
272
325
|
throw new Error(`Person ${existing.id} disappeared during a form submit`)
|
|
273
326
|
}
|
|
274
327
|
|
|
275
|
-
return { record: updated, created: false, filled:
|
|
328
|
+
return { record: updated, created: false, filled: Object.keys(changes) }
|
|
276
329
|
}
|
|
277
330
|
|
|
278
331
|
/**
|
|
@@ -374,28 +427,77 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
374
427
|
return { record: created, created: true, filled: [] }
|
|
375
428
|
}
|
|
376
429
|
|
|
377
|
-
/**
|
|
430
|
+
/**
|
|
431
|
+
* The stage a form's created record opens in: the one the form names, or
|
|
432
|
+
* the pipeline's first open stage. Shared by the three create-triggers.
|
|
433
|
+
*/
|
|
378
434
|
async function openingStageId(
|
|
379
435
|
tx: Transaction,
|
|
380
436
|
workspaceId: string,
|
|
381
|
-
|
|
437
|
+
kind: PipelineKind,
|
|
438
|
+
configured: string | null,
|
|
382
439
|
): Promise<string> {
|
|
383
|
-
if (
|
|
384
|
-
return
|
|
440
|
+
if (configured !== null) {
|
|
441
|
+
return configured
|
|
385
442
|
}
|
|
386
443
|
|
|
387
|
-
const stages = await pipelineRepository.listStagesOfKind(tx, workspaceId,
|
|
444
|
+
const stages = await pipelineRepository.listStagesOfKind(tx, workspaceId, kind)
|
|
388
445
|
const first = stages.find((stage) => stage.open) ?? stages[0]
|
|
389
446
|
|
|
390
447
|
if (first === undefined) {
|
|
391
448
|
// Unreachable through the API: a workspace seeds its stages at creation
|
|
392
449
|
// and the last stage of a pipeline cannot be removed.
|
|
393
|
-
throw AppError.conflict(
|
|
450
|
+
throw AppError.conflict(`This workspace has no ${kind} stages`)
|
|
394
451
|
}
|
|
395
452
|
|
|
396
453
|
return first.id
|
|
397
454
|
}
|
|
398
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Runs `work` under a nested `tx.transaction()` — a SAVEPOINT in
|
|
458
|
+
* postgres-js — and captures every event the action emits into a local
|
|
459
|
+
* buffer. On success (`ok` or `skipped`) the events are forwarded to the
|
|
460
|
+
* real `BufferedEvents`; on throw the savepoint has already rolled back so
|
|
461
|
+
* the events are discarded and a `{status: 'error'}` entry lands on the log.
|
|
462
|
+
* Either way the outer transaction stays alive: the next action runs, and
|
|
463
|
+
* the visitor's 201 survives.
|
|
464
|
+
*/
|
|
465
|
+
async function runAction<T>(
|
|
466
|
+
tx: Transaction,
|
|
467
|
+
events: BufferedEvents,
|
|
468
|
+
log: FormSubmissionActionEntry[],
|
|
469
|
+
action: string,
|
|
470
|
+
work: (inner: Transaction, emit: BufferedEvents['emit']) => Promise<ActionResult<T>>,
|
|
471
|
+
): Promise<T | undefined> {
|
|
472
|
+
const pending: PendingEvent[] = []
|
|
473
|
+
const emit: BufferedEvents['emit'] = (name, target, data) => {
|
|
474
|
+
pending.push({ name, target, data })
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const result = await tx.transaction(async (inner) => work(inner, emit))
|
|
479
|
+
log.push({ action, status: result.status, detail: result.detail })
|
|
480
|
+
|
|
481
|
+
for (const event of pending) {
|
|
482
|
+
// The typed emit rejects an unrelated (name, data) pair; the shim
|
|
483
|
+
// captured them as one call so the pair is already validated.
|
|
484
|
+
events.emit(event.name, event.target, event.data as never)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return result.value
|
|
488
|
+
} catch (error: unknown) {
|
|
489
|
+
const detail =
|
|
490
|
+
error instanceof AppError
|
|
491
|
+
? error.message
|
|
492
|
+
: error instanceof Error
|
|
493
|
+
? error.message
|
|
494
|
+
: 'unknown error'
|
|
495
|
+
log.push({ action, status: 'error', detail })
|
|
496
|
+
|
|
497
|
+
return undefined
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
399
501
|
/**
|
|
400
502
|
* Creates the Deal, when the form makes them and a company was resolved.
|
|
401
503
|
*
|
|
@@ -405,6 +507,7 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
405
507
|
*/
|
|
406
508
|
async function createDeal(
|
|
407
509
|
tx: Transaction,
|
|
510
|
+
emit: BufferedEvents['emit'],
|
|
408
511
|
workspaceId: string,
|
|
409
512
|
form: FormRecord,
|
|
410
513
|
intent: SubmitIntent,
|
|
@@ -419,19 +522,212 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
419
522
|
workspaceId,
|
|
420
523
|
name:
|
|
421
524
|
intent.dealName ??
|
|
422
|
-
|
|
525
|
+
expandNameTemplate(form.dealNameTemplate ?? DEFAULT_DEAL_NAME_TEMPLATE, {
|
|
423
526
|
companyName: company.name,
|
|
424
527
|
personName: intent.personName,
|
|
425
528
|
}),
|
|
426
529
|
companyId: company.id,
|
|
427
|
-
stageId: await openingStageId(tx, workspaceId, form),
|
|
530
|
+
stageId: await openingStageId(tx, workspaceId, 'deal', form.dealStageId),
|
|
428
531
|
valueCents: 0,
|
|
429
532
|
currency: null,
|
|
430
533
|
ownerId: owner?.id ?? null,
|
|
431
534
|
expectedClose: expectedCloseFrom(dependencies.now(), DEAL_CLOSE_HORIZON_DAYS),
|
|
432
535
|
})
|
|
433
536
|
|
|
434
|
-
await
|
|
537
|
+
await personLinks.linkPeople(
|
|
538
|
+
tx,
|
|
539
|
+
dependencies.createId,
|
|
540
|
+
workspaceId,
|
|
541
|
+
{ targetType: 'deal', targetId: id },
|
|
542
|
+
[personId],
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
546
|
+
targetType: 'deal',
|
|
547
|
+
targetId: id,
|
|
548
|
+
kind: 'created',
|
|
549
|
+
...describeCreationVia('Deal', form.name),
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
emit('deals.deal.created', { type: 'deal', id }, {})
|
|
553
|
+
|
|
554
|
+
return id
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Creates the Opportunity, when the form makes them.
|
|
559
|
+
*
|
|
560
|
+
* `opportunities.company_id` is nullable, so a submit that resolved no
|
|
561
|
+
* company still creates the opportunity, with the company link null. The
|
|
562
|
+
* kind is required at form-write time (see `service.ts`), so it is always
|
|
563
|
+
* present here.
|
|
564
|
+
*/
|
|
565
|
+
async function createOpportunity(
|
|
566
|
+
tx: Transaction,
|
|
567
|
+
emit: BufferedEvents['emit'],
|
|
568
|
+
workspaceId: string,
|
|
569
|
+
form: FormRecord,
|
|
570
|
+
intent: SubmitIntent,
|
|
571
|
+
company: CompanyRecord | undefined,
|
|
572
|
+
personId: string,
|
|
573
|
+
): Promise<string> {
|
|
574
|
+
const id = dependencies.createId('opportunity')
|
|
575
|
+
const owner =
|
|
576
|
+
form.opportunityOwnerId === null
|
|
577
|
+
? await workspaceRepository.findDefaultMember(tx, workspaceId)
|
|
578
|
+
: { id: form.opportunityOwnerId }
|
|
579
|
+
|
|
580
|
+
await opportunityRepository.insertOpportunity(tx, {
|
|
581
|
+
id,
|
|
582
|
+
workspaceId,
|
|
583
|
+
name:
|
|
584
|
+
intent.opportunityName ??
|
|
585
|
+
expandNameTemplate(form.opportunityNameTemplate ?? DEFAULT_DEAL_NAME_TEMPLATE, {
|
|
586
|
+
companyName: company?.name ?? '',
|
|
587
|
+
personName: intent.personName,
|
|
588
|
+
}),
|
|
589
|
+
// requireKindWhenCreating refuses an empty kind at write; the ?? is a
|
|
590
|
+
// narrowing convenience, not a runtime fallback.
|
|
591
|
+
kind: form.opportunityKind ?? '',
|
|
592
|
+
stageId: await openingStageId(tx, workspaceId, 'opportunity', form.opportunityStageId),
|
|
593
|
+
companyId: company?.id ?? null,
|
|
594
|
+
ownerId: owner?.id ?? null,
|
|
595
|
+
expectedClose: expectedCloseFrom(dependencies.now(), DEAL_CLOSE_HORIZON_DAYS),
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
await personLinks.linkPeople(
|
|
599
|
+
tx,
|
|
600
|
+
dependencies.createId,
|
|
601
|
+
workspaceId,
|
|
602
|
+
{ targetType: 'opportunity', targetId: id },
|
|
603
|
+
[personId],
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
607
|
+
targetType: 'opportunity',
|
|
608
|
+
targetId: id,
|
|
609
|
+
kind: 'created',
|
|
610
|
+
...describeCreationVia('Opportunity', form.name),
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
emit('opportunities.opportunity.created', { type: 'opportunity', id }, {})
|
|
614
|
+
|
|
615
|
+
return id
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Creates the Partnership, when the form makes them and a company was
|
|
620
|
+
* resolved. `partnerships.company_id` is `NOT NULL`, matching Deal.
|
|
621
|
+
*/
|
|
622
|
+
async function createPartnership(
|
|
623
|
+
tx: Transaction,
|
|
624
|
+
emit: BufferedEvents['emit'],
|
|
625
|
+
workspaceId: string,
|
|
626
|
+
form: FormRecord,
|
|
627
|
+
intent: SubmitIntent,
|
|
628
|
+
company: CompanyRecord,
|
|
629
|
+
personId: string,
|
|
630
|
+
): Promise<string> {
|
|
631
|
+
const id = dependencies.createId('partnership')
|
|
632
|
+
const owner =
|
|
633
|
+
form.partnershipOwnerId === null
|
|
634
|
+
? await workspaceRepository.findDefaultMember(tx, workspaceId)
|
|
635
|
+
: { id: form.partnershipOwnerId }
|
|
636
|
+
const nextTouchpoint = expectedCloseFrom(dependencies.now(), DEAL_CLOSE_HORIZON_DAYS)
|
|
637
|
+
|
|
638
|
+
await partnershipRepository.insertPartnership(tx, {
|
|
639
|
+
id,
|
|
640
|
+
workspaceId,
|
|
641
|
+
name:
|
|
642
|
+
intent.partnershipName ??
|
|
643
|
+
expandNameTemplate(form.partnershipNameTemplate ?? DEFAULT_DEAL_NAME_TEMPLATE, {
|
|
644
|
+
companyName: company.name,
|
|
645
|
+
personName: intent.personName,
|
|
646
|
+
}),
|
|
647
|
+
companyId: company.id,
|
|
648
|
+
stageId: await openingStageId(tx, workspaceId, 'partnership', form.partnershipStageId),
|
|
649
|
+
kind: form.partnershipKind ?? '',
|
|
650
|
+
nextTouchpoint,
|
|
651
|
+
ownerId: owner?.id ?? null,
|
|
652
|
+
goals: '',
|
|
653
|
+
successLooksLike: '',
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
await personLinks.linkPeople(
|
|
657
|
+
tx,
|
|
658
|
+
dependencies.createId,
|
|
659
|
+
workspaceId,
|
|
660
|
+
{ targetType: 'partnership', targetId: id },
|
|
661
|
+
[personId],
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
665
|
+
targetType: 'partnership',
|
|
666
|
+
targetId: id,
|
|
667
|
+
kind: 'created',
|
|
668
|
+
...describeCreationVia('Partnership', form.name),
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
emit('partnerships.partnership.created', { type: 'partnership', id }, {})
|
|
672
|
+
|
|
673
|
+
return id
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Creates the Enquiry, when the form makes them.
|
|
678
|
+
*
|
|
679
|
+
* `enquiries.company_id` is nullable, so a submit that resolved no company
|
|
680
|
+
* still creates the enquiry. Enquiries have no `kind`; the form's optional
|
|
681
|
+
* `enquirySource` is written to the enquiry's `source` column, or empty when
|
|
682
|
+
* unset — an unclassified source reads as unclassified.
|
|
683
|
+
*/
|
|
684
|
+
async function createEnquiry(
|
|
685
|
+
tx: Transaction,
|
|
686
|
+
emit: BufferedEvents['emit'],
|
|
687
|
+
workspaceId: string,
|
|
688
|
+
form: FormRecord,
|
|
689
|
+
intent: SubmitIntent,
|
|
690
|
+
company: CompanyRecord | undefined,
|
|
691
|
+
personId: string,
|
|
692
|
+
): Promise<string> {
|
|
693
|
+
const id = dependencies.createId('enquiry')
|
|
694
|
+
const owner =
|
|
695
|
+
form.enquiryOwnerId === null
|
|
696
|
+
? await workspaceRepository.findDefaultMember(tx, workspaceId)
|
|
697
|
+
: { id: form.enquiryOwnerId }
|
|
698
|
+
|
|
699
|
+
await enquiryRepository.insertEnquiry(tx, {
|
|
700
|
+
id,
|
|
701
|
+
workspaceId,
|
|
702
|
+
name:
|
|
703
|
+
intent.enquiryName ??
|
|
704
|
+
expandNameTemplate(form.enquiryNameTemplate ?? DEFAULT_DEAL_NAME_TEMPLATE, {
|
|
705
|
+
companyName: company?.name ?? '',
|
|
706
|
+
personName: intent.personName,
|
|
707
|
+
}),
|
|
708
|
+
source: form.enquirySource ?? '',
|
|
709
|
+
stageId: await openingStageId(tx, workspaceId, 'enquiry', form.enquiryStageId),
|
|
710
|
+
companyId: company?.id ?? null,
|
|
711
|
+
ownerId: owner?.id ?? null,
|
|
712
|
+
convertedDealId: null,
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
await personLinks.linkPeople(
|
|
716
|
+
tx,
|
|
717
|
+
dependencies.createId,
|
|
718
|
+
workspaceId,
|
|
719
|
+
{ targetType: 'enquiry', targetId: id },
|
|
720
|
+
[personId],
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
724
|
+
targetType: 'enquiry',
|
|
725
|
+
targetId: id,
|
|
726
|
+
kind: 'created',
|
|
727
|
+
...describeCreationVia('Enquiry', form.name),
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
emit('enquiries.enquiry.created', { type: 'enquiry', id }, {})
|
|
435
731
|
|
|
436
732
|
return id
|
|
437
733
|
}
|
|
@@ -481,14 +777,86 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
481
777
|
return intent
|
|
482
778
|
}
|
|
483
779
|
|
|
780
|
+
/**
|
|
781
|
+
* The purposes any grants name, keyed by id, so the submit transaction
|
|
782
|
+
* knows the slug and label without a per-grant round trip.
|
|
783
|
+
*/
|
|
784
|
+
async function loadPurposesForGrants(
|
|
785
|
+
db: Database,
|
|
786
|
+
workspaceId: string,
|
|
787
|
+
grants: readonly ConsentGrant[],
|
|
788
|
+
): Promise<ReadonlyMap<string, { readonly slug: string; readonly label: string }>> {
|
|
789
|
+
if (grants.length === 0) return new Map()
|
|
790
|
+
const ids = Array.from(new Set(grants.map((grant) => grant.purposeId)))
|
|
791
|
+
const rows = await consentPurposesRepository.listPurposesByIds(db, workspaceId, ids)
|
|
792
|
+
return new Map(rows.map((row) => [row.id, { slug: row.slug, label: row.label }]))
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Writes each ticked grant as a `person_consents` row and appends an
|
|
797
|
+
* Activity to the person carrying the verbatim statement. A grant whose
|
|
798
|
+
* purpose has since been removed is silently skipped — the form's own
|
|
799
|
+
* consent field would have been left dangling anyway, and the submit
|
|
800
|
+
* cannot know that from the answer alone.
|
|
801
|
+
*/
|
|
802
|
+
async function applyFormConsentGrants(
|
|
803
|
+
tx: Transaction,
|
|
804
|
+
workspaceId: string,
|
|
805
|
+
formId: string,
|
|
806
|
+
formName: string,
|
|
807
|
+
personId: string,
|
|
808
|
+
grants: readonly ConsentGrant[],
|
|
809
|
+
purposes: ReadonlyMap<string, { readonly slug: string; readonly label: string }>,
|
|
810
|
+
deps: SubmissionDependencies,
|
|
811
|
+
): Promise<void> {
|
|
812
|
+
if (grants.length === 0) return
|
|
813
|
+
const now = deps.now()
|
|
814
|
+
const source = `form:${formId}`
|
|
815
|
+
for (const grant of grants) {
|
|
816
|
+
const purpose = purposes.get(grant.purposeId)
|
|
817
|
+
if (purpose === undefined) continue
|
|
818
|
+
await upsertPersonConsent(
|
|
819
|
+
tx,
|
|
820
|
+
workspaceId,
|
|
821
|
+
personId,
|
|
822
|
+
grant.purposeId,
|
|
823
|
+
purpose.slug,
|
|
824
|
+
purpose.label,
|
|
825
|
+
'granted',
|
|
826
|
+
source,
|
|
827
|
+
now,
|
|
828
|
+
)
|
|
829
|
+
// The exact text the visitor ticked — the field's per-purpose override
|
|
830
|
+
// when set, else the workspace purpose's label. Recorded as the
|
|
831
|
+
// Activity detail so the timeline shows what they actually saw.
|
|
832
|
+
const ticked = grant.customLabel.length > 0 ? grant.customLabel : purpose.label
|
|
833
|
+
const detail = grant.statement.length > 0 ? `${grant.statement} — ${ticked}` : ticked
|
|
834
|
+
await deps.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
835
|
+
targetType: 'person',
|
|
836
|
+
targetId: personId,
|
|
837
|
+
kind: 'updated',
|
|
838
|
+
action: `granted ${purpose.label} consent via ${formName}`,
|
|
839
|
+
detail,
|
|
840
|
+
})
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
484
844
|
return {
|
|
485
845
|
async submit(publicKey, answers) {
|
|
486
846
|
const form = await requireOpenForm(publicKey)
|
|
487
847
|
const { workspaceId } = form
|
|
488
848
|
const fields = await repository.listFields(dependencies.db, form.id)
|
|
489
849
|
const intent = readAnswers(fields, answers)
|
|
850
|
+
const consentGrants = readConsentGrants(fields, answers)
|
|
851
|
+
const [formListRows, attachTargets, consentPurposesForGrants] = await Promise.all([
|
|
852
|
+
repository.listFormLists(dependencies.db, form.id),
|
|
853
|
+
repository.listAttachTargets(dependencies.db, form.id),
|
|
854
|
+
loadPurposesForGrants(dependencies.db, workspaceId, consentGrants),
|
|
855
|
+
])
|
|
490
856
|
|
|
491
857
|
return dependencies.transaction(async ({ tx, events }) => {
|
|
858
|
+
// Core capture: atomic. A failure here fails the submit; every
|
|
859
|
+
// post-action below runs under its own savepoint and logs.
|
|
492
860
|
const person = await upsertPerson(tx, workspaceId, intent)
|
|
493
861
|
const company = await upsertCompany(tx, workspaceId, intent)
|
|
494
862
|
const position =
|
|
@@ -501,10 +869,310 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
501
869
|
company.record.id,
|
|
502
870
|
intent.positionTitle,
|
|
503
871
|
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
872
|
+
|
|
873
|
+
// Consent grants (from ticked `consent` fields) are core capture, not
|
|
874
|
+
// post-actions: they commit with the person, and an Activity records
|
|
875
|
+
// the exact statement text as it was shown to the visitor.
|
|
876
|
+
await applyFormConsentGrants(
|
|
877
|
+
tx,
|
|
878
|
+
workspaceId,
|
|
879
|
+
form.id,
|
|
880
|
+
form.name,
|
|
881
|
+
person.record.id,
|
|
882
|
+
consentGrants,
|
|
883
|
+
consentPurposesForGrants,
|
|
884
|
+
dependencies,
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
const actionLog: FormSubmissionActionEntry[] = []
|
|
888
|
+
|
|
889
|
+
// --- Create triggers ---
|
|
890
|
+
|
|
891
|
+
const dealId = form.createDeal
|
|
892
|
+
? ((await runAction<string>(tx, events, actionLog, 'create_deal', async (inner, emit) => {
|
|
893
|
+
if (company === undefined) {
|
|
894
|
+
return { status: 'skipped', detail: 'no company resolved' }
|
|
895
|
+
}
|
|
896
|
+
const id = await createDeal(
|
|
897
|
+
inner,
|
|
898
|
+
emit,
|
|
899
|
+
workspaceId,
|
|
900
|
+
form,
|
|
901
|
+
intent,
|
|
902
|
+
company.record,
|
|
903
|
+
person.record.id,
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
return { status: 'ok', detail: id, value: id }
|
|
907
|
+
})) ?? null)
|
|
908
|
+
: null
|
|
909
|
+
|
|
910
|
+
const opportunityId = form.createOpportunity
|
|
911
|
+
? ((await runAction<string>(
|
|
912
|
+
tx,
|
|
913
|
+
events,
|
|
914
|
+
actionLog,
|
|
915
|
+
'create_opportunity',
|
|
916
|
+
async (inner, emit) => {
|
|
917
|
+
const id = await createOpportunity(
|
|
918
|
+
inner,
|
|
919
|
+
emit,
|
|
920
|
+
workspaceId,
|
|
921
|
+
form,
|
|
922
|
+
intent,
|
|
923
|
+
company?.record,
|
|
924
|
+
person.record.id,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
return { status: 'ok', detail: id, value: id }
|
|
928
|
+
},
|
|
929
|
+
)) ?? null)
|
|
930
|
+
: null
|
|
931
|
+
|
|
932
|
+
const partnershipId = form.createPartnership
|
|
933
|
+
? ((await runAction<string>(
|
|
934
|
+
tx,
|
|
935
|
+
events,
|
|
936
|
+
actionLog,
|
|
937
|
+
'create_partnership',
|
|
938
|
+
async (inner, emit) => {
|
|
939
|
+
if (company === undefined) {
|
|
940
|
+
return { status: 'skipped', detail: 'no company resolved' }
|
|
941
|
+
}
|
|
942
|
+
const id = await createPartnership(
|
|
943
|
+
inner,
|
|
944
|
+
emit,
|
|
945
|
+
workspaceId,
|
|
946
|
+
form,
|
|
947
|
+
intent,
|
|
948
|
+
company.record,
|
|
949
|
+
person.record.id,
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
return { status: 'ok', detail: id, value: id }
|
|
953
|
+
},
|
|
954
|
+
)) ?? null)
|
|
955
|
+
: null
|
|
956
|
+
|
|
957
|
+
const enquiryId = form.createEnquiry
|
|
958
|
+
? ((await runAction<string>(
|
|
959
|
+
tx,
|
|
960
|
+
events,
|
|
961
|
+
actionLog,
|
|
962
|
+
'create_enquiry',
|
|
963
|
+
async (inner, emit) => {
|
|
964
|
+
const id = await createEnquiry(
|
|
965
|
+
inner,
|
|
966
|
+
emit,
|
|
967
|
+
workspaceId,
|
|
968
|
+
form,
|
|
969
|
+
intent,
|
|
970
|
+
company?.record,
|
|
971
|
+
person.record.id,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
return { status: 'ok', detail: id, value: id }
|
|
975
|
+
},
|
|
976
|
+
)) ?? null)
|
|
977
|
+
: null
|
|
978
|
+
|
|
979
|
+
// --- Tag merges ---
|
|
980
|
+
|
|
981
|
+
if (form.personTags.length > 0) {
|
|
982
|
+
await runAction<void>(tx, events, actionLog, 'tag_person', async (inner, emit) => {
|
|
983
|
+
const merged = mergeTags(person.record.tags, form.personTags)
|
|
984
|
+
|
|
985
|
+
if (!merged.changed) {
|
|
986
|
+
return { status: 'ok', detail: 'no new tags' }
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const now = dependencies.now()
|
|
990
|
+
await peopleRepository.updatePerson(inner, workspaceId, person.record.id, {
|
|
991
|
+
tags: [...merged.next],
|
|
992
|
+
updatedAt: now,
|
|
993
|
+
})
|
|
994
|
+
emit(
|
|
995
|
+
'people.person.updated',
|
|
996
|
+
{ type: 'person', id: person.record.id },
|
|
997
|
+
{ changed: ['tags'] },
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
return { status: 'ok', detail: `merged ${String(merged.next.length - person.record.tags.length)}` }
|
|
1001
|
+
})
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (form.companyTags.length > 0) {
|
|
1005
|
+
await runAction<void>(tx, events, actionLog, 'tag_company', async (inner, emit) => {
|
|
1006
|
+
if (company === undefined) {
|
|
1007
|
+
return { status: 'skipped', detail: 'no company resolved' }
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const merged = mergeTags(company.record.tags, form.companyTags)
|
|
1011
|
+
|
|
1012
|
+
if (!merged.changed) {
|
|
1013
|
+
return { status: 'ok', detail: 'no new tags' }
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const now = dependencies.now()
|
|
1017
|
+
await companyRepository.updateCompany(inner, workspaceId, company.record.id, {
|
|
1018
|
+
tags: [...merged.next],
|
|
1019
|
+
updatedAt: now,
|
|
1020
|
+
})
|
|
1021
|
+
emit(
|
|
1022
|
+
'companies.company.updated',
|
|
1023
|
+
{ type: 'company', id: company.record.id },
|
|
1024
|
+
{ changed: ['tags'] },
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
return { status: 'ok', detail: `merged ${String(merged.next.length - company.record.tags.length)}` }
|
|
1028
|
+
})
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// --- List memberships ---
|
|
1032
|
+
|
|
1033
|
+
// Every purpose named by a person list on this form, so consent
|
|
1034
|
+
// capture below needs no per-row round trip.
|
|
1035
|
+
const listConsentPurposeIds = Array.from(
|
|
1036
|
+
new Set(
|
|
1037
|
+
formListRows
|
|
1038
|
+
.map((row) => row.consentPurposeId)
|
|
1039
|
+
.filter((id): id is string => id !== null),
|
|
1040
|
+
),
|
|
1041
|
+
)
|
|
1042
|
+
const listConsentPurposes =
|
|
1043
|
+
listConsentPurposeIds.length === 0
|
|
1044
|
+
? new Map<string, { readonly slug: string; readonly label: string }>()
|
|
1045
|
+
: new Map(
|
|
1046
|
+
(
|
|
1047
|
+
await consentPurposesRepository.listPurposesByIds(
|
|
1048
|
+
tx,
|
|
1049
|
+
workspaceId,
|
|
1050
|
+
listConsentPurposeIds,
|
|
1051
|
+
)
|
|
1052
|
+
).map((row) => [row.id, { slug: row.slug, label: row.label }]),
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
for (const row of formListRows) {
|
|
1056
|
+
const inserted = await runAction<boolean>(
|
|
1057
|
+
tx,
|
|
1058
|
+
events,
|
|
1059
|
+
actionLog,
|
|
1060
|
+
`add_list:${row.listId}`,
|
|
1061
|
+
async (inner, emit) => {
|
|
1062
|
+
const targetId =
|
|
1063
|
+
row.targetType === 'person'
|
|
1064
|
+
? person.record.id
|
|
1065
|
+
: row.targetType === 'company' && company !== undefined
|
|
1066
|
+
? company.record.id
|
|
1067
|
+
: undefined
|
|
1068
|
+
|
|
1069
|
+
if (targetId === undefined) {
|
|
1070
|
+
return { status: 'skipped', detail: 'no company resolved', value: false }
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
try {
|
|
1074
|
+
await listsRepository.insertListMember(inner, {
|
|
1075
|
+
id: dependencies.createId('listMember'),
|
|
1076
|
+
workspaceId,
|
|
1077
|
+
listId: row.listId,
|
|
1078
|
+
targetType: row.targetType,
|
|
1079
|
+
targetId,
|
|
1080
|
+
addedAt: dependencies.now(),
|
|
1081
|
+
})
|
|
1082
|
+
} catch (error: unknown) {
|
|
1083
|
+
if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
|
|
1084
|
+
return { status: 'ok', detail: 'already a member', value: false }
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
throw error
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
emit(
|
|
1091
|
+
'lists.member.added',
|
|
1092
|
+
{ type: row.targetType, id: targetId },
|
|
1093
|
+
{ listId: row.listId },
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
return { status: 'ok', detail: targetId, value: true }
|
|
1097
|
+
},
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
// Consent capture rides with the add — only on person lists, only
|
|
1101
|
+
// when the list carries a purpose, and only when we actually added
|
|
1102
|
+
// (or the person was already on it, since a returning submitter
|
|
1103
|
+
// did the same thing again). A record already on the list has
|
|
1104
|
+
// already been consented for its purpose; the upsert reflects
|
|
1105
|
+
// that a fresh submit is fresh evidence.
|
|
1106
|
+
if (
|
|
1107
|
+
row.targetType === 'person' &&
|
|
1108
|
+
row.consentPurposeId !== null &&
|
|
1109
|
+
inserted !== null
|
|
1110
|
+
) {
|
|
1111
|
+
const purpose = listConsentPurposes.get(row.consentPurposeId)
|
|
1112
|
+
if (purpose !== undefined) {
|
|
1113
|
+
await runAction<void>(
|
|
1114
|
+
tx,
|
|
1115
|
+
events,
|
|
1116
|
+
actionLog,
|
|
1117
|
+
`consent:${row.listId}`,
|
|
1118
|
+
async (inner) => {
|
|
1119
|
+
await upsertPersonConsent(
|
|
1120
|
+
inner,
|
|
1121
|
+
workspaceId,
|
|
1122
|
+
person.record.id,
|
|
1123
|
+
row.consentPurposeId as string,
|
|
1124
|
+
purpose.slug,
|
|
1125
|
+
purpose.label,
|
|
1126
|
+
'granted',
|
|
1127
|
+
`list:${row.listId}`,
|
|
1128
|
+
dependencies.now(),
|
|
1129
|
+
)
|
|
1130
|
+
await dependencies.recordActivity(inner, workspaceId, FORM_ACTOR, {
|
|
1131
|
+
targetType: 'person',
|
|
1132
|
+
targetId: person.record.id,
|
|
1133
|
+
kind: 'updated',
|
|
1134
|
+
action: `granted ${purpose.label} consent via ${form.name}`,
|
|
1135
|
+
detail: `Added to list membership`,
|
|
1136
|
+
})
|
|
1137
|
+
return { status: 'ok', detail: purpose.slug }
|
|
1138
|
+
},
|
|
1139
|
+
)
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// --- Attach the submitter to pre-existing pipeline records ---
|
|
1145
|
+
|
|
1146
|
+
for (const target of attachTargets) {
|
|
1147
|
+
await runAction<void>(
|
|
1148
|
+
tx,
|
|
1149
|
+
events,
|
|
1150
|
+
actionLog,
|
|
1151
|
+
`attach:${target.targetType}:${target.targetId}`,
|
|
1152
|
+
async (inner) => {
|
|
1153
|
+
// Racing a target delete would fail the FK-less insert with the
|
|
1154
|
+
// same effect as an existence check: log an error, continue.
|
|
1155
|
+
if (!(await targetExists(inner, workspaceId, target.targetType, target.targetId))) {
|
|
1156
|
+
throw AppError.notFound(`${target.targetType} ${target.targetId} not found`)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const inserted = await personLinks.linkPersonIfAbsent(
|
|
1160
|
+
inner,
|
|
1161
|
+
dependencies.createId,
|
|
1162
|
+
workspaceId,
|
|
1163
|
+
target,
|
|
1164
|
+
person.record.id,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
return {
|
|
1168
|
+
status: 'ok',
|
|
1169
|
+
detail: inserted ? 'linked' : 'already linked',
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// --- Persist submission + core activity + record events ---
|
|
508
1176
|
|
|
509
1177
|
const submission = await repository.insertSubmission(tx, {
|
|
510
1178
|
id: dependencies.createId('formSubmission'),
|
|
@@ -515,7 +1183,11 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
515
1183
|
companyId: company?.record.id ?? null,
|
|
516
1184
|
positionId: position?.record.id ?? null,
|
|
517
1185
|
dealId,
|
|
1186
|
+
opportunityId,
|
|
1187
|
+
partnershipId,
|
|
1188
|
+
enquiryId,
|
|
518
1189
|
submittedAt: dependencies.now(),
|
|
1190
|
+
actionLog,
|
|
519
1191
|
})
|
|
520
1192
|
|
|
521
1193
|
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
@@ -525,20 +1197,18 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
525
1197
|
...describeFormSubmission(form.name, describeAnswers(fields, answers)),
|
|
526
1198
|
})
|
|
527
1199
|
|
|
528
|
-
|
|
529
|
-
await dependencies.recordActivity(tx, workspaceId, FORM_ACTOR, {
|
|
530
|
-
targetType: 'deal',
|
|
531
|
-
targetId: dealId,
|
|
532
|
-
kind: 'created',
|
|
533
|
-
...describeCreationVia('Deal', form.name),
|
|
534
|
-
})
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
emitRecordEvents(events, workspaceId, { person, company, position, dealId })
|
|
1200
|
+
emitRecordEvents(events, workspaceId, { person, company, position })
|
|
538
1201
|
events.emit(
|
|
539
1202
|
'forms.submission.submitted',
|
|
540
1203
|
{ type: 'submission', id: submission.id },
|
|
541
|
-
{
|
|
1204
|
+
{
|
|
1205
|
+
formId: form.id,
|
|
1206
|
+
submissionId: submission.id,
|
|
1207
|
+
opportunityId,
|
|
1208
|
+
partnershipId,
|
|
1209
|
+
enquiryId,
|
|
1210
|
+
actions: actionLog.map((entry) => ({ action: entry.action, status: entry.status })),
|
|
1211
|
+
},
|
|
542
1212
|
)
|
|
543
1213
|
|
|
544
1214
|
return {
|
|
@@ -548,8 +1218,12 @@ export function createFormSubmitService(dependencies: SubmissionDependencies): F
|
|
|
548
1218
|
companyId: company?.record.id ?? null,
|
|
549
1219
|
positionId: position?.record.id ?? null,
|
|
550
1220
|
dealId,
|
|
1221
|
+
opportunityId,
|
|
1222
|
+
partnershipId,
|
|
1223
|
+
enquiryId,
|
|
551
1224
|
submittedAt: submission.submittedAt,
|
|
552
1225
|
thankYouMessage: form.thankYouMessage,
|
|
1226
|
+
actionLog,
|
|
553
1227
|
}
|
|
554
1228
|
}, { workspaceId })
|
|
555
1229
|
},
|