Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
11 KiB
11 KiB
Scratchpad — Workflow Contact Actions
- Plan slug:
2026-04-25-workflow-contact-actions - Created:
2026-04-25
What This Is
Working notes for adding Contact module workflow actions: create, edit, deactivate, delete, duplicate, add tag, assign to ticket, add note, add interaction, add to client, and move to client.
Decisions
- 2026-04-25: Draft plan uses one workflow action per requested operation (
contacts.create,contacts.update, etc.) instead of a single mode-based generic contact mutation action. Rationale: better workflow designer UX and clearer downstream output schemas. - 2026-04-25: Draft plan keeps actions additive under the existing
contacts.*prefix so the current designer catalog grouping should place them under Contact. - 2026-04-25: Draft plan recommends requiring a new unique email for
contacts.duplicatebecause contact primary emails are tenant-unique. - 2026-04-25: Draft plan treats
contacts.add_to_clientandcontacts.move_to_clientas separate author-facing actions even though both ultimately updatecontacts.client_id; their conflict/idempotency semantics differ. - 2026-04-25: User approved separating contact deactivation and hard deletion into two actions instead of using a flag.
contacts.deactivateshould be the safe idempotent action requiringcontact:update;contacts.deleteshould remain destructive guarded hard delete requiringcontact:delete. - 2026-04-25: After merging latest main with workflow Client actions, contact actions should mirror the new Client runtime conventions where applicable: action-provided idempotency for create/duplicate/add_tag/add_note/add_interaction; engine-provided idempotency for update/deactivate/delete/assignment; destructive delete requires
confirm: true; delete supports expliciton_not_foundbehavior. - 2026-04-25: Updated
contacts.add_noteplan to be document-backed viacontacts.notes_document_id, not interaction-backed. Rationale: latestclients.add_noteestablishes module-specific notes as append-only notes-document behavior, whilecontacts.add_interactionand genericcrm.create_activity_notecover interaction rows. - 2026-04-25: User resolved remaining product questions: duplicate contact requires a new unique email; contact-to-ticket should not set ticket client unless the existing app action does; contact add-tag should match client add-tag behavior; contact create/update/deactivate should publish domain events with the same lazy best-effort pattern as Client actions.
Discoveries / Constraints
- 2026-04-25: Existing contact workflow actions live in
shared/workflow/runtime/actions/businessOperations/contacts.tsand currently registercontacts.findandcontacts.search. - 2026-04-25: Runtime bootstrap calls
registerBusinessOperationsActionsV2(), which callsregisterContactActions(), so new actions incontacts.tsshould flow into the catalog automatically after runtime initialization. - 2026-04-25:
ContactModel.createContactrequires bothfull_nameandemail; it validates phone numbers, additional email addresses, primary email type, duplicate email, and client existence. - 2026-04-25:
ContactModel.updateContactsupports patch-like update input but has strict primary-email promotion behavior. Changing primary email requires promoting an existing/additional email path rather than blindly swapping. - 2026-04-25: Existing contact search in workflow uses
tag_definitions/tag_mappingsfor contact tags (tagged_type = 'contact'). - 2026-04-25:
server/src/lib/api/services/ContactService.tshas a privatehandleTags()that referencescontact_tags, butrgdid not find a migration creatingcontact_tags. Implementation should verify and likely usetag_mappings/TagModelinstead. - 2026-04-25: Existing contact delete server action in
packages/clients/src/actions/contact-actions/contactActions.tsxusesdeleteEntityWithValidation('contact', ...)and performs cleanup for entity tags, phone rows, comments, portal invitations, notes document content/associations, Entra reconciliation queue references, then deletes fromcontacts. - 2026-04-25: Contact deletion config in
packages/core/src/config/deletion/index.tslists dependencies: tickets, interactions, document associations, portal users, survey invitations/responses, and asset associations. - 2026-04-25: Generic CRM note workflow action
crm.create_activity_notealready creates rows ininteractionsusing systemNotetype and supports contact targets. After reviewing new Client actions,contacts.add_noteshould not wrap this path; it should append to the contact notes document, leaving interaction rows tocontacts.add_interaction/crm.create_activity_note. - 2026-04-25: Existing
packages/clients/src/actions/interactionActions.tshasaddInteractionbehavior: requires eitherclient_idorcontact_name_id, derives client from contact if needed, and uses default interaction status when omitted. - 2026-04-25: Permission resources exist for
contact:create/read/update/delete,interaction:create/read/update/delete, andtag:create/read/update/deletein migrations/seeds. - 2026-04-25: Existing ticket workflow actions define local picker metadata helpers in
tickets.ts; contact actions may need similar helpers or shared extraction to keep schemas designer-friendly. - 2026-04-25: Latest
clients.assign_to_ticketuses a direct ticket update contract with previous/current outputs and relationship validation.contacts.assign_to_ticketshould follow that style rather than requiring anexpected_current_contact_idguard in v1. - 2026-04-25: Quick app-path discovery found
packages/tickets/src/components/ticket/TicketDetails.tsxcontact-change code callsupdateTicket(ticket_id, { contact_name_id: newContactId })only, so the workflow contact assignment plan should not auto-settickets.client_idin v1. - 2026-04-25: Latest
clients.add_tagavoids duplicate-insert error control flow because a caught23505still aborts the Postgres transaction. Contact tag implementation should pre-check mappings/definitions and return existing mappings idempotently. - 2026-04-25: Latest Client actions use best-effort lazy workflow event publication for create/update/archive/note/interaction paths. Contact implementation should either match this pattern where event builders exist or document any intentional gap.
Commands / Runbooks
- 2026-04-25: Context discovery commands used:
find ee/docs/plans -maxdepth 2 -type f \( -name 'PRD.md' -o -name 'features.json' -o -name 'tests.json' -o -name 'SCRATCHPAD.md' \) | head -40git log --oneline -5 && git status --shortrg -n "class ContactModel|createContact|updateContact|deleteContact|ContactModel\." shared server packages ee -g '*.ts' -g '*.tsx' | head -200rg -n "contact_name_id|contacts|interaction|tag_mappings|tag_definitions" server shared packages ee -g '*.ts' -g '*.tsx' -g '*.cjs' | head -240rg -n "createTable\\('contact_tags'|contact_tags" server/migrations ee/server/migrations -g '*.cjs'
Links / References
shared/workflow/runtime/actions/businessOperations/contacts.tsshared/workflow/runtime/actions/businessOperations/tickets.tsshared/workflow/runtime/actions/businessOperations/crm.tsshared/workflow/runtime/actions/businessOperations/clients.tsshared/workflow/runtime/actions/registerBusinessOperationsActions.tsshared/models/contactModel.tsshared/models/tagModel.tsserver/src/lib/api/schemas/contact.tsserver/src/lib/api/services/ContactService.tspackages/clients/src/actions/contact-actions/contactActions.tsxpackages/clients/src/actions/contact-actions/contactNoteActions.tspackages/clients/src/actions/interactionActions.tspackages/core/src/config/deletion/index.tsdocs/AI_coding_standards.md
Resolved Questions
contacts.duplicaterequires a new unique email override.contacts.assign_to_ticketshould only settickets.contact_name_idin v1 unless an existing app action path is found to settickets.client_idtoo. Current discovery indicates the app updates onlycontact_name_id.contacts.add_tagshould create missing tag definitions exactly likeclients.add_tag, under the same update-style permission policy.- Contact create/update/deactivate workflow actions should publish contact domain events using the same lazy best-effort pattern as Client actions.
Implementation Log
- 2026-04-26: Implemented full
contacts.*mutation action surface inshared/workflow/runtime/actions/businessOperations/contacts.ts:- Added registrations/schemas/handlers for
contacts.create,contacts.update,contacts.deactivate,contacts.delete,contacts.duplicate,contacts.add_tag,contacts.assign_to_ticket,contacts.add_note,contacts.add_interaction,contacts.add_to_client,contacts.move_to_client. - Added workflow picker metadata helper usage for contact/client/ticket IDs and compact normalized contact summary output schema reuse.
- Added tenant-scoped helper loaders and validations for contact/client/ticket lookups with standard workflow error categories.
- Added action-provided idempotency for create/duplicate/add_tag/add_note/add_interaction and engine-provided idempotency for update/deactivate/delete/assignment/client-move actions.
- Added permission checks per PRD (
contact:create/read/update/delete,ticket:update) before mutation paths. - Added workflow run audit writes for all side-effectful contact actions via
writeRunAudit. - Added guarded hard-delete implementation using
deleteEntityWithValidationplus contact-owned artifact cleanup (tag_mappings, phone/email rows, comments, portal invitations, notes documents, enterprise queue references). - Added best-effort lazy workflow domain event publishing for contact create/update/deactivate plus note/interaction events.
- Added registrations/schemas/handlers for
- 2026-04-26: Implemented duplicate-contact constraints/behavior:
- Requires explicit new primary email (
input.emailrequired in schema). - Supports field overrides and optional tag copy via canonical
tag_definitions+tag_mappings. - Does not copy historical relationships (tickets/interactions/notes docs/etc.).
- Additional emails are only copied when explicitly provided as an override. Rationale: current schema enforces tenant-unique normalized additional emails, so blind copying source additional emails can violate unique constraints.
- Requires explicit new primary email (
- 2026-04-26: Added tests:
shared/workflow/runtime/actions/__tests__/registerContactActionsMetadata.test.tsfor registration/catalog labels/idempotency and picker + compact output schema checks (T001/T002).shared/workflow/runtime/actions/__tests__/businessOperations.contacts.db.test.tsfor DB-backed integration + permission + audit/event coverage acrosscontacts.*mutations (T003-T015).
Verification
- 2026-04-26: Ran targeted suites with shared Vitest config:
pnpm vitest --config shared/vitest.config.ts shared/workflow/runtime/actions/__tests__/registerContactActionsMetadata.test.ts shared/workflow/runtime/actions/__tests__/businessOperations.contacts.db.test.ts shared/workflow/runtime/actions/__tests__/businessOperations.contacts.emailSearch.test.ts- Result: pass (
3files,16tests).
Gotchas
- 2026-04-26:
tickets.client_idin current schema is non-null in this test environment, so no-client ticket scenarios are not representable in DB setup. Assignment tests validate thatcontacts.assign_to_ticketonly updatestickets.contact_name_idand preserves existingtickets.client_idunchanged.