migration changes donde for create request in approval table

This commit is contained in:
laxmanhalaki 2025-11-05 18:08:51 +05:30
parent cd6a71b804
commit 84bf6e3dfc
4 changed files with 87 additions and 6 deletions

View File

@ -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

View File

@ -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<void> {
// 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<void> {
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');
}

View File

@ -32,7 +32,7 @@ interface ApprovalLevelAttributes {
updatedAt: Date;
}
interface ApprovalLevelCreationAttributes extends Optional<ApprovalLevelAttributes, 'levelId' | 'levelName' | 'levelStartTime' | 'levelEndTime' | 'actionDate' | 'comments' | 'rejectionReason' | 'tat50AlertSent' | 'tat75AlertSent' | 'tatBreached' | 'tatStartTime' | 'createdAt' | 'updatedAt'> {}
interface ApprovalLevelCreationAttributes extends Optional<ApprovalLevelAttributes, 'levelId' | 'levelName' | 'levelStartTime' | 'levelEndTime' | 'actionDate' | 'comments' | 'rejectionReason' | 'tat50AlertSent' | 'tat75AlertSent' | 'tatBreached' | 'tatStartTime' | 'tatDays' | 'createdAt' | 'updatedAt'> {}
class ApprovalLevel extends Model<ApprovalLevelAttributes, ApprovalLevelCreationAttributes> 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'),

View File

@ -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,