Hermes 284313f908
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
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

20 KiB

PRD — CRM Workflow Follow-up Actions

  • Slug: workflow-crm-followup-actions
  • Date: 2026-04-25
  • Status: Draft

Summary

Add the remaining CRM workflow actions after the first-pass CRM plan (workflow-crm-actions) lands. This follow-up plan covers interaction taxonomy, simple activity status transitions, quote creation/search/conversion/approval, and CRM activity tagging.

Planned actions:

  1. crm.create_interaction_type
  2. crm.update_activity_status
  3. crm.create_quote
  4. crm.add_quote_item
  5. crm.create_quote_from_template
  6. crm.find_quotes
  7. crm.submit_quote_for_approval
  8. crm.convert_quote
  9. crm.tag_activity

Explicitly out of scope: crm.create_client_note. Use clients.add_note for client notes and add a future contacts.add_note action under the Contact module if contact note automation is needed.

This plan intentionally builds on discoveries from the recent workflow updates and the first CRM plan: shared runtime actions must not import withAuth server action wrappers directly, picker metadata should follow current Workflow Designer conventions, event publication should use lazy shared-runtime-safe helpers, and DB-backed tests should validate real migrated schema behavior.

Problem

The first-pass CRM plan unlocks activity lookup/update/scheduling and quote sending, but it leaves several high-value CRM automation gaps:

  • MSPs cannot create their own CRM activity taxonomy from workflows.
  • Workflows need a simple “mark this activity completed/closed/won” action without constructing a full patch object.
  • Quote automation remains incomplete without create/search/item/approval/conversion actions.
  • CRM activity tagging is not available for interactions/activities.

These gaps matter because MSP automations often span support, account management, and billing: identify a client state, tag/account-classify it, create or submit a quote, and convert accepted quote value along the way.

Goals

  • Complete the next CRM workflow action layer after first-pass activity and send-quote support.
  • Keep action IDs under crm.* so the existing Designer CRM group continues to work.
  • Provide quote pipeline actions that are safe, permission-checked, and consistent with existing quote lifecycle rules.
  • Provide CRM taxonomy and status helpers that simplify common workflow authoring.
  • Provide a dedicated tag action for CRM activities/interactions.
  • Keep note creation on module-specific actions: existing clients.add_note and a future contacts.add_note if needed.
  • Reuse existing model/service behavior where safe, extracting shared-safe helpers where necessary.

Non-goals

  • Replacing first-pass CRM actions (crm.find_activities, crm.update_activity, crm.schedule_activity, crm.send_quote).
  • Replacing Client module workflow actions such as clients.add_note or clients.add_interaction.
  • Adding a CRM-scoped client/contact note wrapper; note automation should use module-specific actions (clients.add_note, future contacts.add_note).
  • Replacing quote UI/API behavior or adding new quote lifecycle states.
  • Introducing a generic arbitrary-table patch action.
  • Calling withAuth server actions directly from shared/workflow/runtime.
  • Building new Workflow Designer controls beyond metadata-driven picker support.
  • Adding broad quote item editing beyond the create-quote input scope described here.

Prerequisites / Dependencies

  • First-pass CRM plan should be implemented or at least its shared helper decisions should be resolved:
    • lazy workflow event publisher helper for CRM actions
    • CRM permission mapping
    • quote shared-helper/package-boundary approach
    • common CRM activity summary schemas
  • Recent Client workflow action patterns remain the model for picker metadata, event publication, idempotency, and DB-backed tests.
  • Quote actions must respect existing quote status transitions from packages/billing/src/schemas/quoteSchemas.ts.

Users and Primary Flows

Users

  • MSP admin building cross-functional workflows.
  • Account manager automating CRM follow-ups and QBR/upsell workflows.
  • Sales/finance operator automating quote pipeline steps.
  • Internal Alga PSA engineer maintaining Workflow Runtime V2 business operations.

Primary flows

  1. Create CRM taxonomy automatically

    • Workflow detects a new MSP process or onboarding template.
    • Workflow creates a custom interaction type such as QBR, Site Visit, or Upsell Call if it does not exist.
  2. Mark an activity status without a full patch

    • Workflow finds or creates an activity.
    • Workflow runs crm.update_activity_status with a target status ID or status name.
    • The action validates the interaction status and returns the previous/current status.
  3. Create and build a quote

    • Workflow creates a quote header for a client/contact after a qualifying ticket or opportunity event.
    • Workflow either creates a blank quote header with crm.create_quote and adds items with crm.add_quote_item, or creates a populated quote from a template with crm.create_quote_from_template.
    • Workflow optionally submits the quote for approval when tenant settings or business rules require it.
  4. Find quotes before branching

    • Workflow checks whether the client already has open quotes.
    • If none exist, it creates one; if one exists, it updates/sends/submits it through existing CRM quote actions.
  5. Convert an accepted quote

    • Workflow receives a quote accepted event or polls/fetches accepted quote state.
    • Workflow converts quote content to a draft contract, draft invoice, or both based on selected items.
  6. Tag CRM activities

    • Workflow applies tags like Needs QBR, Upsell Candidate, or Onboarding Risk to interactions/activities.
    • Client and contact tagging stay in their own module actions.
    • Tag definition creation and tag application events are emitted consistently.

