module wise audit tables added

This commit is contained in:
laxman h 2026-04-13 20:41:27 +05:30
parent 6373372eb9
commit 1d885f9d9f
23 changed files with 742 additions and 109 deletions

21
check_logs.ts Normal file
View File

@ -0,0 +1,21 @@
import db from './src/database/models/index.js';
async function checkAuditLogs() {
try {
const resignationLogs = await db.ResignationAudit.findAll({
include: [{ model: db.User, as: 'user', attributes: ['fullName'] }],
limit: 5,
order: [['createdAt', 'DESC']]
});
console.log('--- RECENT RESIGNATION AUDIT LOGS ---');
console.log(JSON.stringify(resignationLogs, null, 2));
process.exit(0);
} catch (error) {
console.error('Error checking logs:', error);
process.exit(1);
}
}
checkAuditLogs();

45
debug-state.cjs Normal file
View File

@ -0,0 +1,45 @@
const db = require('./src/database/models/index.js').default;
const { User, Resignation, FnF } = db;
async function checkState() {
const email = 'ramesh_1776053291272@gmail.com';
const user = await User.findOne({ where: { email } });
console.log('--- USER STATE ---');
if (user) {
console.log(`ID: ${user.id}`);
console.log(`DealerID: ${user.dealerId}`);
console.log(`Status: ${user.status}`);
console.log(`Role: ${user.roleCode}`);
} else {
console.log('User not found');
}
const resignation = await Resignation.findOne({
where: { resignationId: 'RES-2026-7033' }, // From nomenclature or E2E logs if available
// Wait, the id from logs was d4e1a5ed-011d-415d-89b4-bde65cbf5d58
include: ['fnf']
}).catch(() => null);
if (!resignation) {
// Try by UUID
const resByUuid = await Resignation.findByPk('d4e1a5ed-011d-415d-89b4-bde65cbf5d58', { include: ['fnf'] });
console.log('\n--- RESIGNATION STATE ---');
if (resByUuid) {
console.log(`ID: ${resByUuid.id}`);
console.log(`Status: ${resByUuid.status}`);
console.log(`Stage: ${resByUuid.currentStage}`);
console.log(`DealerID: ${resByUuid.dealerId}`);
if (resByUuid.fnf) {
console.log(`FnF ID: ${resByUuid.fnf.id}`);
console.log(`FnF Status: ${resByUuid.fnf.status}`);
}
} else {
console.log('Resignation not found');
}
}
process.exit(0);
}
checkState();

View File

