Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
11 KiB
(Archived) QBO Invoice Sync Forms Migration Plan (for 20250509175818_add_qbo_invoice_sync_forms.cjs)
Status: Archived. Alga PSA no longer uses the legacy QBO workflow-based sync described by this document. The currently supported QuickBooks integration path is QuickBooks CSV (manual export/import) via the shared accounting export pipeline.
This document outlines the plan and details for the database migration that registers the System Forms and associated Task Definitions for the QuickBooks Online Invoice Sync workflow. This plan assumes that FormRegistry.getForm() will serve schemas as they are stored, without performing server-side resolution of named $refs within allOf constructs. Therefore, all schemas stored by this migration must be fully self-contained.
I. Prerequisites
system_workflow_form_definitionsTable: This table must exist. If not created by a prior migration, this migration should create it.- Columns:
definition_id(UUID PK),name(TEXT UNIQUE NOT NULL),description(TEXT),version(TEXT NOT NULL),status(TEXT NOT NULL),category(TEXT),tags(TEXT[]),json_schema(JSONB NOT NULL),ui_schema(JSONB),default_values(JSONB),created_by(UUID),created_at(TIMESTAMPTZ NOT NULL),updated_at(TIMESTAMPTZ NOT NULL).
- Columns:
system_workflow_task_definitionsTable: This table must exist. If not created by a prior migration, this migration should create it.- Columns:
task_type(TEXT PK),name(TEXT NOT NULL),description(TEXT),form_id(TEXT NOT NULL, referencessystem_workflow_form_definitions.name),form_type(TEXT NOT NULL, should be 'system'),default_priority(TEXT),default_sla_days(INTEGER),created_at(TIMESTAMPTZ),updated_at(TIMESTAMPTZ),created_by(UUID).
- Columns:
- Helper Functions for Schema Composition: The migration script will need JavaScript helper functions to compose final schemas from base definitions and extensions. Examples:
composeSchema(baseJsonSchema, extensionJsonSchema)composeUiSchema(baseUiSchema, extensionUiSchema)composeDefaultValues(baseDefaults, extensionDefaults)
II. Migration Script Logic (exports.up)
The up function will perform the following steps within a transaction:
-
Define Base Generic Form Schemas (as JavaScript Objects):
baseQboMappingErrorForm: For mapping errors.baseQboLookupErrorForm: For lookup errors.baseQboApiErrorForm: For QBO API errors.baseWorkflowErrorForm: For general workflow errors.- Task 1 (Define Base Schemas): Fully define the
jsonSchema,uiSchema, anddefaultValuesfor these four base forms.
-
Define Specialized QBO Form Extensions (as JavaScript Objects): For each of the 10 specialized QBO task types, define the "extension" part of their schema – i.e., the properties, UI hints, and default values that are specific to them or override the base.
qbo-customer-mapping-lookup-error-form(extendsbaseQboMappingErrorForm)secret-fetch-error-form(extendsbaseQboApiErrorForm)qbo-mapping-error-form-specialized(extendsbaseQboMappingErrorForm- clarify if this name is final or if it's the same as the base)qbo-item-lookup-failed-form(extendsbaseQboLookupErrorForm)qbo-item-mapping-missing-form(extendsbaseQboMappingErrorForm)qbo-invoice-no-items-mapped-form(extendsbaseQboMappingErrorForm)qbo-sync-error-form(extendsbaseQboApiErrorForm)workflow-execution-error-form(extendsbaseWorkflowErrorForm)internal-workflow-error-form(extendsbaseWorkflowErrorForm)- Task 2 (Define Extensions): For each of the 10 forms, detail its specific
jsonSchemaadditions/overrides,uiSchemaadditions/overrides, anddefaultValuesadditions/overrides.
-
Compose and Register Specialized Forms into
system_workflow_form_definitions: Iterate through the 10 specialized form definitions:- Use the helper functions to compose the
finalJsonSchema,finalUiSchema, andfinalDefaultValuesfrom the appropriate base and the specific extension. - Insert a new record into
system_workflow_form_definitionswith:name: The specialized form name (e.g., 'qbo-customer-mapping-lookup-error-form').json_schema: ThefinalJsonSchema(fully self-contained).ui_schema: ThefinalUiSchema.default_values: ThefinalDefaultValues.- Other metadata:
version,status,description,category,created_by, etc.
- Task 3 (created_by Value): Determine the appropriate value for
created_byfor these system records.
- Use the helper functions to compose the
-
Create/Update
system_workflow_task_definitions: For each of the 10 specialized forms registered:- Insert/update a record in
system_workflow_task_definitions. task_type: The corresponding workflow task type string (e.g., 'qbo_sync_error').name: A descriptive name for the task definition.description: From the form definition.form_id: Thenameof the specialized system form just registered.form_type: Set explicitly to'system'.- Other defaults like
default_priority. - Handle conflicts on
task_type(e.g., using.onConflict('task_type').merge()).
- Insert/update a record in
III. Migration Script Logic (exports.down)
The down function will perform the following steps within a transaction:
- Delete
system_workflow_task_definitions: Delete records for the 10 QBO task types. - Delete
system_workflow_form_definitions: Delete records for the 10 specialized QBO form names. - (Optional) Drop tables if this migration created them and no other data relies on them.
IV. Unanswered Questions & Further Tasks
- Task 1 (Define Base Schemas): Provide complete
jsonSchema,uiSchema, anddefaultValuesfor:baseQboMappingErrorFormbaseQboLookupErrorFormbaseQboApiErrorFormbaseWorkflowErrorForm
- Task 2 (Define Extensions): For each of the 10 specialized QBO forms, provide their specific schema "extension" parts relative to their base.
- Example for
qbo-customer-mapping-lookup-error-form:extension.jsonSchema.properties:{ algaCustomerId: { type: 'string', title: 'Alga Customer ID', readOnly: true }, guidance: { type: 'string', title: 'Guidance', default: 'Please check customer mapping in QBO settings.', readOnly: true } }extension.jsonSchema.required:['algaCustomerId'](in addition to base's required)extension.uiSchema:{ 'ui:order': ['errorMessage', 'algaCustomerId', 'guidance', 'resolutionNotes', '*'] }extension.defaultValues:{ guidanceText: "Ensure the customer exists in QBO and is correctly mapped." }
- ... (Repeat for all 10 forms) ...
- Example for
- Task 3 (created_by Value): Decide on a consistent
created_byvalue (e.g., a specific system user UUID, ornull) for records inserted by migrations. - Task 4 (Schema Composition Helpers): Review and refine the
composeSchema,composeUiSchema, andcomposeDefaultValuesJavaScript helper functions within the migration script to ensure they correctly merge all aspects of the schemas as intended (especiallyui:order, nested objects, etc.). - Task 5 (Table Creation): Confirm if
system_workflow_form_definitionsandsystem_workflow_task_definitionstables are created by this migration or a preceding one. Adjustup/downaccordingly. - Task 6 (Base Form Registration): Decide if the "base generic forms" should themselves be registered as separate entries in
system_workflow_form_definitionsor if they only exist as JS objects for composition within this migration. - Task 7 (Form Name "qbo-mapping-error-form-specialized"): Clarify if "qbo-mapping-error-form-specialized" is a distinct form or if it refers to the general "qbo-mapping-error-form" which itself is composed. The task data implies "qbo-mapping-error-form" is the one being used and is composed. Ensure naming consistency.
- Task 8 (Verify
qboInvoiceSyncWorkflow.ts): Confirm thatqboInvoiceSyncWorkflow.tscorrectly uses thetaskTypewhen callingcreate_human_task, and that thesetaskTypes will match thetask_typeentries created insystem_workflow_task_definitionsby this migration. NoformIdparameter should be passed from the workflow.
V. Conceptual Code for Migration (Illustrative)
// server/migrations/20250509175818_add_qbo_invoice_sync_forms.cjs
// Helper function for merging/composing schemas (simplified example)
function composeSchema(baseJsonSchema, extensionJsonSchema) {
// ... implementation ...
}
function composeUiSchema(baseUiSchema = {}, extensionUiSchema = {}) {
// ... implementation ...
}
function composeDefaultValues(baseDefaults = {}, extensionDefaults = {}) {
// ... implementation ...
}
exports.up = async function(knex) {
await knex.transaction(async (trx) => {
// TODO: Implement table creation for system_workflow_form_definitions if needed (Task 5)
// TODO: Implement table creation for system_workflow_task_definitions if needed (Task 5)
// --- Define Base Schemas (Task 1) ---
const baseQboMappingErrorForm = { jsonSchema: {/*...*/}, uiSchema: {/*...*/}, defaultValues: {/*...*/} };
// ... other base forms ...
// --- Define Specialized Form Extensions & Register (Task 2) ---
const specializedFormsData = [
{
name: 'qbo-customer-mapping-lookup-error-form',
taskType: 'qbo_customer_mapping_lookup_error',
baseSchemaRef: baseQboMappingErrorForm,
extension: { jsonSchema: {/*...*/}, uiSchema: {/*...*/}, defaultValues: {/*...*/} },
description: 'Form for QBO customer mapping lookup errors.'
},
// ... other 9 forms ...
];
for (const formData of specializedFormsData) {
const finalJsonSchema = composeSchema(formData.baseSchemaRef.jsonSchema, formData.extension.jsonSchema);
const finalUiSchema = composeUiSchema(formData.baseSchemaRef.uiSchema, formData.extension.uiSchema);
const finalDefaultValues = composeDefaultValues(formData.baseSchemaRef.defaultValues, formData.extension.defaultValues);
await trx('system_workflow_form_definitions').insert({
name: formData.name,
version: '1.0', status: 'ACTIVE', description: formData.description,
json_schema: finalJsonSchema,
ui_schema: finalUiSchema,
default_values: finalDefaultValues,
// created_by: (Task 3)
created_at: new Date(), updatedAt: new Date()
});
await trx('system_workflow_task_definitions').insert({
task_type: formData.taskType,
name: `Handle ${formData.taskType}`, description: formData.description,
form_id: formData.name,
form_type: 'system',
// created_by: (Task 3)
created_at: new Date(), updatedAt: new Date()
}).onConflict('task_type').merge();
}
// TODO: Decide on base form registration (Task 6)
});
};
exports.down = async function(knex) {
await knex.transaction(async (trx) => {
// ... implementation based on specializedFormsData names and taskTypes ...
});
};