import { DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from '@config/database'; import { User } from './User'; import { WorkflowRequest } from './WorkflowRequest'; import { ApprovalStatus } from '../types/common.types'; interface ApprovalLevelAttributes { levelId: string; requestId: string; levelNumber: number; levelName?: string; approverId: string; approverEmail: string; approverName: string; tatHours: number; tatDays: number; status: ApprovalStatus; levelStartTime?: Date; levelEndTime?: Date; actionDate?: Date; comments?: string; rejectionReason?: string; breachReason?: string; isFinalApprover: boolean; elapsedHours: number; remainingHours: number; tatPercentageUsed: number; tat50AlertSent: boolean; tat75AlertSent: boolean; tatBreached: boolean; tatStartTime?: Date; isPaused: boolean; pausedAt?: Date; pausedBy?: string; pauseReason?: string; pauseResumeDate?: Date; pauseTatStartTime?: Date; pauseElapsedHours?: number; createdAt: Date; updatedAt: Date; } interface ApprovalLevelCreationAttributes extends Optional {} class ApprovalLevel extends Model implements ApprovalLevelAttributes { public levelId!: string; public requestId!: string; public levelNumber!: number; public levelName?: string; public approverId!: string; public approverEmail!: string; public approverName!: string; public tatHours!: number; public tatDays!: number; public status!: ApprovalStatus; public levelStartTime?: Date; public levelEndTime?: Date; public actionDate?: Date; public comments?: string; public rejectionReason?: string; public breachReason?: string; public isFinalApprover!: boolean; public elapsedHours!: number; public remainingHours!: number; public tatPercentageUsed!: number; public tat50AlertSent!: boolean; public tat75AlertSent!: boolean; public tatBreached!: boolean; public tatStartTime?: Date; public isPaused!: boolean; public pausedAt?: Date; public pausedBy?: string; public pauseReason?: string; public pauseResumeDate?: Date; public pauseTatStartTime?: Date; public pauseElapsedHours?: number; public createdAt!: Date; public updatedAt!: Date; // Associations public request?: WorkflowRequest; public approver?: User; } ApprovalLevel.init( { levelId: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, field: 'level_id' }, requestId: { type: DataTypes.UUID, allowNull: false, field: 'request_id', references: { model: 'workflow_requests', key: 'request_id' } }, levelNumber: { type: DataTypes.INTEGER, allowNull: false, field: 'level_number' }, levelName: { type: DataTypes.STRING(100), allowNull: true, field: 'level_name' }, approverId: { type: DataTypes.UUID, allowNull: false, field: 'approver_id', references: { model: 'users', key: 'user_id' } }, approverEmail: { type: DataTypes.STRING(255), allowNull: false, field: 'approver_email' }, approverName: { type: DataTypes.STRING(200), allowNull: false, field: 'approver_name' }, tatHours: { type: DataTypes.DECIMAL(10, 2), allowNull: false, field: 'tat_hours' }, tatDays: { type: DataTypes.INTEGER, 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', 'PAUSED'), defaultValue: 'PENDING' }, levelStartTime: { type: DataTypes.DATE, allowNull: true, field: 'level_start_time' }, levelEndTime: { type: DataTypes.DATE, allowNull: true, field: 'level_end_time' }, actionDate: { type: DataTypes.DATE, allowNull: true, field: 'action_date' }, comments: { type: DataTypes.TEXT, allowNull: true }, rejectionReason: { type: DataTypes.TEXT, allowNull: true, field: 'rejection_reason' }, breachReason: { type: DataTypes.TEXT, allowNull: true, field: 'breach_reason', comment: 'Reason for TAT breach - can contain paragraph-length text' }, isFinalApprover: { type: DataTypes.BOOLEAN, defaultValue: false, field: 'is_final_approver' }, elapsedHours: { type: DataTypes.DECIMAL(10, 2), defaultValue: 0, field: 'elapsed_hours' }, remainingHours: { type: DataTypes.DECIMAL(10, 2), defaultValue: 0, field: 'remaining_hours' }, tatPercentageUsed: { type: DataTypes.DECIMAL(5, 2), defaultValue: 0, field: 'tat_percentage_used' }, tat50AlertSent: { type: DataTypes.BOOLEAN, defaultValue: false, field: 'tat50_alert_sent' }, tat75AlertSent: { type: DataTypes.BOOLEAN, defaultValue: false, field: 'tat75_alert_sent' }, tatBreached: { type: DataTypes.BOOLEAN, defaultValue: false, field: 'tat_breached' }, tatStartTime: { type: DataTypes.DATE, allowNull: true, field: 'tat_start_time' }, isPaused: { type: DataTypes.BOOLEAN, defaultValue: false, field: 'is_paused' }, pausedAt: { type: DataTypes.DATE, allowNull: true, field: 'paused_at' }, pausedBy: { type: DataTypes.UUID, allowNull: true, field: 'paused_by', references: { model: 'users', key: 'user_id' } }, pauseReason: { type: DataTypes.TEXT, allowNull: true, field: 'pause_reason' }, pauseResumeDate: { type: DataTypes.DATE, allowNull: true, field: 'pause_resume_date' }, pauseTatStartTime: { type: DataTypes.DATE, allowNull: true, field: 'pause_tat_start_time' }, pauseElapsedHours: { type: DataTypes.DECIMAL(10, 2), allowNull: true, field: 'pause_elapsed_hours' }, createdAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW, field: 'created_at' }, updatedAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW, field: 'updated_at' } }, { sequelize, modelName: 'ApprovalLevel', tableName: 'approval_levels', timestamps: true, createdAt: 'created_at', updatedAt: 'updated_at', indexes: [ { fields: ['request_id'] }, { fields: ['approver_id'] }, { fields: ['status'] }, { unique: true, fields: ['request_id', 'level_number'] } ] } ); // Associations ApprovalLevel.belongsTo(WorkflowRequest, { as: 'request', foreignKey: 'requestId', targetKey: 'requestId' }); ApprovalLevel.belongsTo(User, { as: 'approver', foreignKey: 'approverId', targetKey: 'userId' }); export { ApprovalLevel };