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
241 lines
13 KiB
JavaScript
241 lines
13 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @param {import('knex').Knex} knex
|
|
*/
|
|
exports.up = async function(knex) {
|
|
await knex.transaction(async (trx) => {
|
|
console.log('Starting consolidated QBO workflow schema migration (UP)...');
|
|
|
|
// --- Phase 0: Clear existing workflow-related tables as per user feedback ---
|
|
// Drop dependent tables first
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_task_history" CASCADE;'); // Drop history first if it depends on workflow_tasks
|
|
console.log('Dropped workflow_task_history table (if it existed) with CASCADE.');
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_tasks" CASCADE;');
|
|
console.log('Dropped workflow_tasks table (if it existed) with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "system_workflow_task_definitions" CASCADE;'); // Has FK to system_workflow_form_definitions
|
|
console.log('Dropped system_workflow_task_definitions table (if it existed) with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "system_workflow_form_definitions" CASCADE;');
|
|
console.log('Dropped system_workflow_form_definitions table (if it existed) with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_task_definitions" CASCADE;'); // Tenant-specific definitions
|
|
console.log('Dropped workflow_task_definitions (tenant-specific) table (if it existed).');
|
|
|
|
// --- Phase 1: Create and Populate System Workflow Tables ---
|
|
|
|
// 1.A. Create system_workflow_form_definitions table
|
|
await trx.schema.createTable('system_workflow_form_definitions', (table) => {
|
|
table.uuid('definition_id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
|
table.text('name').unique().notNullable();
|
|
table.text('description');
|
|
table.text('version').notNullable().defaultTo('1.0');
|
|
table.text('status').notNullable().defaultTo('ACTIVE');
|
|
table.text('category');
|
|
table.specificType('tags', 'TEXT[]');
|
|
table.jsonb('json_schema').notNullable();
|
|
table.jsonb('ui_schema');
|
|
table.jsonb('default_values');
|
|
table.uuid('created_by').nullable(); // System records might not have a user creator
|
|
table.timestamps(true, true);
|
|
});
|
|
console.log('Recreated system_workflow_form_definitions table.');
|
|
|
|
// Define all form schemas as named objects
|
|
// Enhanced qbo-mapping-error-form schema (from original qbo-item-mapping-missing-form)
|
|
const enhancedQboMappingErrorFormSchema = {
|
|
json_schema: {
|
|
"type": "object",
|
|
"properties": {
|
|
"instructions": { "type": "string", "title": "Action Required", "description": "Please follow these steps to resolve the missing product mapping:", "readOnly": true },
|
|
"quickbooksSetupLink": { "type": "string", "title": "QuickBooks Integration Setup", "format": "uri", "description": "Click here to open the QuickBooks integration setup page" },
|
|
"productDetails": { "type": "string", "title": "Product Details", "description": "Create a mapping for this product in QuickBooks", "readOnly": true },
|
|
},
|
|
"required": []
|
|
},
|
|
ui_schema: {
|
|
"instructions": { "ui:widget": "AlertWidget", "ui:options": { "alertType": "info" } },
|
|
"quickbooksSetupLink": { "ui:widget": "ButtonLinkWidget", "ui:options": { "buttonText": "Go to QuickBooks Integration Setup", "target": "_blank" } },
|
|
"productDetails": { "ui:widget": "HighlightWidget" },
|
|
"ui:order": ["instructions", "productDetails", "quickbooksSetupLink"]
|
|
},
|
|
default_values: {
|
|
"instructions": "Action Required: Please create a mapping in QuickBooks for product '${contextData.service_name}' from company '${contextData.company_name}'. This mapping is required before the invoice can be synced.",
|
|
"quickbooksSetupLink": "/settings/integrations/quickbooks/${contextData.tenant_id}/${contextData.realm_id}/mappings",
|
|
"productDetails": "Product: ${contextData.service_name} (ID: ${contextData.alga_service_id})\nCompany: ${contextData.company_name} (ID: ${contextData.alga_company_id})\n\nThis product needs to be mapped to a corresponding QuickBooks item. Please go to the QuickBooks Integration Setup page using the button below and create this mapping.",
|
|
}
|
|
};
|
|
|
|
const genericNeedsAttentionForm = {
|
|
json_schema: {
|
|
type: "object",
|
|
properties: {
|
|
message: { type: "string", title: "Message", readOnly: true },
|
|
alertType: {
|
|
type: "string",
|
|
title: "Alert Type",
|
|
enum: ["info", "warning", "error", "success"],
|
|
default: "error"
|
|
}
|
|
},
|
|
required: ["message"]
|
|
},
|
|
ui_schema: {
|
|
message: {
|
|
"ui:widget": "AlertWidget",
|
|
"ui:options": {
|
|
"alertType": "error"
|
|
}
|
|
},
|
|
alertType: {
|
|
"ui:widget": "hidden"
|
|
}
|
|
},
|
|
default_values: {
|
|
message: "An issue needs your attention. \n\n${contextData.message}",
|
|
alertType: "error"
|
|
}
|
|
}
|
|
|
|
const formsToInsert = [
|
|
{ name: 'qbo-mapping-error-form', description: 'Generic form for QBO mapping errors (customer or item).', ...enhancedQboMappingErrorFormSchema },
|
|
{ name: 'generic-workflow-error-form', description: 'Form for internal workflow errors.', ...genericNeedsAttentionForm },
|
|
];
|
|
for (const form of formsToInsert) {
|
|
await trx('system_workflow_form_definitions').insert({ ...form, version: '1.0', status: 'ACTIVE', created_by: null, created_at: new Date(),updated_at: new Date() });
|
|
}
|
|
console.log('Populated system_workflow_form_definitions table.');
|
|
|
|
// 1.B. Create system_workflow_task_definitions table
|
|
await trx.schema.createTable('system_workflow_task_definitions', (table) => {
|
|
table.text('task_type').primary();
|
|
table.text('name').notNullable();
|
|
table.text('description');
|
|
table.text('form_id').notNullable();
|
|
table.foreign('form_id').references('name').inTable('system_workflow_form_definitions').onDelete('RESTRICT').onUpdate('CASCADE');
|
|
table.text('form_type').notNullable().defaultTo('system');
|
|
table.text('default_priority');
|
|
table.integer('default_sla_days');
|
|
table.uuid('created_by').nullable();
|
|
table.timestamps(true, true);
|
|
});
|
|
console.log('Recreated system_workflow_task_definitions table.');
|
|
|
|
const tasksToInsert = [
|
|
{ task_type: 'qbo_mapping_error', name: 'Handle QBO Mapping Error', description: 'Generic form for QBO mapping errors (customer or item).', form_id: 'qbo-mapping-error-form' },
|
|
{ task_type: 'workflow_error', name: 'Handle Workflow Error', description: 'Form for workflow errors.', form_id: 'generic-workflow-error-form' },
|
|
];
|
|
for (const task of tasksToInsert) {
|
|
await trx('system_workflow_task_definitions').insert({ ...task, form_type: 'system', created_by: null, created_at: new Date(), updated_at: new Date() });
|
|
}
|
|
console.log('Populated system_workflow_task_definitions table.');
|
|
|
|
// --- Phase 2: Create Tenant-Specific and Instance Tables (Fresh) ---
|
|
// 2.A. Create workflow_task_definitions table (tenant-specific)
|
|
await trx.schema.createTable('workflow_task_definitions', (table) => {
|
|
table.uuid('task_definition_id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
|
table.text('tenant').notNullable(); // Changed from tenant_id (UUID) to tenant (TEXT)
|
|
// Example: table.foreign('tenant').references('id_string_column').inTable('tenants').onDelete('CASCADE');
|
|
table.text('name').notNullable();
|
|
table.text('description');
|
|
table.text('form_id'); // Can reference a system_workflow_form_definitions.name or a tenant-specific form ID
|
|
table.text('form_type').notNullable().defaultTo('tenant'); // 'tenant' or 'system'
|
|
table.text('default_priority');
|
|
table.integer('default_sla_days');
|
|
table.uuid('created_by'); // User who created this tenant-specific definition
|
|
table.timestamps(true, true);
|
|
table.unique(['tenant', 'name']); // Ensure name is unique per tenant
|
|
});
|
|
console.log('Recreated workflow_task_definitions (tenant-specific) table.');
|
|
|
|
// 2.B. Create workflow_tasks table
|
|
await trx.schema.createTable('workflow_tasks', (table) => {
|
|
table.uuid('task_id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
|
table.text('tenant').notNullable(); // Changed from tenant_id (UUID) to tenant (TEXT)
|
|
// Example: table.foreign('tenant').references('id_string_column').inTable('tenants').onDelete('CASCADE');
|
|
table.uuid('execution_id').notNullable();
|
|
// Example: table.foreign('execution_id').references('execution_id').inTable('workflow_executions').onDelete('CASCADE');
|
|
|
|
table.text('task_definition_type').notNullable(); // 'system' or 'tenant'
|
|
|
|
table.text('system_task_definition_task_type').nullable();
|
|
table.foreign('system_task_definition_task_type', 'fk_wt_system_task_def_type') // Named FK
|
|
.references('task_type').inTable('system_workflow_task_definitions')
|
|
.onDelete('SET NULL').onUpdate('CASCADE');
|
|
|
|
table.uuid('tenant_task_definition_id').nullable();
|
|
table.foreign('tenant_task_definition_id', 'fk_wt_tenant_task_def_id') // Named FK
|
|
.references('task_definition_id').inTable('workflow_task_definitions')
|
|
.onDelete('SET NULL').onUpdate('CASCADE');
|
|
|
|
table.text('title').notNullable();
|
|
table.text('description').nullable();
|
|
table.uuid('event_id').nullable(); // Added event_id
|
|
table.jsonb('context_data').nullable(); // Renamed from 'data'
|
|
table.text('status').notNullable();
|
|
table.text('priority').nullable(); // Renamed from 'priority_override', changed type
|
|
table.jsonb('assigned_roles').nullable();
|
|
table.jsonb('assigned_users').nullable();
|
|
table.timestamp('due_date').nullable(); // Renamed from 'due_at'
|
|
table.timestamp('completed_at').nullable();
|
|
table.jsonb('response_data').nullable();
|
|
table.uuid('created_by').nullable();
|
|
// table.foreign('created_by').references('user_id').inTable('users'); // Example if users table exists
|
|
|
|
table.timestamps(true, true);
|
|
|
|
table.check(`
|
|
(task_definition_type = 'tenant' AND tenant_task_definition_id IS NOT NULL AND system_task_definition_task_type IS NULL) OR
|
|
(task_definition_type = 'system' AND system_task_definition_task_type IS NOT NULL AND tenant_task_definition_id IS NULL)
|
|
`, [], 'chk_task_def_type_consistency');
|
|
});
|
|
console.log('Recreated workflow_tasks table.');
|
|
|
|
// 2.C. Create workflow_task_history table
|
|
await trx.schema.createTable('workflow_task_history', (table) => {
|
|
table.uuid('history_id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
|
table.uuid('task_id').notNullable();
|
|
table.foreign('task_id').references('task_id').inTable('workflow_tasks').onDelete('CASCADE');
|
|
table.text('tenant').notNullable(); // Changed from tenant_id (UUID) to tenant (TEXT)
|
|
// Example: table.foreign('tenant').references('id_string_column').inTable('tenants').onDelete('CASCADE');
|
|
table.text('action').notNullable();
|
|
table.text('from_status').nullable();
|
|
table.text('to_status').nullable();
|
|
table.uuid('user_id').nullable();
|
|
// Example: table.foreign('user_id').references('user_id').inTable('users');
|
|
table.timestamp('timestamp', { useTz: true }).notNullable().defaultTo(knex.fn.now());
|
|
table.jsonb('details').nullable();
|
|
table.timestamps(true, true); // Adds created_at and updated_at
|
|
});
|
|
console.log('Recreated workflow_task_history table.');
|
|
|
|
console.log('Consolidated QBO workflow schema migration (UP) complete.');
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {import('knex').Knex} knex
|
|
*/
|
|
exports.down = async function(knex) {
|
|
await knex.transaction(async (trx) => {
|
|
console.log('Starting consolidated QBO workflow schema migration (DOWN)...');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_task_history" CASCADE;');
|
|
console.log('Dropped workflow_task_history table (if it existed) with CASCADE.');
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_tasks" CASCADE;');
|
|
console.log('Dropped workflow_tasks table with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "workflow_task_definitions" CASCADE;'); // Tenant-specific
|
|
console.log('Dropped workflow_task_definitions (tenant-specific) table with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "system_workflow_task_definitions" CASCADE;');
|
|
console.log('Dropped system_workflow_task_definitions table with CASCADE.');
|
|
|
|
await trx.raw('DROP TABLE IF EXISTS "system_workflow_form_definitions" CASCADE;');
|
|
console.log('Dropped system_workflow_form_definitions table with CASCADE.');
|
|
|
|
console.log('Consolidated QBO workflow schema migration (DOWN) complete.');
|
|
});
|
|
};
|