UX / UI Notes

  • All actions should appear under the existing Workflow Designer CRM group via the crm.* prefix.
  • Suggested labels:
    • crm.create_interaction_type → Create Activity Type
    • crm.update_activity_status → Update Activity Status
    • crm.create_quote → Create Quote
    • crm.add_quote_item → Add Quote Item
    • crm.create_quote_from_template → Create Quote from Template
    • crm.find_quotes → Find Quotes
    • crm.submit_quote_for_approval → Submit Quote for Approval
    • crm.convert_quote → Convert Quote
    • crm.tag_activity → Tag CRM Activity
  • Use picker metadata where supported:
    • client_idclient
    • contact_idcontact, dependent on client_id where appropriate
    • ticket_idticket
    • user_iduser
    • quote_id, interaction_id, interaction_type_id, and interaction_status_id remain UUID fields in v1 unless picker support already exists or is separately introduced.
  • Quote action outputs should include enough summary fields for branching: quote ID, number, status, client ID, totals, conversion target IDs, and approval/send state.

Requirements

crm.create_interaction_type

  • Register action ID crm.create_interaction_type, version 1.
  • Inputs:
    • type_name
    • optional icon
    • optional display_order
    • optional idempotency_key
    • optional if_exists: return_existing or error (default return_existing)
  • Validate non-empty type name and reasonable length.
  • Enforce tenant uniqueness by normalized type name where possible.
  • If display_order is omitted, assign next display order using existing interaction_types max order behavior.
  • Set created_by to workflow actor.
  • Require settings:update because activity types are tenant CRM taxonomy/configuration.
  • Return interaction type summary and created boolean.
  • Use action-provided idempotency.
  • Write run audit.

crm.update_activity_status

  • Register action ID crm.update_activity_status, version 1.
  • Inputs:
    • activity_id
    • either status_id or status_name
    • optional reason
    • optional no_op_if_already_status default true
  • Validate activity exists in tenant.
  • Resolve/validate target status from statuses where status_type = 'interaction'.
  • If already in target status and no-op is true, return no-op without duplicate audit/event noise.
  • Update only status_id and timestamp/audit metadata as available.
  • Return activity ID, previous/current status IDs/names, no_op, and updated activity summary.
  • Implement as a dedicated wrapper around the same safe update path used by crm.update_activity.

crm.create_quote

  • Register action ID crm.create_quote, version 1.
  • Scope v1 to quote header creation only. Quote item creation is handled by crm.add_quote_item.
  • Inputs should support a constrained, workflow-friendly subset of quote header creation:
    • client_id
    • optional contact_id
    • title
    • optional description
    • quote_date
    • valid_until
    • optional po_number
    • optional internal_notes
    • optional client_notes
    • optional terms_and_conditions
    • optional currency_code default USD
    • optional idempotency_key
  • Validate non-template quotes require client ID.
  • Validate valid_until >= quote_date.
  • Validate contact belongs to client when provided.
  • Use existing quote model/schema behavior where package-boundary safe.
  • Return quote summary.
  • Require billing create authorization and quote read authorization decisions equivalent to existing quote actions.
  • Use action-provided idempotency and run audit.

crm.add_quote_item

  • Register action ID crm.add_quote_item, version 1.
  • Inputs:
    • quote_id
    • description
    • quantity
    • optional unit_price
    • optional unit_of_measure
    • optional display_order
    • optional phase
    • optional is_optional default false
    • optional is_selected default true
    • optional is_recurring default false
    • optional billing_frequency
    • optional billing_method: fixed, hourly, or usage
    • optional is_discount default false
    • optional discount_type: percentage or fixed
    • optional discount_percentage
    • optional applies_to_item_id
    • optional applies_to_service_id
    • optional is_taxable default true
    • optional tax_region
    • optional tax_rate
    • optional location_id
    • optional cost
    • optional cost_currency
    • optional idempotency_key
  • Validate quote exists in tenant and is editable.
  • Reject adding items to quote templates in v1 unless template support is explicitly resolved later.
  • Validate line item fields using existing quote item schema rules where package-boundary safe:
    • recurring items require billing frequency.
    • discount items require discount type.
    • percentage discounts require discount percentage.
  • Assign display_order deterministically when omitted.
  • Persist item through shared-safe quote item model/helper behavior.
  • Recalculate quote financials after insertion.
  • Return quote item summary plus refreshed quote summary/totals.
  • Require billing update/read authorization equivalent to existing quote item behavior.
  • Use action-provided idempotency and run audit.

