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 — CRM Workflow Actions
- Plan slug:
workflow-crm-actions - Created:
2026-04-25
What This Is
Rolling notes for expanding Workflow Runtime V2 CRM actions beyond crm.create_activity_note.
Decisions
- (2026-04-25) First implementation pass is scoped to
crm.find_activities,crm.update_activity,crm.schedule_activity, andcrm.send_quote. Rationale: these match the user's recommended high-value first pass and unlock CRM lookup/update/follow-up/quote-send workflows without committing to the entire roadmap at once. - (2026-04-25) Keep all first-pass actions under
crm.*so existing designer catalog grouping places them under the built-in CRM group. - (2026-04-25) Do not call
withAuthNext.js server action wrappers directly fromshared/workflow/runtime. Reuse underlying models/services only when package-boundary safe, or extract shared-safe helpers first. - (2026-04-25) Mirror latest Client workflow action patterns: picker metadata via
withWorkflowJsonSchemaMetadata, lazy event-bus imports, deterministic event idempotency keys, and DB-backed shared-root tests. - (2026-04-25) Treat
crm.create_client_noteas roadmap-only until we decide whether newly mergedclients.add_notealready satisfies the intended user need.
Discoveries / Constraints
- (2026-04-25) Current CRM workflow action file is
shared/workflow/runtime/actions/businessOperations/crm.tsand only registerscrm.create_activity_note. - (2026-04-25) Latest main added substantial Client workflow action coverage in
shared/workflow/runtime/actions/businessOperations/clients.ts, includingclients.add_noteandclients.add_interaction; CRM plan must avoid duplicating those module-specific semantics by accident. - (2026-04-25) Client workflow actions introduced a local
withWorkflowPickerhelper usingwithWorkflowJsonSchemaMetadataandx-workflow-picker-kind; CRM should use the same convention for supported fields. - (2026-04-25) Client workflow actions publish events using a lazy dynamic import helper (
publishWorkflowDomainEvent) so shared-root tests do not fail when@alga-psa/event-busis not resolvable. CRM should copy that pattern. - (2026-04-25) Existing interaction server actions are in
packages/clients/src/actions/interactionActions.ts:addInteraction,getInteractionsForEntity,getRecentInteractions,updateInteraction,getInteractionStatuses, anddeleteInteraction. - (2026-04-25) Existing interaction model is in
packages/clients/src/models/interactions.tsand supportsgetForEntity,getRecentInteractions,addInteraction,updateInteraction, andgetById, but those model methods callcreateTenantKnexinternally; direct use from a workflow transaction may require careful adaptation or local query implementation. - (2026-04-25) Existing interaction type server action is
createInteractionTypeinpackages/clients/src/actions/interactionTypeActions.tsand writes tointeraction_typeswithcreated_by. - (2026-04-25) Existing quote server actions are in
packages/billing/src/actions/quoteActions.ts:createQuote,sendQuote,submitQuoteForApproval, conversion wrappers, and related helpers. These arewithAuthserver actions and should not be imported directly into shared runtime handlers. - (2026-04-25) Existing quote model in
packages/billing/src/models/quote.tssupportsgetById,getByNumber,listByTenant,listByClient,create, and update/status transition behavior. - (2026-04-25) Quote conversion services in
packages/billing/src/services/quoteConversionService.tsexposeconvertQuoteToDraftContract,convertQuoteToDraftInvoice, andconvertQuoteToDraftContractAndInvoicefor later roadmap actions. - (2026-04-25) CRM event schemas/builders currently include
INTERACTION_LOGGED,NOTE_CREATED,TAG_DEFINITION_CREATED,TAG_APPLIED, andTAG_REMOVED; no obviousINTERACTION_UPDATEDevent was found in initial search. - (2026-04-25) Tag event builders exist in
shared/workflow/streams/domainEventBuilders/tagEventBuilders.ts; tag action implementation can follow ticket/client tag patterns later.
Commands / Runbooks
- (2026-04-25) Initial discovery commands:
rg -n "export async function (updateInteraction|getRecentInteractions|getInteractionsForEntity|createInteractionType)|function (updateInteraction|getRecentInteractions|getInteractionsForEntity|createInteractionType)|createInteractionType|updateInteraction|getRecentInteractions|getInteractionsForEntity" packages server shared ee -g'*.ts' | head -160rg -n "createQuote|sendQuote|convertQuoteToDraft|submit.*approval|find.*Quote|Quote" packages/billing server/src ee packages -g'*.ts' | head -220rg -n "TAG_APPLIED|TAG_REMOVED|TAG_DEFINITION_CREATED|buildNoteCreatedPayload|buildInteractionLoggedPayload|NOTE_CREATED|INTERACTION_LOGGED" shared packages server ee -g'*.ts' | head -180rg -n "export (async function|const) (createQuote|sendQuote|submitQuote|.*approval|find|getQuote|updateQuote|convertQuote)|function (createQuote|sendQuote|submitQuote)|const (createQuote|sendQuote|submitQuote)" packages/billing/src/actions/quoteActions.ts server/src -g'*.ts' | head -160rg -n "list\\(|QuoteListOptions|getAll|page|filters|status|client_id" packages/billing/src/models/quote.ts | head -180
Links / References
shared/workflow/runtime/actions/businessOperations/crm.tsshared/workflow/runtime/actions/businessOperations/clients.tsshared/workflow/runtime/actions/businessOperations/tickets.tsshared/workflow/runtime/actions/businessOperations/shared.tsshared/workflow/runtime/actions/registerBusinessOperationsActions.tsshared/workflow/runtime/designer/actionCatalog.tsshared/workflow/runtime/jsonSchemaMetadata.tspackages/clients/src/actions/interactionActions.tspackages/clients/src/models/interactions.tspackages/clients/src/actions/interactionTypeActions.tspackages/billing/src/actions/quoteActions.tspackages/billing/src/models/quote.tspackages/billing/src/services/quoteConversionService.tsshared/workflow/streams/domainEventBuilders/crmInteractionNoteEventBuilders.tsshared/workflow/streams/domainEventBuilders/tagEventBuilders.tsshared/workflow/runtime/schemas/crmEventSchemas.ts
Open Questions
- What permission resource/action should govern CRM interaction reads and mutations? Candidate mappings are
client,contact, or an existing CRM/activity-specific permission if present. - Should
crm.update_activityemit any workflow event, or should it only audit because there is no currentINTERACTION_UPDATEDschema? - Should
crm.schedule_activityemitINTERACTION_LOGGEDeven though it creates a future-dated interaction/follow-up? - What is the safest package boundary for quote send logic from shared workflow runtime? Can we extract reusable quote send logic from
packages/billing/src/actions/quoteActions.ts, or should workflow runtime implement an equivalent helper locally? - Should
crm.send_quoteno-op on already sent quotes whenno_op_if_already_sentis true, or should resend be explicitly out of scope until acrm.resend_quoteaction exists? - Should
crm.create_client_noteremain on the CRM roadmap now thatclients.add_noteexists?
Implementation Log (2026-04-26)
- Implemented all first-pass CRM runtime actions in
shared/workflow/runtime/actions/businessOperations/crm.ts:crm.find_activitiescrm.update_activitycrm.schedule_activitycrm.send_quote
- Preserved existing
crm.create_activity_notebehavior and registration. - Added shared CRM runtime helper patterns to match current V2 conventions:
withWorkflowPicker+withWorkflowJsonSchemaMetadata- lazy event publication helper for workflow domain events
- tenant-scoped detail joins and summary normalization for interactions
- standardized validation +
throwActionError/rethrowAsStandardErrorerror mapping
Open Question Resolutions (2026-04-26)
- Permission mapping for CRM interactions:
crm.find_activitiesnow requiresclient:read, and additionally requirescontact:read/ticket:readwhen those filters are supplied.crm.update_activityandcrm.schedule_activityrequireclient:update;crm.schedule_activityadditionally requiresticket:readwhenticket_idis provided.- Rationale: interactions are CRM records anchored to client relationships; supplemental resource permissions are enforced when filters/links depend on those resources.
crm.update_activityevent emission:- Decision: do not emit a CRM update domain event in v1; rely on run audit + returned before/after diff.
- Rationale: no
INTERACTION_UPDATEDschema currently exists; avoided introducing new event schema in first pass.
- Future-dated schedule event semantics:
- Decision:
crm.schedule_activityemitsINTERACTION_LOGGEDwith scheduledinteraction_dateand deterministic idempotency key. - Rationale: creation of the interaction record is the durable event; consumers can branch on time fields.
- Decision:
- Quote helper boundary:
- Decision:
crm.send_quoteuses shared-runtime-safe DB/model helpers and best-effort internal helpers; it does not importwithAuthserver action wrappers. - Rationale: satisfies shared runtime boundary and keeps quote send semantics in workflow runtime.
- Decision:
- Already-sent quote behavior:
- Decision:
crm.send_quoteno-ops by default when quote is alreadysent(no_op_if_already_sent=true), and returns metadata withno_op=true. - Decision: if
no_op_if_already_sent=false, action raises validation error (resend out of scope for v1 action).
- Decision:
crm.create_client_noteroadmap decision:- Decision: remains roadmap-only; no implementation added in this pass.
- Rationale: overlaps with existing
clients.add_notebehavior.
Test Coverage Added (2026-04-26)
- Added runtime/unit tests:
shared/workflow/runtime/actions/__tests__/registerCrmActionsMetadata.test.tsshared/workflow/runtime/__tests__/workflowDesignerCrmCatalogRuntime.test.tsshared/workflow/runtime/nodes/__tests__/actionCallCrmSaveAsRuntime.test.ts
- Added DB-backed action tests:
shared/workflow/runtime/actions/__tests__/businessOperations.crm.db.test.ts- Covers: find/update/schedule/send success, guard/failure paths, permission denials,
INTERACTION_LOGGEDpublish path, andcrm.create_activity_noteregression.
Commands / Runbooks (2026-04-26)
- Runtime/unit tests:
cd shared && npx vitest workflow/runtime/actions/__tests__/registerCrmActionsMetadata.test.ts workflow/runtime/__tests__/workflowDesignerCrmCatalogRuntime.test.ts workflow/runtime/nodes/__tests__/actionCallCrmSaveAsRuntime.test.ts --run
- DB-backed CRM tests:
cd shared && npx vitest workflow/runtime/actions/__tests__/businessOperations.crm.db.test.ts --run