PSA/docs/forms/system-forms.md
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

9.6 KiB

System Forms

System Forms represent shared, reusable form definitions that are available to all tenants within the platform. This concept is analogous to System Workflows as described in the Workflow System documentation.

Overview

System Forms provide a way to define forms once at the system level and make them available to all tenants. This reduces duplication, simplifies management, and ensures a consistent user experience across the platform.

Key Characteristics

  • Shared Definitions: System Forms are defined once at the system level and are available to all tenants. These serve as reusable templates for common human task interactions.

  • Tenant-Specific Usage: While the form definition is shared, its instantiation and data capture remain tenant-specific, ensuring data isolation. When a human task requiring a form is generated for a specific tenant, it can utilize a System Form definition.

  • Identification: System Forms are identified by a unique name in the system_workflow_form_definitions table. When a human task is created using a taskType, the system looks up the corresponding task definition. This task definition then specifies the form_id (which is the name of the form in system_workflow_form_definitions or workflow_form_definitions) and form_type to be used.

  • Usage by Tenant-Specific Workflows:

    • Tenant-specific workflows (defined in workflow_registrations) can utilize System Forms.
    • When a human task is created using a taskType, the system first retrieves the associated task definition (either from system_workflow_task_definitions or workflow_task_definitions). This task definition contains a form_id and a form_type. The Form Registry service then uses this form_type to determine which form definition table to query:
      • If form_type is 'system', it directly queries the system_workflow_form_definitions table.
      • If form_type is 'tenant' or not specified, it queries the tenant-specific workflow_form_definitions table.
    • This approach is more efficient as it avoids unnecessary fallback queries.

Database Schema

System Forms are stored in the system_workflow_form_definitions table:

  • definition_id: UUID, Primary Key, default gen_random_uuid()
  • name: TEXT, NOT NULL, UNIQUE - The globally unique identifier for the system form
  • description: TEXT, NULLABLE
  • version: TEXT, NOT NULL
  • status: TEXT, NOT NULL - e.g., 'ACTIVE', 'DRAFT', 'ARCHIVED'
  • category: TEXT, NULLABLE
  • tags: TEXT[], NULLABLE
  • json_schema: JSONB, NOT NULL - Stores the JSON Schema for form validation
  • ui_schema: JSONB, NULLABLE - Stores the UI Schema for form rendering
  • default_values: JSONB, NULLABLE - Default values for form fields
  • created_by: UUID, NULLABLE, Foreign Key to users.id or a system identifier
  • created_at: TIMESTAMPTZ, NOT NULL, default CURRENT_TIMESTAMP
  • updated_at: TIMESTAMPTZ, NOT NULL, default CURRENT_TIMESTAMP

Unlike tenant-specific forms, which use separate tables for form definitions (workflow_form_definitions) and schemas (workflow_form_schemas), System Forms combine both aspects in a single table. This approach offers several benefits:

  1. Simplicity: Reduces table count and simplifies queries for retrieving complete system form definitions.
  2. Consistency: Aligns with how other system-level entities like system_workflow_registrations store their JSONB configuration directly.
  3. Cohesion: System form definitions and their schemas are typically a single, cohesive unit, making a combined table more natural.

Form Type in Task Definitions

The workflow_task_definitions table includes a form_type field that indicates whether the formId refers to a tenant-specific form ('tenant') or a system form ('system'). This optimization allows the Form Registry service to directly query the appropriate table without needing to first check the tenant-specific table and then fall back to the system table.

When a human task is created using a taskType, the system first looks up the corresponding task definition (system or tenant-specific). This task definition contains the form_id (the name of the form, e.g., 'qbo-customer-mapping-lookup-error-form') and form_type which are then used to retrieve the actual form schema:

  1. The system first looks up the task definition to get the actual form_id and form_type.
  2. Based on the form_type, it directly queries the appropriate table:
    • If form_type is 'system', it looks for the form in the system_workflow_form_definitions table.
    • If form_type is 'tenant' or not specified, it looks for the form in the tenant-specific workflow_form_definitions table.

This approach is more efficient as it avoids unnecessary fallback queries and potential issues with foreign key constraints, as each table can have its own foreign key relationship.

Registration and Retrieval

Registering a System Form

To register a System Form, use the registerSystemWorkflowFormDefinitionAction:

