PSA/server/migrations/20250413032627_add_billing_plan_fixed_config_table.cjs
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

143 lines
5.1 KiB
JavaScript

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = async function(knex) { // Changed to async function
console.log('Starting migration: add_billing_plan_fixed_config_table...');
// Step 1: Create the table
await knex.schema.withSchema('public').createTable('billing_plan_fixed_config', (table) => {
table.uuid('tenant').notNullable();
table.uuid('plan_id').notNullable();
table.decimal('base_rate', 19, 2).nullable(); // Add base_rate for the plan's fixed price
table.boolean('enable_proration').notNullable().defaultTo(false);
table.string('billing_cycle_alignment', 20).notNullable().defaultTo('start'); // Match existing type/length
table.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
table.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now());
table.primary(['tenant', 'plan_id']);
table.foreign(['tenant', 'plan_id'])
.references(['tenant', 'plan_id'])
.inTable('public.billing_plans')
.onDelete('CASCADE');
});
console.log('Table billing_plan_fixed_config created.');
// Step 2: Populate the newly created table (logic moved from ...32935...)
console.log('Checking for inconsistent fixed plan service configurations...');
// Select relevant data from plan_service_fixed_config for fixed plans, joining through plan_service_configuration
const serviceConfigs = await knex('plan_service_fixed_config as psfc')
.join(
'plan_service_configuration as psc',
function () {
this.on('psfc.config_id', '=', 'psc.config_id').andOn(
'psfc.tenant',
'=',
'psc.tenant',
);
},
)
.join('billing_plans as bp', function () {
this.on('psc.plan_id', '=', 'bp.plan_id').andOn('psc.tenant', '=', 'bp.tenant');
})
.where('bp.plan_type', 'Fixed')
.select(
'psc.plan_id', // Select plan_id from psc
'psfc.tenant',
'psfc.enable_proration',
'psfc.billing_cycle_alignment',
'psfc.base_rate' // Select base_rate from service config for migration
);
// Group configurations by plan_id and tenant in memory
const groupedConfigs = serviceConfigs.reduce(
(acc, config) => {
const key = `${config.tenant}::${config.plan_id}`;
if (!acc[key]) {
acc[key] = {
plan_id: config.plan_id,
tenant: config.tenant,
configs: new Set(),
};
}
acc[key].configs.add(
JSON.stringify({
enable_proration: config.enable_proration,
billing_cycle_alignment: config.billing_cycle_alignment,
base_rate: config.base_rate // Include base_rate in inconsistency check
}),
);
return acc;
},
{},
);
let inconsistentCount = 0;
// Check for inconsistencies within each group
for (const key in groupedConfigs) {
const group = groupedConfigs[key];
if (group.configs.size > 1) {
inconsistentCount++;
console.warn(
`INCONSISTENCY DETECTED: Contract Line (tenant: ${group.tenant}, plan_id: ${group.plan_id}) has multiple different (enable_proration, billing_cycle_alignment, base_rate) settings across its services. The first service's setting will be used for migration.`,
);
}
}
if (inconsistentCount === 0) {
console.log('No inconsistencies found.');
} else {
console.log(
`Detected ${inconsistentCount} fixed plans with inconsistent service configurations.`,
);
}
console.log('Populating billing_plan_fixed_config table...');
const insertQuery = `
INSERT INTO billing_plan_fixed_config (plan_id, tenant, base_rate, enable_proration, billing_cycle_alignment, created_at, updated_at)
SELECT DISTINCT ON (bp.tenant, bp.plan_id)
bp.plan_id as plan_id,
bp.tenant,
psfc.base_rate, -- Select the base_rate from the first service config
psfc.enable_proration,
psfc.billing_cycle_alignment,
NOW() as created_at,
NOW() as updated_at
FROM
billing_plans bp
JOIN
plan_service_configuration psc ON bp.plan_id = psc.plan_id AND bp.tenant = psc.tenant
JOIN
plan_service_fixed_config psfc ON psc.config_id = psfc.config_id AND psc.tenant = psfc.tenant
WHERE
bp.plan_type = 'Fixed'
ORDER BY
bp.tenant, bp.plan_id, psfc.created_at ASC
ON CONFLICT (tenant, plan_id) DO NOTHING;
`;
try {
const result = await knex.raw(insertQuery);
console.log(
`Successfully populated billing_plan_fixed_config. Rows affected (may be 0 if plans already existed or no fixed plans): ${result.rowCount === null ? 'N/A (Check DB)' : result.rowCount}`,
);
} catch (error) {
console.error(
'Error populating billing_plan_fixed_config:',
error instanceof Error ? error.message : error,
);
// Re-throw the error to fail the migration
throw error;
}
console.log('Finished migration: add_billing_plan_fixed_config_table (including data population).');
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function(knex) {
return knex.schema.withSchema('public').dropTableIfExists('billing_plan_fixed_config');
};