218 lines
11 KiB
JavaScript
218 lines
11 KiB
JavaScript
const args = Object.fromEntries(
|
|
process.argv.slice(2)
|
|
.map(arg => arg.replace(/^--/, '').split('='))
|
|
.map(([k, v]) => [k, v ?? 'true'])
|
|
);
|
|
const BASE_URL = args.baseUrl || process.env.BASE_URL || 'http://localhost:5000/api';
|
|
const PASSWORD = 'Admin@123';
|
|
const STEP_DELAY_MS = Number(args.delayMs || 500);
|
|
const SHOULD_SKIP_CLEARANCES = String(args.skipClearances || 'false') === 'true';
|
|
|
|
const EMAILS = {
|
|
DD_ADMIN: 'lince@royalenfield.com',
|
|
ASM: 'abhishek@royalenfield.com',
|
|
RBM: 'manish@royalenfield.com',
|
|
DD_ZM: 'piyush@royalenfield.com',
|
|
ZBH: 'manav@royalenfield.com',
|
|
DD_LEAD: 'jaya@royalenfield.com',
|
|
DD_HEAD: 'ganesh@royalenfield.com',
|
|
LEGAL: 'legal@royalenfield.com',
|
|
NBH: 'yashwin@royalenfield.com',
|
|
CCO: 'admin@royalenfield.com',
|
|
CEO: 'admin@royalenfield.com',
|
|
SALES: 'sales@royalenfield.com',
|
|
SERVICE: 'service@royalenfield.com',
|
|
SPARES: 'spares@royalenfield.com',
|
|
ACCOUNTS: 'accounts@royalenfield.com',
|
|
WARRANTY: 'warranty@royalenfield.com',
|
|
MARKETING: 'marketing@royalenfield.com',
|
|
HR: 'hr@royalenfield.com',
|
|
IT: 'it@royalenfield.com',
|
|
LOGISTICS: 'logistics@royalenfield.com',
|
|
QUALITY: 'quality@royalenfield.com',
|
|
APPAREL: 'apparel@royalenfield.com',
|
|
DMS: 'dms@royalenfield.com'
|
|
};
|
|
|
|
async function apiRequest(endpoint, method = 'GET', body = null, token = null) {
|
|
const headers = { 'Content-Type': 'application/json' };
|
|
if (token) headers['Authorization'] = `Bearer ${token}`;
|
|
|
|
const config = { method, headers };
|
|
if (body) config.body = JSON.stringify(body);
|
|
|
|
const response = await fetch(`${BASE_URL}${endpoint}`, config);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API Error ${method} ${endpoint}: ${JSON.stringify(data)}`);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
async function login(email) {
|
|
if (!login.cache) login.cache = {};
|
|
if (login.cache[email]) return login.cache[email];
|
|
const data = await apiRequest('/auth/login', 'POST', { email, password: PASSWORD });
|
|
login.cache[email] = data.token;
|
|
return login.cache[email];
|
|
}
|
|
|
|
const delay = (ms = STEP_DELAY_MS) => new Promise(res => setTimeout(res, ms));
|
|
const log = (step, msg) => console.log(`[STEP ${step}] ${msg}`);
|
|
|
|
async function run() {
|
|
try {
|
|
console.log('--- STARTING DEALER TERMINATION E2E FLOW ---');
|
|
|
|
const adminToken = await login(EMAILS.DD_ADMIN);
|
|
const dealersRes = await apiRequest('/dealer', 'GET', null, adminToken);
|
|
const targetDealer = dealersRes.data[0];
|
|
|
|
if (!targetDealer) throw new Error('No dealer profiles found for termination test. Run seed first.');
|
|
|
|
console.log(`Targeting Dealer: ${targetDealer.legalName} (${targetDealer.id})`);
|
|
|
|
let terminationId = args.terminationId;
|
|
const isUnethical = String(args.category || '').trim().toLowerCase().includes('unethical');
|
|
|
|
if (!terminationId) {
|
|
console.log('[STEP 1] Initiating Termination...');
|
|
const asmToken = await login(EMAILS.ASM);
|
|
const createRes = await apiRequest('/termination', 'POST', {
|
|
dealerId: targetDealer.id,
|
|
category: args.category || 'Performance',
|
|
reason: args.reason || 'Consistently failed to meet commitment targets.',
|
|
proposedLwd: new Date().toISOString(),
|
|
comments: 'Auto termination for testing flow ending with Legal Team, CCO, CEO.'
|
|
}, asmToken);
|
|
terminationId = createRes.termination.id;
|
|
console.log(`[STEP 1] Termination Request Created. ID: ${terminationId}. Category: ${args.category || 'Performance'}`);
|
|
} else {
|
|
console.log(`[STEP 1] Resuming existing termination: ${terminationId}`);
|
|
}
|
|
|
|
const currentTermination = await apiRequest(`/termination/${terminationId}`, 'GET', null, adminToken);
|
|
const currentStage = currentTermination?.termination?.currentStage;
|
|
console.log(`[INFO] Current stage before progression: ${currentStage}`);
|
|
|
|
const approvals = [
|
|
{ stage: 'RBM + DD-ZM Review', actors: [{ email: EMAILS.RBM, remarks: 'Validated.' }, { email: EMAILS.DD_ZM, remarks: 'Confirmed.' }] },
|
|
{ stage: 'ZBH Review', actors: [{ email: EMAILS.ZBH, remarks: 'Strategic decision aligned.' }] },
|
|
{ stage: 'DD Lead Review', actors: [{ email: EMAILS.DD_LEAD, remarks: 'Breaches documented.' }] },
|
|
{ stage: 'Legal Verification', actors: [{ email: EMAILS.LEGAL, remarks: 'Case is sound.' }] },
|
|
{ stage: 'DD Head Review', actors: [{ email: EMAILS.DD_HEAD, remarks: 'Strategic impact assessed.' }] },
|
|
{ stage: 'NBH Evaluation', actors: [{ email: EMAILS.NBH, remarks: 'Functional teams aligned.' }] },
|
|
{ stage: 'Show Cause Notice (SCN)', actors: [{ email: EMAILS.DD_ADMIN, remarks: 'SCN Issued.' }] },
|
|
{ stage: 'Personal Hearing', actors: [
|
|
{ email: EMAILS.DD_LEAD, remarks: 'Hearing completed.' },
|
|
{ email: EMAILS.ZBH, remarks: 'Review recorded.' },
|
|
{ email: EMAILS.RBM, remarks: 'Review recorded.' },
|
|
{ email: EMAILS.DD_HEAD, remarks: 'Review recorded.' }
|
|
] },
|
|
{ stage: 'NBH Final Approval', actors: [{ email: EMAILS.NBH, remarks: 'Final recommendation.' }] },
|
|
{ stage: 'CCO Approval', actors: [{ email: EMAILS.CCO, remarks: 'Approved.' }] },
|
|
{ stage: 'CEO Final Approval', actors: [{ email: EMAILS.CEO, remarks: 'Final authorization.' }] },
|
|
{ stage: 'Legal - Termination Letter', actors: [{ email: EMAILS.LEGAL, remarks: 'Termination letter shared.' }] }
|
|
];
|
|
|
|
const stageOrder = [
|
|
'Submitted', 'RBM + DD-ZM Review', 'ZBH Review', 'DD Lead Review', 'Legal Verification', 'DD Head Review',
|
|
'NBH Evaluation', 'Show Cause Notice (SCN)', 'Personal Hearing', 'NBH Final Approval', 'CCO Approval',
|
|
'CEO Final Approval', 'Legal - Termination Letter', 'Terminated'
|
|
];
|
|
|
|
// If Unethical, the skip-routing skips to DD Lead Review (index 3 in stageOrder)
|
|
const startIndex = isUnethical ? 2 : Math.max(0, stageOrder.indexOf(currentStage));
|
|
let currentStep = 2;
|
|
|
|
for (let i = startIndex; i < approvals.length; i++) {
|
|
const step = approvals[i];
|
|
log(currentStep, `Stage: ${step.stage} - Processing approvals...`);
|
|
|
|
for (const actor of step.actors) {
|
|
const token = await login(actor.email);
|
|
await apiRequest(`/termination/${terminationId}/status`, 'PUT', {
|
|
action: 'approve',
|
|
remarks: actor.remarks
|
|
}, token);
|
|
log(currentStep, `Actor ${actor.email} Result: SUCCESS`);
|
|
await delay(100);
|
|
}
|
|
|
|
currentStep++;
|
|
await delay();
|
|
}
|
|
|
|
// --- NEW: F&F CLEARANCE LOOP (16 DEPARTMENTS) ---
|
|
if (!SHOULD_SKIP_CLEARANCES) {
|
|
log(13, 'Starting 16-Department F&F Clearance Flow for Termination...');
|
|
}
|
|
const terminationData = await apiRequest(`/termination/${terminationId}`, 'GET', null, adminToken);
|
|
const fnfId = terminationData.termination.fnfSettlement?.id;
|
|
|
|
if (!fnfId) {
|
|
log('SKIP', 'FnF Settlement not initialized for this termination case.');
|
|
} else if (!SHOULD_SKIP_CLEARANCES) {
|
|
const departments = [
|
|
{ name: 'Warranty Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No pending claims.' },
|
|
{ name: 'Accessories Department', status: 'Dues', amount: 15000, type: 'Receivable', remarks: 'Shortage in accessory stock.' },
|
|
{ name: 'Sales Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'Allocations transferred.' },
|
|
{ name: 'RTO Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' },
|
|
{ name: 'Service Department', status: 'Dues', amount: 8000, type: 'Receivable', remarks: 'Loaner vehicle damange charges.' },
|
|
{ name: 'Parts Department', status: 'Dues', amount: 20000, type: 'Payable', remarks: 'Return parts credit.' },
|
|
{ name: 'Finance Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No interest dues.' },
|
|
{ name: 'Insurance Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' },
|
|
{ name: 'Inventory Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'Inventory handed over.' },
|
|
{ name: 'Marketing Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' },
|
|
{ name: 'HR Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'Staff settlement clear.' },
|
|
{ name: 'IT Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'Hardware recovered.' },
|
|
{ name: 'Legal Department', status: 'Dues', amount: 50000, type: 'Receivable', remarks: 'Litigation cost recovery as per agreement.' },
|
|
{ name: 'Quality Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' },
|
|
{ name: 'Logistics Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' },
|
|
{ name: 'Customer Relations Department', status: 'Cleared', amount: 0, type: 'Receivable', remarks: 'No dues.' }
|
|
];
|
|
|
|
for (const dept of departments) {
|
|
log('13.1', `Clearing Dept: ${dept.name} [${dept.status}] - ${dept.remarks}`);
|
|
await apiRequest(`/termination/${terminationId}/clearance`, 'PUT', {
|
|
department: dept.name,
|
|
status: dept.status,
|
|
remarks: dept.remarks,
|
|
amount: dept.amount,
|
|
type: dept.type
|
|
}, adminToken);
|
|
await delay(100);
|
|
}
|
|
log(13, 'All 16 Departments Cleared for Termination.');
|
|
await delay();
|
|
}
|
|
|
|
|
|
console.log('[FINAL STEP] Verifying Terminated Status & Account Deactivation...');
|
|
const finalDetails = await apiRequest(`/termination/${terminationId}`, 'GET', null, adminToken);
|
|
console.log(`Final Stage REACHED: ${finalDetails.termination.currentStage}`);
|
|
|
|
// Fetch user data to verify deactivation
|
|
const userRes = await apiRequest(`/admin/users`, 'GET', null, adminToken);
|
|
const dealerUser = userRes.data.find(u => u.dealerId === targetDealer.id);
|
|
|
|
if (dealerUser && !dealerUser.isActive && dealerUser.status === 'inactive') {
|
|
console.log(`[VERIFICATION] Account ${dealerUser.email} successfully DEACTIVATED.`);
|
|
} else {
|
|
console.error(`[VERIFICATION] Failed: Account ${dealerUser?.email} is still active. Status: ${dealerUser?.status}`);
|
|
throw new Error('Automated account deactivation check failed.');
|
|
}
|
|
|
|
console.log('\n--- VERIFICATION SUCCESSFUL ---');
|
|
console.log('Outcome: DEALER TERMINATED & PORTAL ACCESS REVOKED');
|
|
process.exit(0);
|
|
|
|
} catch (error) {
|
|
console.error('Workflow failed:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
run();
|