crm.create_quote_from_template

  • Register action ID crm.create_quote_from_template, version 1.
  • Inputs:
    • template_id
    • client_id
    • optional contact_id
    • optional title override
    • optional quote_date override
    • optional valid_until override
    • optional po_number
    • optional internal_notes override/append decision if supported safely
    • optional client_notes override/append decision if supported safely
    • optional currency_code override if supported safely
    • optional idempotency_key
  • Validate template_id points to a tenant quote template (is_template = true).
  • Validate target client exists and contact belongs to that client when supplied.
  • Reuse/extract shared-safe behavior from existing quote template creation logic; do not import withAuth server action wrappers directly into shared runtime.
  • Copy safe template header fields and template items to the new quote.
  • Apply supplied overrides after template defaults.
  • Recalculate quote financials after copying items.
  • Return created quote summary and created item summaries.
  • Require billing create/read authorization equivalent to existing quote template creation behavior.
  • Use action-provided idempotency and run audit.

crm.find_quotes

  • Register action ID crm.find_quotes, version 1.
  • Side-effect-free.
  • Inputs:
    • optional quote_id
    • optional quote_number
    • optional client_id
    • optional status
    • optional date_from
    • optional date_to
    • optional is_template default false
    • pagination and sorting fields aligned with Quote.listByTenant
    • optional on_empty: return_empty or error
  • Require at least one meaningful filter or an explicit bounded date range unless a small page size is enforced.
  • Enforce billing read authorization and quote authorization-kernel filtering equivalent to existing quote list/read behavior.
  • Return paginated quote summaries and first quote.

crm.submit_quote_for_approval

  • Register action ID crm.submit_quote_for_approval, version 1.
  • Inputs:
    • quote_id
    • optional comment or reason
    • optional no_op_if_already_pending default true
  • Validate quote exists, is readable/updatable by workflow actor, is not a template, and is in draft status unless no-op applies.
  • Transition status to pending_approval using existing quote status rules.
  • Record quote activity if existing behavior does or if a shared helper is extracted.
  • Return quote summary, previous status, new status, and no-op flag.
  • Write run audit.

crm.convert_quote

  • Register action ID crm.convert_quote, version 1.
  • Inputs:
    • quote_id
    • target: contract, invoice, or contract_and_invoice
    • optional no_op_if_already_converted default true
  • Validate quote exists, is not a template, and is eligible for conversion.
  • Enforce billing create + update authorization equivalent to existing conversion actions.
  • Reuse shared-safe conversion services:
    • convertQuoteToDraftContract
    • convertQuoteToDraftInvoice
    • convertQuoteToDraftContractAndInvoice
  • Validate selected quote items support the requested target and return clear errors from conversion service as workflow action errors.
  • Return quote summary plus created contract ID and/or invoice ID.
  • Write run audit.

crm.tag_activity

  • Register action ID crm.tag_activity, version 1.
  • Scope v1 to interactions/activities only. Client and contact tagging stay in their own module actions.
  • Inputs:
    • activity_id
    • tags: non-empty array of tag text values
    • optional if_exists: no_op or error (default no_op)
    • optional idempotency_key
  • Validate target interaction/activity exists in tenant.
  • Validate tag text using current tag validation rules.
  • Create missing tag definitions and insert missing tag mappings idempotently.
  • Require CRM interaction/activity update permission and tag create permission when creating new tag definitions, matching existing tag behavior where possible.
  • Emit TAG_DEFINITION_CREATED for newly created definitions and TAG_APPLIED for newly applied mappings through lazy event publishing with deterministic keys.
  • Return added/existing tag summaries and counts.
  • Use action-provided idempotency and run audit.

Explicitly dropped: crm.create_client_note

  • Do not implement crm.create_client_note in this follow-up plan.
  • Use clients.add_note for client note automation.
  • Add a future contacts.add_note Contact-module action if workflows need contact note automation.
  • Rationale: module-specific note actions keep ownership clearer and avoid duplicating the newly merged Client workflow note behavior under CRM.

