From 84bf6e3dfc75d676f0ac61cc7e9ab44672be8962 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Wed, 5 Nov 2025 18:08:51 +0530 Subject: [PATCH] migration changes donde for create request in approval table --- env.example | 5 +- .../2025110501-alter-tat-days-to-generated.ts | 76 +++++++++++++++++++ src/models/ApprovalLevel.ts | 6 +- src/services/workflow.service.ts | 6 +- 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 src/migrations/2025110501-alter-tat-days-to-generated.ts diff --git a/env.example b/env.example index b8ccb3e..45c06c0 100644 --- a/env.example +++ b/env.example @@ -51,7 +51,10 @@ AI_MAX_TOKENS=500 LOG_LEVEL=info LOG_FILE_PATH=./logs -# CORS +# CORS - Comma-separated list of allowed origins +# Local: http://localhost:3000 +# Production: https://your-frontend-domain.com +# Multiple: http://localhost:3000,http://localhost:5173,https://your-frontend-domain.com CORS_ORIGIN=http://localhost:3000 # Rate Limiting diff --git a/src/migrations/2025110501-alter-tat-days-to-generated.ts b/src/migrations/2025110501-alter-tat-days-to-generated.ts new file mode 100644 index 0000000..7b6e66b --- /dev/null +++ b/src/migrations/2025110501-alter-tat-days-to-generated.ts @@ -0,0 +1,76 @@ +import { QueryInterface } from 'sequelize'; + +/** + * Migration: Convert tat_days to GENERATED STORED column + * + * This ensures tat_days is auto-calculated from tat_hours across all environments. + * Production already has this as a generated column, this migration makes other environments consistent. + */ +export async function up(queryInterface: QueryInterface): Promise { + // Check if tat_days is already a generated column + const result = await queryInterface.sequelize.query(` + SELECT + a.attname as column_name, + a.attgenerated as is_generated + FROM pg_attribute a + JOIN pg_class c ON a.attrelid = c.oid + WHERE c.relname = 'approval_levels' + AND a.attname = 'tat_days' + AND NOT a.attisdropped; + `, { type: 'SELECT' }); + + const column = result[0] as any; + + if (column && column.is_generated === 's') { + console.log('✅ tat_days is already a GENERATED STORED column - skipping migration'); + return; + } + + console.log('📝 Converting tat_days to GENERATED STORED column...'); + + // Step 1: Drop the existing regular column + await queryInterface.sequelize.query(` + ALTER TABLE approval_levels DROP COLUMN IF EXISTS tat_days; + `); + + // Step 2: Add it back as a GENERATED STORED column + // Formula: CEIL(tat_hours / 24.0) - rounds up to nearest day + await queryInterface.sequelize.query(` + ALTER TABLE approval_levels + ADD COLUMN tat_days INTEGER + GENERATED ALWAYS AS (CAST(CEIL(tat_hours / 24.0) AS INTEGER)) STORED; + `); + + console.log('✅ tat_days is now a GENERATED STORED column - will auto-calculate from tat_hours'); +} + +export async function down(queryInterface: QueryInterface): Promise { + console.log('⚠️ Rolling back: Converting tat_days from GENERATED to regular column'); + + // Drop the generated column + await queryInterface.sequelize.query(` + ALTER TABLE approval_levels DROP COLUMN IF EXISTS tat_days; + `); + + // Add it back as a regular column (with default calculation for existing rows) + await queryInterface.sequelize.query(` + ALTER TABLE approval_levels + ADD COLUMN tat_days INTEGER; + `); + + // Populate existing rows with calculated values + await queryInterface.sequelize.query(` + UPDATE approval_levels + SET tat_days = CAST(CEIL(tat_hours / 24.0) AS INTEGER) + WHERE tat_days IS NULL; + `); + + // Make it NOT NULL after populating + await queryInterface.sequelize.query(` + ALTER TABLE approval_levels + ALTER COLUMN tat_days SET NOT NULL; + `); + + console.log('✅ Rolled back to regular INTEGER column'); +} + diff --git a/src/models/ApprovalLevel.ts b/src/models/ApprovalLevel.ts index 7302e04..5915603 100644 --- a/src/models/ApprovalLevel.ts +++ b/src/models/ApprovalLevel.ts @@ -32,7 +32,7 @@ interface ApprovalLevelAttributes { updatedAt: Date; } -interface ApprovalLevelCreationAttributes extends Optional {} +interface ApprovalLevelCreationAttributes extends Optional {} class ApprovalLevel extends Model implements ApprovalLevelAttributes { public levelId!: string; @@ -119,8 +119,10 @@ ApprovalLevel.init( }, tatDays: { type: DataTypes.INTEGER, - allowNull: false, + allowNull: true, field: 'tat_days' + // This is a GENERATED STORED column in production DB (calculated as CEIL(tat_hours / 24.0)) + // Database will auto-calculate this value - do NOT pass it during INSERT/UPDATE operations }, status: { type: DataTypes.ENUM('PENDING', 'IN_PROGRESS', 'APPROVED', 'REJECTED', 'SKIPPED'), diff --git a/src/services/workflow.service.ts b/src/services/workflow.service.ts index 22265f7..f4efafb 100644 --- a/src/services/workflow.service.ts +++ b/src/services/workflow.service.ts @@ -306,7 +306,7 @@ export class WorkflowService { approverEmail: email.toLowerCase(), approverName: userName, tatHours, - tatDays: Math.ceil(tatHours / 24), + // tatDays is auto-calculated by database as a generated column status: targetLevel === (workflow as any).currentLevel ? ApprovalStatus.IN_PROGRESS : ApprovalStatus.PENDING, isFinalApprover: targetLevel === allLevels.length + 1, levelStartTime: targetLevel === (workflow as any).currentLevel ? new Date() : null, @@ -702,7 +702,7 @@ export class WorkflowService { approverEmail: levelData.approverEmail, approverName: levelData.approverName, tatHours: levelData.tatHours, - tatDays: calculateTATDays(levelData.tatHours), + // tatDays is auto-calculated by database as a generated column status: ApprovalStatus.PENDING, elapsedHours: 0, remainingHours: levelData.tatHours, @@ -1050,7 +1050,7 @@ export class WorkflowService { approverEmail: levelData.approverEmail, approverName: levelData.approverName, tatHours: levelData.tatHours, - tatDays: calculateTATDays(levelData.tatHours), + // tatDays is auto-calculated by database as a generated column status: ApprovalStatus.PENDING, elapsedHours: 0, remainingHours: levelData.tatHours,