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
194 lines
6.2 KiB
JavaScript
194 lines
6.2 KiB
JavaScript
/**
|
|
* @param { import("knex").Knex } knex
|
|
* @returns { Promise<void> }
|
|
*/
|
|
exports.up = async function(knex) {
|
|
// 1. Rename description column to title (preserves existing data)
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.renameColumn('description', 'title');
|
|
});
|
|
|
|
// 2. Add new notes column for detailed information
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.text('notes');
|
|
});
|
|
|
|
// 3. Add enhanced time tracking columns
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.timestamp('start_time', { useTz: true });
|
|
table.timestamp('end_time', { useTz: true });
|
|
});
|
|
|
|
// 4. Update CHECK constraints to include 'interaction'
|
|
await knex.raw(`
|
|
ALTER TABLE standard_statuses
|
|
DROP CONSTRAINT IF EXISTS standard_statuses_item_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE standard_statuses
|
|
ADD CONSTRAINT standard_statuses_item_type_check
|
|
CHECK (item_type = ANY (ARRAY['project'::text, 'project_task'::text, 'ticket'::text, 'interaction'::text]))
|
|
`);
|
|
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
DROP CONSTRAINT IF EXISTS statuses_item_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
ADD CONSTRAINT statuses_item_type_check
|
|
CHECK (item_type = ANY (ARRAY['project'::text, 'project_task'::text, 'ticket'::text, 'interaction'::text]))
|
|
`);
|
|
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
DROP CONSTRAINT IF EXISTS statuses_status_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
ADD CONSTRAINT statuses_status_type_check
|
|
CHECK (status_type = ANY (ARRAY['project'::text, 'ticket'::text, 'project_task'::text, 'interaction'::text]))
|
|
`);
|
|
|
|
// 5. Add status_id foreign key to leverage existing status system
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.uuid('status_id');
|
|
});
|
|
// Add composite foreign key constraint
|
|
await knex.raw(`
|
|
ALTER TABLE interactions
|
|
ADD CONSTRAINT interactions_status_fk
|
|
FOREIGN KEY (tenant, status_id)
|
|
REFERENCES statuses (tenant, status_id)
|
|
`);
|
|
|
|
// 6. Add standard interaction statuses
|
|
const tenants = await knex('tenants').select('tenant');
|
|
|
|
for (const tenant of tenants) {
|
|
// First create standard statuses for this tenant
|
|
await knex('standard_statuses').insert([
|
|
{ name: 'Planned', item_type: 'interaction', display_order: 1, tenant: tenant.tenant, is_closed: false },
|
|
{ name: 'In Progress', item_type: 'interaction', display_order: 2, tenant: tenant.tenant, is_closed: false },
|
|
{ name: 'Completed', item_type: 'interaction', display_order: 3, tenant: tenant.tenant, is_closed: true, is_default: true },
|
|
{ name: 'Cancelled', item_type: 'interaction', display_order: 4, tenant: tenant.tenant, is_closed: true }
|
|
]);
|
|
|
|
// Get the standard status IDs
|
|
const standardStatuses = await knex('standard_statuses')
|
|
.where({ tenant: tenant.tenant, item_type: 'interaction' })
|
|
.select('*');
|
|
|
|
// Get a system user for this tenant
|
|
const systemUser = await knex('users')
|
|
.where({ tenant: tenant.tenant })
|
|
.first();
|
|
|
|
if (systemUser) {
|
|
// Create tenant statuses that reference the standard ones
|
|
for (const standardStatus of standardStatuses) {
|
|
await knex('statuses').insert({
|
|
tenant: tenant.tenant,
|
|
name: standardStatus.name,
|
|
status_type: 'interaction',
|
|
order_number: standardStatus.display_order,
|
|
created_by: systemUser.user_id,
|
|
is_closed: standardStatus.is_closed,
|
|
is_default: standardStatus.is_default || false,
|
|
created_at: knex.fn.now()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 7. Set all existing interactions to 'Completed' status (backward compatibility)
|
|
const completedStatuses = await knex('statuses')
|
|
.where({
|
|
status_type: 'interaction',
|
|
name: 'Completed'
|
|
})
|
|
.select('tenant', 'status_id');
|
|
|
|
for (const status of completedStatuses) {
|
|
await knex('interactions')
|
|
.where({ tenant: status.tenant })
|
|
.update({ status_id: status.status_id });
|
|
}
|
|
|
|
// 7. Populate start_time and end_time from existing data where possible
|
|
// Set both start_time and end_time to interaction_date (duration will be 0)
|
|
await knex.raw(`
|
|
UPDATE interactions
|
|
SET
|
|
start_time = interaction_date,
|
|
end_time = interaction_date
|
|
WHERE interaction_date IS NOT NULL
|
|
`);
|
|
};
|
|
|
|
/**
|
|
* @param { import("knex").Knex } knex
|
|
* @returns { Promise<void> }
|
|
*/
|
|
exports.down = async function(knex) {
|
|
// Remove the composite foreign key constraint first
|
|
await knex.raw(`
|
|
ALTER TABLE interactions
|
|
DROP CONSTRAINT IF EXISTS interactions_status_fk
|
|
`);
|
|
|
|
// Remove the new columns
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.dropColumn('status_id');
|
|
table.dropColumn('start_time');
|
|
table.dropColumn('end_time');
|
|
table.dropColumn('notes');
|
|
});
|
|
|
|
// Rename title back to description
|
|
await knex.schema.alterTable('interactions', (table) => {
|
|
table.renameColumn('title', 'description');
|
|
});
|
|
|
|
// Remove interaction statuses
|
|
await knex('statuses')
|
|
.where({ status_type: 'interaction' })
|
|
.delete();
|
|
|
|
// Remove standard interaction statuses
|
|
await knex('standard_statuses')
|
|
.where({ item_type: 'interaction' })
|
|
.delete();
|
|
|
|
// Restore original CHECK constraints
|
|
await knex.raw(`
|
|
ALTER TABLE standard_statuses
|
|
DROP CONSTRAINT IF EXISTS standard_statuses_item_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE standard_statuses
|
|
ADD CONSTRAINT standard_statuses_item_type_check
|
|
CHECK (item_type = ANY (ARRAY['project'::text, 'project_task'::text, 'ticket'::text]))
|
|
`);
|
|
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
DROP CONSTRAINT IF EXISTS statuses_item_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
ADD CONSTRAINT statuses_item_type_check
|
|
CHECK (item_type = ANY (ARRAY['project'::text, 'project_task'::text, 'ticket'::text]))
|
|
`);
|
|
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
DROP CONSTRAINT IF EXISTS statuses_status_type_check
|
|
`);
|
|
await knex.raw(`
|
|
ALTER TABLE statuses
|
|
ADD CONSTRAINT statuses_status_type_check
|
|
CHECK (status_type = ANY (ARRAY['project'::text, 'ticket'::text, 'project_task'::text]))
|
|
`);
|
|
};
|