@ -256,7 +256,7 @@ export const FNF_STATUS = {
COMPLETED: 'Completed' COMPLETED: 'Completed'
} as const; } as const;
// F&F Departments (Full list of 16 functional units as per Finance Dashboard) // F&F Departments (Full list of 16 functional units as per Royal Enfield standards)
export const FNF_DEPARTMENTS = [ export const FNF_DEPARTMENTS = [
'Warranty Department', 'Warranty Department',
'Accessories Department', 'Accessories Department',

View File

@ -35,10 +35,12 @@ export class NomenclatureService {
} }
/** /**
* Generates a Settlement/FnF ID (e.g., SET-2026-9012) * Generates a Settlement/FnF ID (e.g., FNF-2025-001)
*/ */
static generateSettlementId() { static generateFnFId() {
return `SET-${new Date().getFullYear()}-${Math.floor(1000 + Math.random() * 9000)}`; const year = new Date().getFullYear();
const rand = Math.floor(1 + Math.random() * 999);
return `FNF-${year}-${rand.toString().padStart(3, '0')}`;
} }
/** /**

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ConstitutionalAuditAttributes {
id: string;
userId: string | null;
constitutionalChangeId: string;
action: string;
details: any | null;
remarks: string | null;
}
export interface ConstitutionalAuditInstance extends Model<ConstitutionalAuditAttributes>, ConstitutionalAuditAttributes { }
export default (sequelize: Sequelize) => {
const ConstitutionalAudit = sequelize.define<ConstitutionalAuditInstance>('ConstitutionalAudit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
constitutionalChangeId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'constitutional_changes',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
details: {
type: DataTypes.JSON,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'constitutional_audit_logs',
timestamps: true,
indexes: [
{ fields: ['constitutionalChangeId'] },
{ fields: ['userId'] },
{ fields: ['action'] }
]
});
(ConstitutionalAudit as any).associate = (models: any) => {
ConstitutionalAudit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
ConstitutionalAudit.belongsTo(models.ConstitutionalChange, { foreignKey: 'constitutionalChangeId', as: 'constitutionalChange' });
};
return ConstitutionalAudit;
};

View File

@ -20,6 +20,7 @@ export interface FnFAttributes {
remarks: string | null; remarks: string | null;
clearanceDocuments: any[]; clearanceDocuments: any[];
progressPercentage: number; progressPercentage: number;
timeline: any[];
} }
export interface FnFInstance extends Model<FnFAttributes>, FnFAttributes { } export interface FnFInstance extends Model<FnFAttributes>, FnFAttributes { }
@ -115,6 +116,10 @@ export default (sequelize: Sequelize) => {
progressPercentage: { progressPercentage: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 0 defaultValue: 0
},
timeline: {
type: DataTypes.JSON,
defaultValue: []
} }
}, { }, {
tableName: 'fnf_settlements', tableName: 'fnf_settlements',

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface FnFAuditAttributes {
id: string;
userId: string | null;
fnfId: string;
action: string;
details: any | null;
remarks: string | null;
}
export interface FnFAuditInstance extends Model<FnFAuditAttributes>, FnFAuditAttributes { }
export default (sequelize: Sequelize) => {
const FnFAudit = sequelize.define<FnFAuditInstance>('FnFAudit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
fnfId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'fnf_settlements',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
details: {
type: DataTypes.JSON,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'fnf_audit_logs',
timestamps: true,
indexes: [
{ fields: ['fnfId'] },
{ fields: ['userId'] },
{ fields: ['action'] }
]
});
(FnFAudit as any).associate = (models: any) => {
FnFAudit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
FnFAudit.belongsTo(models.FnF, { foreignKey: 'fnfId', as: 'fnf' });
};
return FnFAudit;
};

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface RelocationAuditAttributes {
id: string;
userId: string | null;
relocationRequestId: string;
action: string;
details: any | null;
remarks: string | null;
}
export interface RelocationAuditInstance extends Model<RelocationAuditAttributes>, RelocationAuditAttributes { }
export default (sequelize: Sequelize) => {
const RelocationAudit = sequelize.define<RelocationAuditInstance>('RelocationAudit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
relocationRequestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'relocation_requests',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
details: {
type: DataTypes.JSON,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'relocation_audit_logs',
timestamps: true,
indexes: [
{ fields: ['relocationRequestId'] },
{ fields: ['userId'] },
{ fields: ['action'] }
]
});
(RelocationAudit as any).associate = (models: any) => {
RelocationAudit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
RelocationAudit.belongsTo(models.RelocationRequest, { foreignKey: 'relocationRequestId', as: 'relocationRequest' });
};
return RelocationAudit;
};

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface ResignationAuditAttributes {
id: string;
userId: string | null;
resignationId: string;
action: string;
details: any | null;
remarks: string | null;
}
export interface ResignationAuditInstance extends Model<ResignationAuditAttributes>, ResignationAuditAttributes { }
export default (sequelize: Sequelize) => {
const ResignationAudit = sequelize.define<ResignationAuditInstance>('ResignationAudit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
resignationId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'resignations',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
details: {
type: DataTypes.JSON,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'resignation_audit_logs',
timestamps: true,
indexes: [
{ fields: ['resignationId'] },
{ fields: ['userId'] },
{ fields: ['action'] }
]
});
(ResignationAudit as any).associate = (models: any) => {
ResignationAudit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
ResignationAudit.belongsTo(models.Resignation, { foreignKey: 'resignationId', as: 'resignation' });
};
return ResignationAudit;
};

View File

@ -0,0 +1,65 @@
import { Model, DataTypes, Sequelize } from 'sequelize';
export interface TerminationAuditAttributes {
id: string;
userId: string | null;
terminationRequestId: string;
action: string;
details: any | null;
remarks: string | null;
}
export interface TerminationAuditInstance extends Model<TerminationAuditAttributes>, TerminationAuditAttributes { }
export default (sequelize: Sequelize) => {
const TerminationAudit = sequelize.define<TerminationAuditInstance>('TerminationAudit', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'id'
}
},
terminationRequestId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'termination_requests',
key: 'id'
}
},
action: {
type: DataTypes.STRING,
allowNull: false
},
details: {
type: DataTypes.JSON,
allowNull: true
},
remarks: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'termination_audit_logs',
timestamps: true,
indexes: [
{ fields: ['terminationRequestId'] },
{ fields: ['userId'] },
{ fields: ['action'] }
]
});
(TerminationAudit as any).associate = (models: any) => {
TerminationAudit.belongsTo(models.User, { foreignKey: 'userId', as: 'user' });
TerminationAudit.belongsTo(models.TerminationRequest, { foreignKey: 'terminationRequestId', as: 'termination' });
};
return TerminationAudit;
};

View File

@ -33,6 +33,11 @@ import createState from './State.js';
import createTerminationScnResponse from './TerminationScnResponse.js'; import createTerminationScnResponse from './TerminationScnResponse.js';
import createTerminationHearingRecord from './TerminationHearingRecord.js'; import createTerminationHearingRecord from './TerminationHearingRecord.js';
import createFffClearance from './FffClearance.js'; import createFffClearance from './FffClearance.js';
import createResignationAudit from './ResignationAudit.js';
import createTerminationAudit from './TerminationAudit.js';
import createFnFAudit from './FnFAudit.js';
import createConstitutionalAudit from './ConstitutionalAudit.js';
import createRelocationAudit from './RelocationAudit.js';
import createDealerBankDetail from './DealerBankDetail.js'; import createDealerBankDetail from './DealerBankDetail.js';
// Batch 1: Organizational Hierarchy & User Management // Batch 1: Organizational Hierarchy & User Management
@ -147,6 +152,11 @@ db.State = createState(sequelize);
db.TerminationScnResponse = createTerminationScnResponse(sequelize); db.TerminationScnResponse = createTerminationScnResponse(sequelize);
db.TerminationHearingRecord = createTerminationHearingRecord(sequelize); db.TerminationHearingRecord = createTerminationHearingRecord(sequelize);
db.FffClearance = createFffClearance(sequelize); db.FffClearance = createFffClearance(sequelize);
db.ResignationAudit = createResignationAudit(sequelize);
db.TerminationAudit = createTerminationAudit(sequelize);
db.FnFAudit = createFnFAudit(sequelize);
db.ConstitutionalAudit = createConstitutionalAudit(sequelize);
db.RelocationAudit = createRelocationAudit(sequelize);
db.DealerBankDetail = createDealerBankDetail(sequelize); db.DealerBankDetail = createDealerBankDetail(sequelize);
// Batch 1: Organizational Hierarchy & User Management // Batch 1: Organizational Hierarchy & User Management

View File

@ -52,6 +52,8 @@ const ACTION_DESCRIPTIONS: Record<string, string> = {
PAYMENT_UPDATED: 'Payment record updated', PAYMENT_UPDATED: 'Payment record updated',
SECURITY_DEPOSIT_UPDATED: 'Security deposit updated', SECURITY_DEPOSIT_UPDATED: 'Security deposit updated',
FNF_UPDATED: 'F&F settlement updated', FNF_UPDATED: 'F&F settlement updated',
CLEARANCE_UPDATED: 'Departmental clearance response recorded',
STAKEHOLDER_CLEARANCE_UPDATED: 'F&F stakeholder clearance synced',
USER_CREATED: 'User account created', USER_CREATED: 'User account created',
USER_UPDATED: 'User account updated', USER_UPDATED: 'User account updated',
USER_STATUS_CHANGED: 'User status changed', USER_STATUS_CHANGED: 'User status changed',
@ -71,6 +73,7 @@ const ACTION_DESCRIPTIONS: Record<string, string> = {
export const getAuditLogs = async (req: AuthRequest, res: Response) => { export const getAuditLogs = async (req: AuthRequest, res: Response) => {
try { try {
const { entityType, entityId, page = '1', limit = '50' } = req.query; const { entityType, entityId, page = '1', limit = '50' } = req.query;
console.log(`[AuditController] Fetching logs for ${entityType} ID: ${entityId}`);
if (!entityType || !entityId) { if (!entityType || !entityId) {
return res.status(400).json({ return res.status(400).json({
@ -83,36 +86,97 @@ export const getAuditLogs = async (req: AuthRequest, res: Response) => {
const limitNum = Math.min(100, Math.max(1, parseInt(limit as string))); const limitNum = Math.min(100, Math.max(1, parseInt(limit as string)));
const offset = (pageNum - 1) * limitNum; const offset = (pageNum - 1) * limitNum;
const { count, rows: logs } = await AuditLog.findAndCountAll({ let count = 0;
where: { let logs: any[] = [];
entityType: entityType as string,
entityId: entityId as string
},
include: [{
model: User,
as: 'user',
attributes: ['id', 'fullName', 'email']
}],
order: [['createdAt', 'DESC']],
limit: limitNum,
offset
});
// Format the response with human-readable descriptions // Dynamic Table Switching based on Module
// Case-insensitive entity type routing
const type = (entityType as string).toLowerCase();
if (type === 'resignation') {
const result = await db.ResignationAudit.findAndCountAll({
where: { resignationId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
} else if (type === 'termination') {
const result = await db.TerminationAudit.findAndCountAll({
where: { terminationRequestId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
} else if (type === 'fnf') {
const result = await db.FnFAudit.findAndCountAll({
where: { fnfId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
} else if (type === 'constitutional_change') {
const result = await db.ConstitutionalAudit.findAndCountAll({
where: { constitutionalChangeId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
} else if (type === 'relocation') {
const result = await db.RelocationAudit.findAndCountAll({
where: { relocationRequestId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
} else {
console.log(`[AuditController] Falling back to global AuditLog for type: ${type}`);
const result = await db.AuditLog.findAndCountAll({
where: { entityType: entityType as string, entityId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email'] }],
order: [['createdAt', 'DESC']],
limit: limitNum, offset
});
count = result.count;
logs = result.rows;
}
console.log(`[AuditController] Found ${count} logs for ${entityType}`);
// Format the response with human-readable descriptions and consistent mapping
const formattedLogs = logs.map((log: any) => { const formattedLogs = logs.map((log: any) => {
const logData = log.toJSON ? log.toJSON() : log; const logData = log.get ? log.get({ plain: true }) : log;
const details = logData.details || logData.newData;
let baseDescription = ACTION_DESCRIPTIONS[logData.action] ||
logData.action.split('_').map((w: any) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
// EXCLUSIVE ADDITION: Enhance description with context if available (Stage/Status/Dept)
if (details) {
if (details.stage) baseDescription += ` - Stage: ${details.stage}`;
else if (details.department) baseDescription += ` - ${details.department}`;
else if (details.status && logData.action === 'UPDATED') baseDescription += ` to ${details.status}`;
}
return { return {
id: logData.id, id: logData.id,
action: logData.action, action: logData.action,
description: ACTION_DESCRIPTIONS[logData.action] || logData.action, description: baseDescription,
entityType: logData.entityType, entityType: entityType,
entityId: logData.entityId, entityId: entityId,
userName: logData.user?.fullName || 'System', userName: logData.user?.fullName || 'System',
userEmail: logData.user?.email || null, userEmail: logData.user?.email || null,
oldData: logData.oldData, remarks: logData.remarks || logData.newData?.remarks,
newData: logData.newData, newData: details, // Normalize module-specific 'details' to 'newData' for UI
changes: formatChanges(logData.oldData, logData.newData),
ipAddress: logData.ipAddress,
timestamp: logData.createdAt timestamp: logData.createdAt
}; };
}); });
@ -147,25 +211,54 @@ export const getAuditSummary = async (req: AuthRequest, res: Response) => {
}); });
} }
const totalLogs = await AuditLog.count({ let totalLogs = 0;
where: { let latestLog: any = null;
entityType: entityType as string, const type = (entityType as string).toLowerCase();
entityId: entityId as string
}
});
const latestLog = await AuditLog.findOne({ // Dynamic Table Switching
where: { if (type === 'resignation') {
entityType: entityType as string, totalLogs = await db.ResignationAudit.count({ where: { resignationId: entityId as string } });
entityId: entityId as string latestLog = await db.ResignationAudit.findOne({
}, where: { resignationId: entityId as string },
include: [{ include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
model: User, order: [['createdAt', 'DESC']]
as: 'user', });
attributes: ['id', 'fullName'] } else if (type === 'termination') {
}], totalLogs = await db.TerminationAudit.count({ where: { terminationRequestId: entityId as string } });
order: [['createdAt', 'DESC']] latestLog = await db.TerminationAudit.findOne({
}); where: { terminationRequestId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
order: [['createdAt', 'DESC']]
});
} else if (type === 'fnf') {
totalLogs = await db.FnFAudit.count({ where: { fnfId: entityId as string } });
latestLog = await db.FnFAudit.findOne({
where: { fnfId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
order: [['createdAt', 'DESC']]
});
} else if (type === 'constitutional' || type === 'constitutional_change') {
totalLogs = await db.ConstitutionalAudit.count({ where: { constitutionalChangeId: entityId as string } });
latestLog = await db.ConstitutionalAudit.findOne({
where: { constitutionalChangeId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
order: [['createdAt', 'DESC']]
});
} else if (type === 'relocation' || type === 'relocation_request') {
totalLogs = await db.RelocationAudit.count({ where: { relocationRequestId: entityId as string } });
latestLog = await db.RelocationAudit.findOne({
where: { relocationRequestId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
order: [['createdAt', 'DESC']]
});
} else {
totalLogs = await db.AuditLog.count({ where: { entityType: entityType as string, entityId: entityId as string } });
latestLog = await db.AuditLog.findOne({
where: { entityType: entityType as string, entityId: entityId as string },
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
order: [['createdAt', 'DESC']]
});
}
res.json({ res.json({
success: true, success: true,
@ -173,7 +266,7 @@ export const getAuditSummary = async (req: AuthRequest, res: Response) => {
totalEntries: totalLogs, totalEntries: totalLogs,
lastActivity: latestLog ? { lastActivity: latestLog ? {
action: (latestLog as any).action, action: (latestLog as any).action,
description: ACTION_DESCRIPTIONS[(latestLog as any).action] || (latestLog as any).action, description: ACTION_DESCRIPTIONS[(latestLog as any).action] || (latestLog as any).action.split('_').map((w: any) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' '),
user: (latestLog as any).user?.fullName || 'System', user: (latestLog as any).user?.fullName || 'System',
timestamp: (latestLog as any).createdAt timestamp: (latestLog as any).createdAt
} : null } : null

View File

@ -65,11 +65,11 @@ export const createResignation = async (req: AuthRequest, res: Response, next: N
}, { transaction }); }, { transaction });
await outlet.update({ status: 'Pending Resignation' }, { transaction }); await outlet.update({ status: 'Pending Resignation' }, { transaction });
await db.AuditLog.create({ await db.ResignationAudit.create({
userId: req.user.id, userId: req.user.id,
action: AUDIT_ACTIONS.CREATED, action: AUDIT_ACTIONS.CREATED,
entityType: 'resignation', resignationId: resignation.id,
entityId: resignation.id remarks: 'Dealer submitted resignation request'
}, { transaction }); }, { transaction });
await transaction.commit(); await transaction.commit();
@ -265,7 +265,7 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
const dealerProfileId = (resignation as any).dealer?.dealerId; const dealerProfileId = (resignation as any).dealer?.dealerId;
const fnf = await db.FnF.create({ const fnf = await db.FnF.create({
settlementId: NomenclatureService.generateSettlementId(), settlementId: NomenclatureService.generateFnFId(),
resignationId: resignation.id, resignationId: resignation.id,
outletId: resignation.outletId, outletId: resignation.outletId,
dealerId: dealerProfileId, // Correctly using the Dealer model ID dealerId: dealerProfileId, // Correctly using the Dealer model ID
@ -483,6 +483,15 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
}] }]
}, { transaction }); }, { transaction });
// Record module-specific audit
await db.ResignationAudit.create({
userId: req.user.id,
resignationId: resignation.id,
action: 'CLEARANCE_UPDATED',
remarks: remarks || `Cleared ${department}`,
details: { department, status, amount }
}, { transaction });
// Sync with F&F Clearance if settlement exists // Sync with F&F Clearance if settlement exists
const fnf = await db.FnF.findOne({ where: { resignationId: resignation.id } }); const fnf = await db.FnF.findOne({ where: { resignationId: resignation.id } });
if (fnf) { if (fnf) {
@ -520,6 +529,15 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
}, { transaction }); }, { transaction });
} }
// Record F&F specific audit
await db.FnFAudit.create({
userId: req.user.id,
fnfId: fnf.id,
action: 'CLEARANCE_UPDATED',
remarks: remarks || `Departmental clearance recorded for ${department}`,
details: { department, status: fnfStatus, source: 'Resignation Workflow' }
}, { transaction });
// If there's an amount, create/update line item // If there's an amount, create/update line item
if (amount > 0) { if (amount > 0) {
const existingItem = await db.FnFLineItem.findOne({ const existingItem = await db.FnFLineItem.findOne({
@ -594,7 +612,7 @@ export const updateResignationStatus = async (req: AuthRequest, res: Response, n
case 'pushfnf': case 'pushfnf':
// Verify if user role is authorized for manual jump to F&F // Verify if user role is authorized for manual jump to F&F
const authorizedRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN]; const authorizedRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.NBH, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN];
if (!authorizedRoles.includes(req.user.roleCode as any)) { if (!req.user || !authorizedRoles.includes(req.user.roleCode as any)) {
return res.status(403).json({ success: false, message: 'You do not have permission to push this request to F&F' }); return res.status(403).json({ success: false, message: 'You do not have permission to push this request to F&F' });
} }
// Jump directly to F&F Initiation // Jump directly to F&F Initiation

View File

@ -2,7 +2,9 @@ import { Request, Response } from 'express';
import db from '../../database/models/index.js'; import db from '../../database/models/index.js';
const { FinancePayment, FnF, Application, Resignation, User, Outlet, FnFLineItem, TerminationRequest, FffClearance, AuditLog } = db; const { FinancePayment, FnF, Application, Resignation, User, Outlet, FnFLineItem, TerminationRequest, FffClearance, AuditLog } = db;
import { AuthRequest } from '../../types/express.types.js'; import { AuthRequest } from '../../types/express.types.js';
import { FNF_STATUS, AUDIT_ACTIONS, FNF_DEPARTMENTS } from '../../common/config/constants.js'; import { FNF_STATUS, AUDIT_ACTIONS, FNF_DEPARTMENTS, RESIGNATION_STAGES, TERMINATION_STAGES } from '../../common/config/constants.js';
import { ResignationWorkflowService } from '../../services/ResignationWorkflowService.js';
import { TerminationWorkflowService } from '../../services/TerminationWorkflowService.js';
export const getDepartments = async (req: Request, res: Response) => { export const getDepartments = async (req: Request, res: Response) => {
try { try {
@ -92,8 +94,20 @@ export const updateFnF = async (req: AuthRequest, res: Response) => {
newData: { status, netAmount: finalSettlementAmount, remarks } newData: { status, netAmount: finalSettlementAmount, remarks }
}); });
// If status is being set to Completed, we might want to trigger additional logic here // If status is being set to Completed, update the parent request status as well
// like notifying the dealer or updating the resignation status if it's not already if (status === 'Completed' || status === FNF_STATUS.COMPLETED) {
if (fnf.resignationId) {
await Resignation.update(
{ status: 'Completed', stage: 'Completed' },
{ where: { id: fnf.resignationId } }
);
} else if (fnf.terminationRequestId) {
await TerminationRequest.update(
{ status: 'Completed' },
{ where: { id: fnf.terminationRequestId } }
);
}
}
res.json({ success: true, message: 'F&F settlement updated successfully', data: fnf }); res.json({ success: true, message: 'F&F settlement updated successfully', data: fnf });
} catch (error) { } catch (error) {
@ -108,6 +122,7 @@ export const getFnFSettlements = async (req: Request, res: Response) => {
include: [ include: [
{ model: Resignation, as: 'resignation', attributes: ['id', 'resignationId'] }, { model: Resignation, as: 'resignation', attributes: ['id', 'resignationId'] },
{ model: TerminationRequest, as: 'terminationRequest', attributes: ['id', 'status', 'category'] }, { model: TerminationRequest, as: 'terminationRequest', attributes: ['id', 'status', 'category'] },
{ model: db.Dealer, as: 'dealer', attributes: ['legalName', 'businessName', 'id'] },
{ model: Outlet, as: 'outlet', include: [{ model: User, as: 'dealer', attributes: ['fullName', 'id'] }] }, { model: Outlet, as: 'outlet', include: [{ model: User, as: 'dealer', attributes: ['fullName', 'id'] }] },
{ model: FnFLineItem, as: 'lineItems' }, { model: FnFLineItem, as: 'lineItems' },
{ model: FffClearance, as: 'clearances' } { model: FffClearance, as: 'clearances' }
@ -171,7 +186,7 @@ export const addLineItem = async (req: AuthRequest, res: Response) => {
}); });
// Update FnF progress and department statuses // Update FnF progress and department statuses
await calculateFnFLogic(id); await calculateFnFLogic(id as string, req.user?.id);
await AuditLog.create({ await AuditLog.create({
userId: req.user?.id || null, userId: req.user?.id || null,
@ -196,7 +211,8 @@ export const updateLineItem = async (req: AuthRequest, res: Response) => {
await lineItem.update({ description, department, amount }); await lineItem.update({ description, department, amount });
// Update FnF progress and department statuses // Update FnF progress and department statuses
await calculateFnFLogic(lineItem.fnfId); // Update FnF progress and department statuses
await calculateFnFLogic(lineItem.fnfId, req.user?.id);
await AuditLog.create({ await AuditLog.create({
userId: req.user?.id || null, userId: req.user?.id || null,
@ -222,7 +238,8 @@ export const deleteLineItem = async (req: AuthRequest, res: Response) => {
await lineItem.destroy(); await lineItem.destroy();
// Update FnF progress and department statuses // Update FnF progress and department statuses
await calculateFnFLogic(fnfId); // Update FnF progress and department statuses
await calculateFnFLogic(fnfId, req.user?.id);
await AuditLog.create({ await AuditLog.create({
userId: req.user?.id || null, userId: req.user?.id || null,
@ -239,7 +256,7 @@ export const deleteLineItem = async (req: AuthRequest, res: Response) => {
}; };
// Helper to calculate and update FnF progress // Helper to calculate and update FnF progress
const calculateFnFLogic = async (id: string) => { const calculateFnFLogic = async (id: string, userId: string | null = null) => {
const fnf = await FnF.findByPk(id, { const fnf = await FnF.findByPk(id, {
include: [{ model: FnFLineItem, as: 'lineItems' }, { model: FffClearance, as: 'clearances' }] include: [{ model: FnFLineItem, as: 'lineItems' }, { model: FffClearance, as: 'clearances' }]
}); });
@ -302,6 +319,29 @@ const calculateFnFLogic = async (id: string) => {
progressPercentage progressPercentage
}); });
// If status moved to Completed, also update parent resignation/termination
if (newStatus === FNF_STATUS.COMPLETED || newStatus === 'Completed') {
if (fnf.resignationId) {
const resignation = await Resignation.findByPk(fnf.resignationId);
if (resignation) {
await ResignationWorkflowService.transitionResignation(resignation, RESIGNATION_STAGES.COMPLETED, userId, {
action: 'F&F Settlement Completed',
remarks: 'Full & Final settlement process finalized. Transitioning resignation to Completed.',
status: 'Completed'
});
}
} else if (fnf.terminationRequestId) {
const terminationRequest = await TerminationRequest.findByPk(fnf.terminationRequestId);
if (terminationRequest) {
await TerminationWorkflowService.transitionTermination(terminationRequest, TERMINATION_STAGES.TERMINATED, userId, {
action: 'F&F Settlement Completed',
remarks: 'Full & Final settlement process finalized. Transitioning termination to Terminated.',
status: 'Terminated'
});
}
}
}
return fnf; return fnf;
}; };
@ -322,15 +362,43 @@ export const updateClearance = async (req: AuthRequest, res: Response) => {
}); });
// Automatically update FnF progress // Automatically update FnF progress
await calculateFnFLogic(id); console.log(`[SettlementController] Updating clearance for F&F: ${id}`);
const fnfRecord = await calculateFnFLogic(id as string, req.user?.id);
await AuditLog.create({ // 1. Local F&F Audit (Dedicated Table: fnf_audit_logs)
userId: req.user?.id || null, try {
action: AUDIT_ACTIONS.FNF_UPDATED, console.log(`[SettlementController] Creating FnFAudit for ${id}`);
entityType: 'fnf', await db.FnFAudit.create({
entityId: id, userId: req.user?.id || null,
newData: { action: 'UPDATE_CLEARANCE', department: clearance.department, status, remarks } fnfId: id,
}); action: 'CLEARANCE_UPDATED',
remarks: remarks || 'No remarks',
details: { department: clearance.department, status }
});
} catch (auditError) {
console.error('[SettlementController] Local FnFAudit creation failed:', auditError);
}
// 2. Interconnected Mirror (Dedicated Table: resignation_audit_logs or termination_audit_logs)
if (fnfRecord && (fnfRecord.resignationId || fnfRecord.terminationRequestId)) {
try {
const isResignation = !!fnfRecord.resignationId;
const parentAuditModel = isResignation ? db.ResignationAudit : db.TerminationAudit;
const parentKey = isResignation ? 'resignationId' : 'terminationRequestId';
const parentId = fnfRecord.resignationId || fnfRecord.terminationRequestId;
console.log(`[SettlementController] Creating Parent Audit for ${parentId} (${isResignation ? 'resignation' : 'termination'})`);
await parentAuditModel.create({
userId: req.user?.id || null,
[parentKey]: parentId,
action: 'STAKEHOLDER_CLEARANCE_UPDATED',
remarks: `Automated sync from F&F: ${remarks || 'No remarks'}`,
details: { department: clearance.department, status }
});
} catch (parentAuditError) {
console.error('[SettlementController] Parent Audit creation failed:', parentAuditError);
}
}
res.json({ success: true, message: 'Clearance updated successfully', clearance }); res.json({ success: true, message: 'Clearance updated successfully', clearance });
} catch (error) { } catch (error) {
@ -342,7 +410,7 @@ export const updateClearance = async (req: AuthRequest, res: Response) => {
export const calculateFnF = async (req: AuthRequest, res: Response) => { export const calculateFnF = async (req: AuthRequest, res: Response) => {
try { try {
const { id } = req.params; const { id } = req.params;
const fnf = await calculateFnFLogic(id); const fnf = await calculateFnFLogic(id as string, req.user?.id);
if (!fnf) return res.status(404).json({ success: false, message: 'F&F not found' }); if (!fnf) return res.status(404).json({ success: false, message: 'F&F not found' });
res.json({ success: true, fnf }); res.json({ success: true, fnf });

View File

@ -38,11 +38,11 @@ export const createTermination = async (req: AuthRequest, res: Response, next: N
}] }]
}, { transaction }); }, { transaction });
await db.AuditLog.create({ await db.TerminationAudit.create({
userId: req.user.id, userId: req.user.id,
action: AUDIT_ACTIONS.CREATED, action: AUDIT_ACTIONS.CREATED,
entityType: 'termination', terminationRequestId: termination.id,
entityId: termination.id remarks: 'Admin initiated termination request'
}, { transaction }); }, { transaction });
await transaction.commit(); await transaction.commit();
@ -306,14 +306,24 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
); );
} }
await db.AuditLog.create({ await db.TerminationAudit.create({
userId: req.user.id, userId: req.user.id,
action: AUDIT_ACTIONS.UPDATED, action: 'CLEARANCE_UPDATED',
entityType: 'termination', terminationRequestId: id,
entityId: id, remarks: remarks || `Cleared ${department}`,
newData: { department, status, amount } details: { department, status, amount }
}, { transaction }); }, { transaction });
if (fnf) {
await db.FnFAudit.create({
userId: req.user.id,
fnfId: fnf.id,
action: 'CLEARANCE_UPDATED',
remarks: remarks || `Departmental clearance recorded for ${department}`,
details: { department, status, source: 'Termination Workflow' }
}, { transaction });
}
await transaction.commit(); await transaction.commit();
res.json({ success: true, message: `Clearance updated for ${department}`, clearances }); res.json({ success: true, message: `Clearance updated for ${department}`, clearances });
} catch (error) { } catch (error) {

View File

@ -30,11 +30,12 @@ export class ConstitutionalWorkflowService {
await request.update(updateData); await request.update(updateData);
// Audit Log // Audit Log
await db.AuditLog.create({ await db.ConstitutionalAudit.create({
userId, userId,
constitutionalChangeId: request.id,
action: action === 'Reject' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.UPDATED, action: action === 'Reject' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.UPDATED,
entityType: 'constitutional_change', remarks: remarks || '',
entityId: request.id details: { status: updateData.status, stage: targetStage }
}); });
return request; return request;

View File

@ -42,12 +42,12 @@ export class RelocationWorkflowService {
await request.update({ timeline: updatedTimeline }); await request.update({ timeline: updatedTimeline });
// 3. Create Audit Log // 3. Create Audit Log
await AuditLog.create({ await db.RelocationAudit.create({
userId: userId, userId: userId,
relocationRequestId: request.id,
action: action === 'REJECT' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.APPROVED, action: action === 'REJECT' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.APPROVED,
entityType: 'relocation', remarks: reason || '',
entityId: request.id, details: { status: targetStatus, stage: stage || request.currentStage }
newData: { status: targetStatus, stage: stage || request.currentStage, reason }
}); });
console.log(`[RelocationWorkflowService] Transitioned Request ${request.requestId} to ${targetStatus}`); console.log(`[RelocationWorkflowService] Transitioned Request ${request.requestId} to ${targetStatus}`);

View File

@ -2,6 +2,7 @@ import db from '../database/models/index.js';
const { AuditLog, User, Worknote } = db; const { AuditLog, User, Worknote } = db;
import { AUDIT_ACTIONS, RESIGNATION_STAGES, ROLES } from '../common/config/constants.js'; import { AUDIT_ACTIONS, RESIGNATION_STAGES, ROLES } from '../common/config/constants.js';
import { NotificationService } from './NotificationService.js'; import { NotificationService } from './NotificationService.js';
import { Op } from 'sequelize';
import logger from '../common/utils/logger.js'; import logger from '../common/utils/logger.js';
@ -41,12 +42,12 @@ export class ResignationWorkflowService {
if (action === 'WITHDRAW' || action === 'Withdrawn') auditAction = AUDIT_ACTIONS.UPDATED; if (action === 'WITHDRAW' || action === 'Withdrawn') auditAction = AUDIT_ACTIONS.UPDATED;
if (action === 'SENT_BACK' || action === 'Sent Back') auditAction = AUDIT_ACTIONS.UPDATED; if (action === 'SENT_BACK' || action === 'Sent Back') auditAction = AUDIT_ACTIONS.UPDATED;
await AuditLog.create({ await db.ResignationAudit.create({
userId: userId, userId: userId,
resignationId: resignation.id,
action: auditAction, action: auditAction,
entityType: 'resignation', remarks: remarks || '',
entityId: resignation.id, details: { status: updateData.status, stage: targetStage }
newData: { status: updateData.status, stage: targetStage, remarks }
}); });
// 4. Create Worknote if it's a "Sent Back" action for communication // 4. Create Worknote if it's a "Sent Back" action for communication
@ -64,8 +65,10 @@ export class ResignationWorkflowService {
// 5. Send Notifications // 5. Send Notifications
const user = await User.findOne({ const user = await User.findOne({
where: { where: {
dealerId: resignation.dealerId, [Op.or]: [
roleCode: ROLES.DEALER { id: resignation.dealerId },
{ dealerId: resignation.dealerId }
]
} }
}); });
@ -91,7 +94,7 @@ export class ResignationWorkflowService {
}); });
} }
} else { } else {
logger.warn(`[ResignationWorkflowService] No user account found with dealerId ${resignation.dealerId} and role ${ROLES.DEALER}`); logger.warn(`[ResignationWorkflowService] No user account found with dealerId ${resignation.dealerId}`);
} }
return resignation; return resignation;

View File

@ -1,4 +1,5 @@
import db from '../database/models/index.js'; import db from '../database/models/index.js';
import { Op } from 'sequelize';
const { AuditLog, User, TerminationScnResponse, TerminationHearingRecord, Dealer, FnF, FnFLineItem, FffClearance } = db; const { AuditLog, User, TerminationScnResponse, TerminationHearingRecord, Dealer, FnF, FnFLineItem, FffClearance } = db;
import { AUDIT_ACTIONS, TERMINATION_STAGES, ROLES, FNF_DEPARTMENTS } from '../common/config/constants.js'; import { AUDIT_ACTIONS, TERMINATION_STAGES, ROLES, FNF_DEPARTMENTS } from '../common/config/constants.js';
import { NotificationService } from './NotificationService.js'; import { NotificationService } from './NotificationService.js';
@ -40,19 +41,21 @@ export class TerminationWorkflowService {
if (action === 'REJECT' || action === 'Rejected') auditAction = AUDIT_ACTIONS.REJECTED; if (action === 'REJECT' || action === 'Rejected') auditAction = AUDIT_ACTIONS.REJECTED;
if (action === 'SCN_SUBMITTED' || action === 'Hearing Recorded') auditAction = AUDIT_ACTIONS.UPDATED; if (action === 'SCN_SUBMITTED' || action === 'Hearing Recorded') auditAction = AUDIT_ACTIONS.UPDATED;
await AuditLog.create({ await db.TerminationAudit.create({
userId: userId, userId: userId,
terminationRequestId: termination.id,
action: auditAction, action: auditAction,
entityType: 'termination', remarks: remarks || '',
entityId: termination.id, details: { status: updateData.status, stage: targetStage }
newData: { status: updateData.status, stage: targetStage, remarks }
}); });
// 4. Send Notifications // 4. Send Notifications
const user = await User.findOne({ const user = await User.findOne({
where: { where: {
dealerId: termination.dealerId, [Op.or]: [
roleCode: ROLES.DEALER { id: termination.dealerId },
{ dealerId: termination.dealerId }
]
} }
}); });
@ -79,7 +82,7 @@ export class TerminationWorkflowService {
}); });
} }
} else { } else {
logger.warn(`[TerminationWorkflowService] No user account found with dealerId ${termination.dealerId} and role ${ROLES.DEALER}`); logger.warn(`[TerminationWorkflowService] No user account found with dealerId ${termination.dealerId}`);
} }
return termination; return termination;
@ -104,7 +107,7 @@ export class TerminationWorkflowService {
// 2. Create FnF Settlement Record with direct IDs and readable identifier // 2. Create FnF Settlement Record with direct IDs and readable identifier
const fnf = await FnF.create({ const fnf = await FnF.create({
settlementId: NomenclatureService.generateSettlementId(), settlementId: NomenclatureService.generateFnFId(),
terminationRequestId: termination.id, terminationRequestId: termination.id,
dealerId: termination.dealerId, dealerId: termination.dealerId,
outletId: primaryOutlet?.id || null, outletId: primaryOutlet?.id || null,

20
sync_audit.ts Normal file
View File

@ -0,0 +1,20 @@
import db from './src/database/models/index.js';
async function syncAuditTables() {
try {
console.log('Syncing Module-Specific Audit Tables...');
await db.ResignationAudit.sync({ alter: true });
await db.TerminationAudit.sync({ alter: true });
await db.FnFAudit.sync({ alter: true });
await db.ConstitutionalAudit.sync({ alter: true });
await db.RelocationAudit.sync({ alter: true });
await db.FnF.sync({ alter: true });
console.log('Sync complete.');
process.exit(0);
} catch (error) {
console.error('Sync failed:', error);
process.exit(1);
}
}
syncAuditTables();

View File

@ -218,16 +218,27 @@ async function run() {
const financeToken = await login(EMAILS.FINANCE); const financeToken = await login(EMAILS.FINANCE);
await apiRequest(`/settlement/fnf/${fnfId}`, 'PUT', { await apiRequest(`/settlement/fnf/${fnfId}`, 'PUT', {
status: 'Completed', status: 'Completed',
finalSettlementAmount: 0, finalSettlementAmount: 415173, // Matches your observed amount
remarks: 'Settlement completed' paymentMode: 'NEFT / Bank Transfer',
transactionReference: `TXN-${Date.now()}`,
settlementDate: new Date().toISOString(),
remarks: 'Settlement completed and verified via automated script.'
}, financeToken); }, financeToken);
await delay(); await delay();
// --- FINAL COMPLETION --- // --- FINAL COMPLETION ---
console.log('[STEP 11] Moving Resignation to COMPLETED...'); console.log('[STEP 11] Verifying Resignation is now COMPLETED (Auto-transitioned)...');
await apiRequest(`/self-service/resignations/${resignationId}/approve`, 'PUT', { const finalStatusRes = await apiRequest(`/self-service/resignations/${resignationId}`, 'GET', null, adminToken);
remarks: 'Final resignation completion.' if (finalStatusRes.resignation.status === 'Completed') {
}, adminToken); log(11, 'Resignation auto-transitioned to Completed successfully.');
} else {
console.log(`[STEP 11] FAILED: Current status is ${finalStatusRes.resignation.status}`);
// Fallback: manually trigger completion if auto-sync failed to keep script running
await apiRequest(`/self-service/resignations/${resignationId}/approve`, 'PUT', {
remarks: 'Final resignation completion (Manual Fallback).',
force: true
}, adminToken);
}
await delay(); await delay();
// [FINAL STEP] Verification of deactivation // [FINAL STEP] Verification of deactivation
@ -237,9 +248,7 @@ async function run() {
const userRes = await apiRequest('/admin/users', 'GET', null, adminToken); const userRes = await apiRequest('/admin/users', 'GET', null, adminToken);
// Fetch dealer to get its associated user ID // Fetch dealer to get its associated user ID
const dealersRes = await apiRequest('/dealer', 'GET', null, adminToken); const dealerU = userRes.data.find(u => u.email === targetApp.email);
const targetD = dealersRes.data.find(d => d.id === targetOutlet.dealerId);
const dealerU = userRes.data.find(u => u.id === targetD.user?.id);
if (dealerU && (dealerU.status === 'deactivated' || !dealerU.isActive)) { if (dealerU && (dealerU.status === 'deactivated' || !dealerU.isActive)) {
console.log(`[VERIFICATION] SUCCESS: Account ${dealerU.email} is deactivated. Status: ${dealerU.status}`); console.log(`[VERIFICATION] SUCCESS: Account ${dealerU.email} is deactivated. Status: ${dealerU.status}`);

View File

@ -156,7 +156,7 @@ async function run() {
// Fetch user data to verify deactivation // Fetch user data to verify deactivation
const userRes = await apiRequest(`/admin/users`, 'GET', null, adminToken); const userRes = await apiRequest(`/admin/users`, 'GET', null, adminToken);
const dealerUser = userRes.data.find(u => u.id === targetDealer.user?.id); const dealerUser = userRes.data.find(u => u.dealerId === targetDealer.id);
if (dealerUser && !dealerUser.isActive && dealerUser.status === 'deactivated') { if (dealerUser && !dealerUser.isActive && dealerUser.status === 'deactivated') {
console.log(`[VERIFICATION] Account ${dealerUser.email} successfully DEACTIVATED.`); console.log(`[VERIFICATION] Account ${dealerUser.email} successfully DEACTIVATED.`);

View File

@ -114,7 +114,7 @@ async function prospectLogin(phone) {
async function mockUploadDocument(appId, token, docType) { async function mockUploadDocument(appId, token, docType) {
const formData = new FormData(); const formData = new FormData();
const fileBuffer = fs.readFileSync('C:/Users/BACKPACKERS/Pictures/claim_document_type.PNG'); const fileBuffer = fs.readFileSync('/home/laxman-h/Pictures/Screenshots/Screenshot from 2026-03-26 10-08-00.png');
const blob = new Blob([fileBuffer], { type: 'image/png' }); const blob = new Blob([fileBuffer], { type: 'image/png' });
formData.append('file', blob, 'screenshot.png'); formData.append('file', blob, 'screenshot.png');
formData.append('documentType', docType); formData.append('documentType', docType);
@ -467,7 +467,7 @@ async function triggerWorkflow() {
status: 'Completed', status: 'Completed',
overallComments: 'Dealer is 100% ready for inauguration. All infra and statutory items verified.' overallComments: 'Dealer is 100% ready for inauguration. All infra and statutory items verified.'
}, adminToken); }, adminToken);
// Status check // Status check
const finalAppStatus = await apiRequest(`/onboarding/applications/${applicationUUID}`, 'GET', null, adminToken); const finalAppStatus = await apiRequest(`/onboarding/applications/${applicationUUID}`, 'GET', null, adminToken);
log(11.2, `Application Status after EOR: ${finalAppStatus.data.overallStatus}`); log(11.2, `Application Status after EOR: ${finalAppStatus.data.overallStatus}`);