import { registerSystemWorkflowFormDefinitionAction } from 'server/src/lib/actions/workflow-actions/formRegistryActions';

// Register a new system form
const definitionId = await registerSystemWorkflowFormDefinitionAction({
  name: 'system-credit-reimbursement-request',
  description: 'System-wide form for requesting credit reimbursements',
  version: '1.0.0',
  category: 'finance',
  status: FormStatus.ACTIVE,
  jsonSchema: {
    type: 'object',
    required: ['customer', 'amount', 'reason'],
    properties: {
      customer: {
        type: 'string',
        title: 'Customer Name'
      },
      amount: {
        type: 'number',
        title: 'Amount'
      },
      reason: {
        type: 'string',
        title: 'Reason for Reimbursement'
      },
      date: {
        type: 'string',
        format: 'date',
        title: 'Date of Transaction'
      }
    }
  },
  uiSchema: {
    customer: {
      'ui:autofocus': true
    },
    amount: {
      'ui:widget': 'currencyWidget'
    },
    reason: {
      'ui:widget': 'textarea'
    },
    date: {
      'ui:widget': 'date'
    }
  },
  defaultValues: {
    date: new Date().toISOString().split('T')[0]
  }
}, ['reimbursement', 'credit', 'finance', 'system']);

Retrieving a Form

The getFormAction function now uses the form_type field to determine which table to query:

import { getFormAction } from 'server/src/lib/actions/workflow-actions/formRegistryActions';

// Get a form by ID (this is actually the task_definition_id)
const form = await getFormAction('credit-reimbursement-request');

// Get a specific version of a form
const formV2 = await getFormAction('credit-reimbursement-request', '2.0.0');

The retrieval process:

  1. First, it looks up the task definition to get the actual form_id and form_type.
  2. Based on the form_type, it directly queries the appropriate table:
    • If form_type is 'system', it queries the system_workflow_form_definitions table.
    • If form_type is 'tenant' or not specified, it queries the workflow_form_definitions table.

QBO Invoice Sync Forms

Note: This section is retained for historical reference. Alga PSA no longer uses the legacy QBO workflow-based invoice sync described here for current QuickBooks functionality. The supported QuickBooks path is QuickBooks CSV via the shared accounting export pipeline.

The QBO Invoice Sync Workflow forms are registered as System Forms through a dedicated migration script (20250509175818_add_qbo_invoice_sync_forms.cjs). This migration:

  1. Creates the system_workflow_form_definitions table.

  2. Registers four base generic forms as System Forms:

    • qbo-mapping-error-form: For entity mapping errors
    • qbo-lookup-error-form: For entity lookup errors
    • qbo-api-error-form: For QBO API communication errors
    • workflow-error-form: For general workflow execution errors
  3. Registers ten specialized forms by extending these base forms:

    • qbo-customer-mapping-lookup-error-form
    • secret-fetch-error-form
    • qbo-mapping-error-form-specialized
    • qbo-item-lookup-failed-form
    • qbo-item-mapping-missing-form
    • qbo-invoice-no-items-mapped-form
    • qbo-sync-error-form
    • workflow-execution-error-form
    • internal-workflow-error-form
  4. Creates task definitions that associate each task type with its corresponding form, including the form_type: 'system' field to indicate that these forms are System Forms.

Benefits

The System Forms approach offers several key benefits:

  1. Reduced Duplication: Forms only need to be defined once at the system level, rather than for each tenant.
  2. Simplified Management: Updates to system forms are automatically available to all tenants.
  3. Consistent User Experience: Ensures a consistent look and feel for common tasks across the platform.
  4. Tenant Customization: Tenants can still override system forms with their own custom versions if needed.
  5. Architectural Alignment: Follows the same pattern as other system-level entities like System Workflows.

Integration with Human Tasks

When creating a human task that requires a form, you can reference a System Form:

await typedActions.createHumanTask({
  taskType: 'qbo_mapping_error', // The form is determined by the task definition associated with this taskType
  title: `Failed QBO Customer Mapping Lookup for Company ID: ${algaCompany.company_id}`,
  details: {
    // Task details...
  },
  assignedUserId: null,
  tenantId: tenant,
});

The Form Registry service will resolve the form based on the form_id and form_type found in the task definition, which was looked up using the taskType provided during task creation.