308 lines
7.5 KiB
TypeScript
308 lines
7.5 KiB
TypeScript
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<ApprovalLevelAttributes, 'levelId' | 'levelName' | 'levelStartTime' | 'levelEndTime' | 'actionDate' | 'comments' | 'rejectionReason' | 'breachReason' | 'tat50AlertSent' | 'tat75AlertSent' | 'tatBreached' | 'tatStartTime' | 'tatDays' | 'isPaused' | 'pausedAt' | 'pausedBy' | 'pauseReason' | 'pauseResumeDate' | 'pauseTatStartTime' | 'pauseElapsedHours' | 'createdAt' | 'updatedAt'> {}
|
|
|
|
class ApprovalLevel extends Model<ApprovalLevelAttributes, ApprovalLevelCreationAttributes> 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 };
|