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
205 lines
7.3 KiB
JavaScript
205 lines
7.3 KiB
JavaScript
/**
|
|
* Migration to transfer data from plan_services to the new plan_service_configuration tables
|
|
* This script follows CitusDB compatibility requirements:
|
|
* - Process records in batches by tenant
|
|
* - Use transactions to ensure data consistency
|
|
* - Always include tenant in WHERE clauses
|
|
*/
|
|
exports.up = async function(knex) {
|
|
// Get all tenants
|
|
const tenants = await knex('tenants').select('tenant');
|
|
|
|
// Process each tenant separately
|
|
for (const { tenant } of tenants) {
|
|
console.log(`Migrating plan services for tenant: ${tenant}`);
|
|
|
|
// Get all contract lines for this tenant
|
|
const plans = await knex('billing_plans')
|
|
.where({ tenant })
|
|
.select('*');
|
|
|
|
// Process each plan
|
|
for (const plan of plans) {
|
|
await knex.transaction(async (trx) => {
|
|
// Get services for this plan
|
|
const services = await trx('plan_services')
|
|
.where({
|
|
'plan_id': plan.plan_id,
|
|
'tenant': tenant
|
|
})
|
|
.select('*');
|
|
|
|
// Skip if no services found
|
|
if (!services || services.length === 0) {
|
|
console.log(`No services found for plan ${plan.plan_name} (${plan.plan_id})`);
|
|
return;
|
|
}
|
|
|
|
console.log(`Migrating ${services.length} services for plan ${plan.plan_name} (${plan.plan_id})`);
|
|
|
|
// Create configurations for each service
|
|
for (const service of services) {
|
|
// Get service details to determine appropriate configuration type
|
|
const serviceDetails = await trx('service_catalog')
|
|
.where({
|
|
'service_id': service.service_id,
|
|
'tenant': tenant
|
|
})
|
|
.first();
|
|
|
|
if (!serviceDetails) {
|
|
console.log(`Service ${service.service_id} not found, skipping`);
|
|
continue;
|
|
}
|
|
|
|
// Determine configuration type based on plan type and service type
|
|
let configurationType;
|
|
|
|
// Map plan_type to configuration_type
|
|
switch (plan.plan_type) {
|
|
case 'Fixed':
|
|
configurationType = 'Fixed';
|
|
break;
|
|
case 'Hourly':
|
|
configurationType = 'Hourly';
|
|
break;
|
|
case 'Usage':
|
|
configurationType = 'Usage';
|
|
break;
|
|
case 'Bucket':
|
|
configurationType = 'Bucket';
|
|
break;
|
|
default:
|
|
// Default to Fixed if unknown
|
|
configurationType = 'Fixed';
|
|
}
|
|
|
|
// Insert base configuration
|
|
const result = await trx('plan_service_configuration')
|
|
.insert({
|
|
plan_id: plan.plan_id,
|
|
service_id: service.service_id,
|
|
configuration_type: configurationType,
|
|
custom_rate: service.custom_rate,
|
|
quantity: service.quantity,
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
})
|
|
.returning('config_id');
|
|
|
|
// Extract the config_id value from the result
|
|
const configId = result[0].config_id || result[0];
|
|
|
|
// Insert type-specific configuration
|
|
switch (configurationType) {
|
|
case 'Fixed':
|
|
await trx('plan_service_fixed_config')
|
|
.insert({
|
|
config_id: configId,
|
|
enable_proration: false, // Default value
|
|
billing_cycle_alignment: 'start', // Default value
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
break;
|
|
|
|
case 'Hourly':
|
|
await trx('plan_service_hourly_config')
|
|
.insert({
|
|
config_id: configId,
|
|
minimum_billable_time: 15, // Default value
|
|
round_up_to_nearest: 15, // Default value
|
|
enable_overtime: false, // Default value
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
break;
|
|
|
|
case 'Usage':
|
|
await trx('plan_service_usage_config')
|
|
.insert({
|
|
config_id: configId,
|
|
unit_of_measure: 'Unit', // Default value
|
|
enable_tiered_pricing: false, // Default value
|
|
minimum_usage: 0, // Default value
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
|
|
// Check if there are service rate tiers to migrate
|
|
const serviceTiers = await trx('service_rate_tiers')
|
|
.where({
|
|
'service_id': service.service_id,
|
|
'tenant': tenant
|
|
})
|
|
.select('*');
|
|
|
|
// Migrate service rate tiers if they exist
|
|
if (serviceTiers && serviceTiers.length > 0) {
|
|
// Update usage config to enable tiered pricing
|
|
await trx('plan_service_usage_config')
|
|
.where({
|
|
'config_id': configId,
|
|
'tenant': tenant
|
|
})
|
|
.update({
|
|
enable_tiered_pricing: true,
|
|
updated_at: new Date()
|
|
});
|
|
|
|
// Create plan service rate tiers
|
|
for (const tier of serviceTiers) {
|
|
await trx('plan_service_rate_tiers')
|
|
.insert({
|
|
config_id: configId,
|
|
min_quantity: tier.min_quantity,
|
|
max_quantity: tier.max_quantity,
|
|
rate: tier.rate,
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'Bucket':
|
|
// For bucket plans, we need to get the bucket plan details
|
|
const bucketPlan = await trx('bucket_plans')
|
|
.where({
|
|
'plan_id': plan.plan_id,
|
|
'tenant': tenant
|
|
})
|
|
.first();
|
|
|
|
await trx('plan_service_bucket_config')
|
|
.insert({
|
|
config_id: configId,
|
|
total_hours: bucketPlan ? bucketPlan.total_hours : 0,
|
|
billing_period: bucketPlan ? bucketPlan.billing_period : 'monthly',
|
|
overage_rate: bucketPlan ? bucketPlan.overage_rate : 0,
|
|
allow_rollover: bucketPlan ? bucketPlan.allow_rollover : false,
|
|
tenant: tenant,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
console.log('Data migration completed successfully');
|
|
};
|
|
|
|
exports.down = async function(knex) {
|
|
// This is a data migration, so the down function would be complex
|
|
// and potentially destructive. It's safer to handle rollbacks manually
|
|
// if needed, or restore from a backup.
|
|
console.log('Warning: down migration not implemented for data migration');
|
|
}; |