Cross-cutting Requirements

  • Implement in shared/workflow/runtime/actions/businessOperations/crm.ts or extracted shared-safe helper modules.
  • Do not directly import packages/*/src/actions/* server action wrappers into shared runtime action code.
  • Use withTenantTransaction, requirePermission, writeRunAudit, throwActionError, and rethrowAsStandardError.
  • Use withWorkflowJsonSchemaMetadata for picker-backed fields.
  • Use deterministic idempotency and no-op behavior for retry-sensitive mutations.
  • Keep all actions tenant-scoped and actor-attributed.
  • Preserve crm.create_activity_note and first-pass CRM actions.

Data / API / Integrations

Key files

  • shared/workflow/runtime/actions/businessOperations/crm.ts
  • shared/workflow/runtime/actions/businessOperations/clients.ts
  • shared/workflow/runtime/actions/businessOperations/tickets.ts
  • shared/workflow/runtime/actions/businessOperations/shared.ts
  • shared/workflow/runtime/jsonSchemaMetadata.ts
  • packages/clients/src/actions/interactionTypeActions.ts
  • packages/clients/src/actions/clientNoteActions.ts (reference only; CRM note wrapper dropped)
  • packages/clients/src/actions/contact-actions/contactNoteActions.ts (reference for future contacts.add_note, not this plan)
  • packages/tags/src/actions/tagActions.ts
  • packages/billing/src/actions/quoteActions.ts
  • packages/billing/src/models/quote.ts
  • packages/billing/src/models/quoteItem.ts
  • packages/billing/src/actions/quoteActions.ts (createQuoteFromTemplate as behavior reference; do not import wrapper directly)
  • packages/billing/src/schemas/quoteSchemas.ts
  • packages/billing/src/services/quoteConversionService.ts
  • shared/workflow/streams/domainEventBuilders/tagEventBuilders.ts
  • shared/workflow/streams/domainEventBuilders/crmInteractionNoteEventBuilders.ts

Tables likely touched

  • interaction_types
  • interactions
  • statuses
  • quotes
  • quote_items
  • quote_activities
  • quote conversion target tables for contracts/invoices
  • tag_definitions
  • tag_mappings
  • clients
  • contacts
  • audit_logs

Security / Permissions

  • crm.create_interaction_type requires settings:update because activity types are tenant CRM taxonomy/configuration.
  • Resolve and document CRM interaction mutation permission for crm.update_activity_status.
  • Quote actions, including crm.add_quote_item, must enforce billing create/read/update and quote authorization-kernel decisions equivalent to existing quote actions.
  • crm.tag_activity must enforce CRM interaction/activity update permission and tag create permission when creating new definitions.
  • Workflow actor remains the created_by/performed_by/audit actor unless a future version explicitly supports override.

Observability

  • Write run audit for all side-effectful actions.
  • Emit existing events where supported:
    • TAG_DEFINITION_CREATED
    • TAG_APPLIED
  • Quote pipeline actions should emit quote-specific workflow events only when matching QUOTE_* event schemas/builders already exist. If no matching quote event contract exists, remain audit-only plus existing quote activity records.
  • Do not invent new QUOTE_* or INTERACTION_UPDATED events in this plan; new event contracts require a separate event-schema plan.
  • Capture no-op outcomes in action outputs and audit where useful.

Rollout / Migration

  • No database migration is expected by default.
  • All actions are additive.
  • If quote helper extraction changes package exports, keep it backward compatible and covered by existing quote tests.
  • crm.create_client_note is dropped in favor of module-specific actions; no migration or runtime registration is needed for it.

Open Questions

All planning decisions for this follow-up scope are resolved. Implementation may still discover package-boundary details that need scratchpad updates.

Acceptance Criteria (Definition of Done)

  • Runtime initialization registers the selected follow-up actions at version 1.
  • Designer catalog shows the selected actions under CRM with labels, schema metadata, and output schemas.
  • Interaction type creation is idempotent and permission-checked.
  • Activity status update provides a simple validated transition/no-op wrapper.
  • Quote create/add-item/template/find/submit/convert actions respect quote lifecycle, billing permissions, and authorization-kernel behavior.
  • Quote pipeline actions emit only already-defined quote workflow events; when no matching quote event schema/builder exists, they remain audit-only plus quote activity records.
  • CRM activity tagging creates definitions/mappings idempotently for interactions and emits tag events for new changes.
  • crm.create_client_note remains absent; client note automation uses clients.add_note, and contact note automation is deferred to a future Contact-module action.
  • DB-backed tests cover representative happy paths and high-risk guard cases.
  • Existing first-pass CRM, Client, Ticket, and quote tests do not regress.