module wise audit tables added
This commit is contained in:
parent
6373372eb9
commit
1d885f9d9f
21
check_logs.ts
Normal file
21
check_logs.ts
Normal 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
45
debug-state.cjs
Normal 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();
|
||||
@ -256,7 +256,7 @@ export const FNF_STATUS = {
|
||||
COMPLETED: 'Completed'
|
||||
} 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 = [
|
||||
'Warranty Department',
|
||||
'Accessories Department',
|
||||
|
||||
@ -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() {
|
||||
return `SET-${new Date().getFullYear()}-${Math.floor(1000 + Math.random() * 9000)}`;
|
||||
static generateFnFId() {
|
||||
const year = new Date().getFullYear();
|
||||
const rand = Math.floor(1 + Math.random() * 999);
|
||||
return `FNF-${year}-${rand.toString().padStart(3, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
65
src/database/models/ConstitutionalAudit.ts
Normal file
65
src/database/models/ConstitutionalAudit.ts
Normal 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;
|
||||
};
|
||||
@ -20,6 +20,7 @@ export interface FnFAttributes {
|
||||
remarks: string | null;
|
||||
clearanceDocuments: any[];
|
||||
progressPercentage: number;
|
||||
timeline: any[];
|
||||
}
|
||||
|
||||
export interface FnFInstance extends Model<FnFAttributes>, FnFAttributes { }
|
||||
@ -115,6 +116,10 @@ export default (sequelize: Sequelize) => {
|
||||
progressPercentage: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0
|
||||
},
|
||||
timeline: {
|
||||
type: DataTypes.JSON,
|
||||
defaultValue: []
|
||||
}
|
||||
}, {
|
||||
tableName: 'fnf_settlements',
|
||||
|
||||
65
src/database/models/FnFAudit.ts
Normal file
65
src/database/models/FnFAudit.ts
Normal 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;
|
||||
};
|
||||
65
src/database/models/RelocationAudit.ts
Normal file
65
src/database/models/RelocationAudit.ts
Normal 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;
|
||||
};
|
||||
65
src/database/models/ResignationAudit.ts
Normal file
65
src/database/models/ResignationAudit.ts
Normal 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;
|
||||
};
|
||||
65
src/database/models/TerminationAudit.ts
Normal file
65
src/database/models/TerminationAudit.ts
Normal 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;
|
||||
};
|
||||
@ -33,6 +33,11 @@ import createState from './State.js';
|
||||
import createTerminationScnResponse from './TerminationScnResponse.js';
|
||||
import createTerminationHearingRecord from './TerminationHearingRecord.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';
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
@ -147,6 +152,11 @@ db.State = createState(sequelize);
|
||||
db.TerminationScnResponse = createTerminationScnResponse(sequelize);
|
||||
db.TerminationHearingRecord = createTerminationHearingRecord(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);
|
||||
|
||||
// Batch 1: Organizational Hierarchy & User Management
|
||||
|
||||
@ -52,6 +52,8 @@ const ACTION_DESCRIPTIONS: Record<string, string> = {
|
||||
PAYMENT_UPDATED: 'Payment record updated',
|
||||
SECURITY_DEPOSIT_UPDATED: 'Security deposit 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_UPDATED: 'User account updated',
|
||||
USER_STATUS_CHANGED: 'User status changed',
|
||||
@ -71,6 +73,7 @@ const ACTION_DESCRIPTIONS: Record<string, string> = {
|
||||
export const getAuditLogs = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const { entityType, entityId, page = '1', limit = '50' } = req.query;
|
||||
console.log(`[AuditController] Fetching logs for ${entityType} ID: ${entityId}`);
|
||||
|
||||
if (!entityType || !entityId) {
|
||||
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 offset = (pageNum - 1) * limitNum;
|
||||
|
||||
const { count, rows: logs } = await AuditLog.findAndCountAll({
|
||||
where: {
|
||||
entityType: entityType as string,
|
||||
entityId: entityId as string
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'fullName', 'email']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: limitNum,
|
||||
offset
|
||||
});
|
||||
let count = 0;
|
||||
let logs: any[] = [];
|
||||
|
||||
// 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 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 {
|
||||
id: logData.id,
|
||||
action: logData.action,
|
||||
description: ACTION_DESCRIPTIONS[logData.action] || logData.action,
|
||||
entityType: logData.entityType,
|
||||
entityId: logData.entityId,
|
||||
description: baseDescription,
|
||||
entityType: entityType,
|
||||
entityId: entityId,
|
||||
userName: logData.user?.fullName || 'System',
|
||||
userEmail: logData.user?.email || null,
|
||||
oldData: logData.oldData,
|
||||
newData: logData.newData,
|
||||
changes: formatChanges(logData.oldData, logData.newData),
|
||||
ipAddress: logData.ipAddress,
|
||||
remarks: logData.remarks || logData.newData?.remarks,
|
||||
newData: details, // Normalize module-specific 'details' to 'newData' for UI
|
||||
timestamp: logData.createdAt
|
||||
};
|
||||
});
|
||||
@ -147,25 +211,54 @@ export const getAuditSummary = async (req: AuthRequest, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
const totalLogs = await AuditLog.count({
|
||||
where: {
|
||||
entityType: entityType as string,
|
||||
entityId: entityId as string
|
||||
}
|
||||
});
|
||||
let totalLogs = 0;
|
||||
let latestLog: any = null;
|
||||
const type = (entityType as string).toLowerCase();
|
||||
|
||||
const latestLog = await AuditLog.findOne({
|
||||
where: {
|
||||
entityType: entityType as string,
|
||||
entityId: entityId as string
|
||||
},
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'user',
|
||||
attributes: ['id', 'fullName']
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
// Dynamic Table Switching
|
||||
if (type === 'resignation') {
|
||||
totalLogs = await db.ResignationAudit.count({ where: { resignationId: entityId as string } });
|
||||
latestLog = await db.ResignationAudit.findOne({
|
||||
where: { resignationId: entityId as string },
|
||||
include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName'] }],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
} else if (type === 'termination') {
|
||||
totalLogs = await db.TerminationAudit.count({ where: { terminationRequestId: entityId as string } });
|
||||
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({
|
||||
success: true,
|
||||
@ -173,7 +266,7 @@ export const getAuditSummary = async (req: AuthRequest, res: Response) => {
|
||||
totalEntries: totalLogs,
|
||||
lastActivity: latestLog ? {
|
||||
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',
|
||||
timestamp: (latestLog as any).createdAt
|
||||
} : null
|
||||
|
||||
@ -65,11 +65,11 @@ export const createResignation = async (req: AuthRequest, res: Response, next: N
|
||||
}, { transaction });
|
||||
|
||||
await outlet.update({ status: 'Pending Resignation' }, { transaction });
|
||||
await db.AuditLog.create({
|
||||
await db.ResignationAudit.create({
|
||||
userId: req.user.id,
|
||||
action: AUDIT_ACTIONS.CREATED,
|
||||
entityType: 'resignation',
|
||||
entityId: resignation.id
|
||||
resignationId: resignation.id,
|
||||
remarks: 'Dealer submitted resignation request'
|
||||
}, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
@ -265,7 +265,7 @@ export const approveResignation = async (req: AuthRequest, res: Response, next:
|
||||
const dealerProfileId = (resignation as any).dealer?.dealerId;
|
||||
|
||||
const fnf = await db.FnF.create({
|
||||
settlementId: NomenclatureService.generateSettlementId(),
|
||||
settlementId: NomenclatureService.generateFnFId(),
|
||||
resignationId: resignation.id,
|
||||
outletId: resignation.outletId,
|
||||
dealerId: dealerProfileId, // Correctly using the Dealer model ID
|
||||
@ -483,6 +483,15 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
|
||||
}]
|
||||
}, { 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
|
||||
const fnf = await db.FnF.findOne({ where: { resignationId: resignation.id } });
|
||||
if (fnf) {
|
||||
@ -520,6 +529,15 @@ export const updateClearance = async (req: AuthRequest, res: Response, next: Nex
|
||||
}, { 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 (amount > 0) {
|
||||
const existingItem = await db.FnFLineItem.findOne({
|
||||
@ -594,7 +612,7 @@ export const updateResignationStatus = async (req: AuthRequest, res: Response, n
|
||||
case 'pushfnf':
|
||||
// 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];
|
||||
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' });
|
||||
}
|
||||
// Jump directly to F&F Initiation
|
||||
|
||||
@ -2,7 +2,9 @@ import { Request, Response } from 'express';
|
||||
import db from '../../database/models/index.js';
|
||||
const { FinancePayment, FnF, Application, Resignation, User, Outlet, FnFLineItem, TerminationRequest, FffClearance, AuditLog } = db;
|
||||
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) => {
|
||||
try {
|
||||
@ -92,8 +94,20 @@ export const updateFnF = async (req: AuthRequest, res: Response) => {
|
||||
newData: { status, netAmount: finalSettlementAmount, remarks }
|
||||
});
|
||||
|
||||
// If status is being set to Completed, we might want to trigger additional logic here
|
||||
// like notifying the dealer or updating the resignation status if it's not already
|
||||
// If status is being set to Completed, update the parent request status as well
|
||||
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 });
|
||||
} catch (error) {
|
||||
@ -108,6 +122,7 @@ export const getFnFSettlements = async (req: Request, res: Response) => {
|
||||
include: [
|
||||
{ model: Resignation, as: 'resignation', attributes: ['id', 'resignationId'] },
|
||||
{ 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: FnFLineItem, as: 'lineItems' },
|
||||
{ model: FffClearance, as: 'clearances' }
|
||||
@ -171,7 +186,7 @@ export const addLineItem = async (req: AuthRequest, res: Response) => {
|
||||
});
|
||||
|
||||
// Update FnF progress and department statuses
|
||||
await calculateFnFLogic(id);
|
||||
await calculateFnFLogic(id as string, req.user?.id);
|
||||
|
||||
await AuditLog.create({
|
||||
userId: req.user?.id || null,
|
||||
@ -196,7 +211,8 @@ export const updateLineItem = async (req: AuthRequest, res: Response) => {
|
||||
await lineItem.update({ description, department, amount });
|
||||
|
||||
// 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({
|
||||
userId: req.user?.id || null,
|
||||
@ -222,7 +238,8 @@ export const deleteLineItem = async (req: AuthRequest, res: Response) => {
|
||||
await lineItem.destroy();
|
||||
|
||||
// Update FnF progress and department statuses
|
||||
await calculateFnFLogic(fnfId);
|
||||
// Update FnF progress and department statuses
|
||||
await calculateFnFLogic(fnfId, req.user?.id);
|
||||
|
||||
await AuditLog.create({
|
||||
userId: req.user?.id || null,
|
||||
@ -239,7 +256,7 @@ export const deleteLineItem = async (req: AuthRequest, res: Response) => {
|
||||
};
|
||||
|
||||
// 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, {
|
||||
include: [{ model: FnFLineItem, as: 'lineItems' }, { model: FffClearance, as: 'clearances' }]
|
||||
});
|
||||
@ -302,6 +319,29 @@ const calculateFnFLogic = async (id: string) => {
|
||||
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;
|
||||
};
|
||||
|
||||
@ -322,15 +362,43 @@ export const updateClearance = async (req: AuthRequest, res: Response) => {
|
||||
});
|
||||
|
||||
// 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({
|
||||
userId: req.user?.id || null,
|
||||
action: AUDIT_ACTIONS.FNF_UPDATED,
|
||||
entityType: 'fnf',
|
||||
entityId: id,
|
||||
newData: { action: 'UPDATE_CLEARANCE', department: clearance.department, status, remarks }
|
||||
});
|
||||
// 1. Local F&F Audit (Dedicated Table: fnf_audit_logs)
|
||||
try {
|
||||
console.log(`[SettlementController] Creating FnFAudit for ${id}`);
|
||||
await db.FnFAudit.create({
|
||||
userId: req.user?.id || null,
|
||||
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 });
|
||||
} catch (error) {
|
||||
@ -342,7 +410,7 @@ export const updateClearance = async (req: AuthRequest, res: Response) => {
|
||||
export const calculateFnF = async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
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' });
|
||||
|
||||
res.json({ success: true, fnf });
|
||||
|
||||
@ -38,11 +38,11 @@ export const createTermination = async (req: AuthRequest, res: Response, next: N
|
||||
}]
|
||||
}, { transaction });
|
||||
|
||||
await db.AuditLog.create({
|
||||
await db.TerminationAudit.create({
|
||||
userId: req.user.id,
|
||||
action: AUDIT_ACTIONS.CREATED,
|
||||
entityType: 'termination',
|
||||
entityId: termination.id
|
||||
terminationRequestId: termination.id,
|
||||
remarks: 'Admin initiated termination request'
|
||||
}, { transaction });
|
||||
|
||||
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,
|
||||
action: AUDIT_ACTIONS.UPDATED,
|
||||
entityType: 'termination',
|
||||
entityId: id,
|
||||
newData: { department, status, amount }
|
||||
action: 'CLEARANCE_UPDATED',
|
||||
terminationRequestId: id,
|
||||
remarks: remarks || `Cleared ${department}`,
|
||||
details: { department, status, amount }
|
||||
}, { 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();
|
||||
res.json({ success: true, message: `Clearance updated for ${department}`, clearances });
|
||||
} catch (error) {
|
||||
|
||||
@ -30,11 +30,12 @@ export class ConstitutionalWorkflowService {
|
||||
await request.update(updateData);
|
||||
|
||||
// Audit Log
|
||||
await db.AuditLog.create({
|
||||
await db.ConstitutionalAudit.create({
|
||||
userId,
|
||||
constitutionalChangeId: request.id,
|
||||
action: action === 'Reject' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.UPDATED,
|
||||
entityType: 'constitutional_change',
|
||||
entityId: request.id
|
||||
remarks: remarks || '',
|
||||
details: { status: updateData.status, stage: targetStage }
|
||||
});
|
||||
|
||||
return request;
|
||||
|
||||
@ -42,12 +42,12 @@ export class RelocationWorkflowService {
|
||||
await request.update({ timeline: updatedTimeline });
|
||||
|
||||
// 3. Create Audit Log
|
||||
await AuditLog.create({
|
||||
await db.RelocationAudit.create({
|
||||
userId: userId,
|
||||
relocationRequestId: request.id,
|
||||
action: action === 'REJECT' ? AUDIT_ACTIONS.REJECTED : AUDIT_ACTIONS.APPROVED,
|
||||
entityType: 'relocation',
|
||||
entityId: request.id,
|
||||
newData: { status: targetStatus, stage: stage || request.currentStage, reason }
|
||||
remarks: reason || '',
|
||||
details: { status: targetStatus, stage: stage || request.currentStage }
|
||||
});
|
||||
|
||||
console.log(`[RelocationWorkflowService] Transitioned Request ${request.requestId} to ${targetStatus}`);
|
||||
|
||||
@ -2,6 +2,7 @@ import db from '../database/models/index.js';
|
||||
const { AuditLog, User, Worknote } = db;
|
||||
import { AUDIT_ACTIONS, RESIGNATION_STAGES, ROLES } from '../common/config/constants.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
import { Op } from 'sequelize';
|
||||
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 === 'SENT_BACK' || action === 'Sent Back') auditAction = AUDIT_ACTIONS.UPDATED;
|
||||
|
||||
await AuditLog.create({
|
||||
await db.ResignationAudit.create({
|
||||
userId: userId,
|
||||
resignationId: resignation.id,
|
||||
action: auditAction,
|
||||
entityType: 'resignation',
|
||||
entityId: resignation.id,
|
||||
newData: { status: updateData.status, stage: targetStage, remarks }
|
||||
remarks: remarks || '',
|
||||
details: { status: updateData.status, stage: targetStage }
|
||||
});
|
||||
|
||||
// 4. Create Worknote if it's a "Sent Back" action for communication
|
||||
@ -64,8 +65,10 @@ export class ResignationWorkflowService {
|
||||
// 5. Send Notifications
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
dealerId: resignation.dealerId,
|
||||
roleCode: ROLES.DEALER
|
||||
[Op.or]: [
|
||||
{ id: resignation.dealerId },
|
||||
{ dealerId: resignation.dealerId }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@ -91,7 +94,7 @@ export class ResignationWorkflowService {
|
||||
});
|
||||
}
|
||||
} 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;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import db from '../database/models/index.js';
|
||||
import { Op } from 'sequelize';
|
||||
const { AuditLog, User, TerminationScnResponse, TerminationHearingRecord, Dealer, FnF, FnFLineItem, FffClearance } = db;
|
||||
import { AUDIT_ACTIONS, TERMINATION_STAGES, ROLES, FNF_DEPARTMENTS } from '../common/config/constants.js';
|
||||
import { NotificationService } from './NotificationService.js';
|
||||
@ -40,19 +41,21 @@ export class TerminationWorkflowService {
|
||||
if (action === 'REJECT' || action === 'Rejected') auditAction = AUDIT_ACTIONS.REJECTED;
|
||||
if (action === 'SCN_SUBMITTED' || action === 'Hearing Recorded') auditAction = AUDIT_ACTIONS.UPDATED;
|
||||
|
||||
await AuditLog.create({
|
||||
await db.TerminationAudit.create({
|
||||
userId: userId,
|
||||
terminationRequestId: termination.id,
|
||||
action: auditAction,
|
||||
entityType: 'termination',
|
||||
entityId: termination.id,
|
||||
newData: { status: updateData.status, stage: targetStage, remarks }
|
||||
remarks: remarks || '',
|
||||
details: { status: updateData.status, stage: targetStage }
|
||||
});
|
||||
|
||||
// 4. Send Notifications
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
dealerId: termination.dealerId,
|
||||
roleCode: ROLES.DEALER
|
||||
[Op.or]: [
|
||||
{ id: termination.dealerId },
|
||||
{ dealerId: termination.dealerId }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
@ -79,7 +82,7 @@ export class TerminationWorkflowService {
|
||||
});
|
||||
}
|
||||
} 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;
|
||||
@ -104,7 +107,7 @@ export class TerminationWorkflowService {
|
||||
|
||||
// 2. Create FnF Settlement Record with direct IDs and readable identifier
|
||||
const fnf = await FnF.create({
|
||||
settlementId: NomenclatureService.generateSettlementId(),
|
||||
settlementId: NomenclatureService.generateFnFId(),
|
||||
terminationRequestId: termination.id,
|
||||
dealerId: termination.dealerId,
|
||||
outletId: primaryOutlet?.id || null,
|
||||
|
||||
20
sync_audit.ts
Normal file
20
sync_audit.ts
Normal 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();
|
||||
@ -218,16 +218,27 @@ async function run() {
|
||||
const financeToken = await login(EMAILS.FINANCE);
|
||||
await apiRequest(`/settlement/fnf/${fnfId}`, 'PUT', {
|
||||
status: 'Completed',
|
||||
finalSettlementAmount: 0,
|
||||
remarks: 'Settlement completed'
|
||||
finalSettlementAmount: 415173, // Matches your observed amount
|
||||
paymentMode: 'NEFT / Bank Transfer',
|
||||
transactionReference: `TXN-${Date.now()}`,
|
||||
settlementDate: new Date().toISOString(),
|
||||
remarks: 'Settlement completed and verified via automated script.'
|
||||
}, financeToken);
|
||||
await delay();
|
||||
|
||||
// --- FINAL COMPLETION ---
|
||||
console.log('[STEP 11] Moving Resignation to COMPLETED...');
|
||||
await apiRequest(`/self-service/resignations/${resignationId}/approve`, 'PUT', {
|
||||
remarks: 'Final resignation completion.'
|
||||
}, adminToken);
|
||||
console.log('[STEP 11] Verifying Resignation is now COMPLETED (Auto-transitioned)...');
|
||||
const finalStatusRes = await apiRequest(`/self-service/resignations/${resignationId}`, 'GET', null, adminToken);
|
||||
if (finalStatusRes.resignation.status === 'Completed') {
|
||||
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();
|
||||
|
||||
// [FINAL STEP] Verification of deactivation
|
||||
@ -237,9 +248,7 @@ async function run() {
|
||||
const userRes = await apiRequest('/admin/users', 'GET', null, adminToken);
|
||||
|
||||
// Fetch dealer to get its associated user ID
|
||||
const dealersRes = await apiRequest('/dealer', 'GET', null, adminToken);
|
||||
const targetD = dealersRes.data.find(d => d.id === targetOutlet.dealerId);
|
||||
const dealerU = userRes.data.find(u => u.id === targetD.user?.id);
|
||||
const dealerU = userRes.data.find(u => u.email === targetApp.email);
|
||||
|
||||
if (dealerU && (dealerU.status === 'deactivated' || !dealerU.isActive)) {
|
||||
console.log(`[VERIFICATION] SUCCESS: Account ${dealerU.email} is deactivated. Status: ${dealerU.status}`);
|
||||
|
||||
@ -156,7 +156,7 @@ async function run() {
|
||||
|
||||
// Fetch user data to verify deactivation
|
||||
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') {
|
||||
console.log(`[VERIFICATION] Account ${dealerUser.email} successfully DEACTIVATED.`);
|
||||
|
||||
@ -114,7 +114,7 @@ async function prospectLogin(phone) {
|
||||
|
||||
async function mockUploadDocument(appId, token, docType) {
|
||||
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' });
|
||||
formData.append('file', blob, 'screenshot.png');
|
||||
formData.append('documentType', docType);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user