287 lines
14 KiB
JavaScript
287 lines
14 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 SHOULD_SKIP_FINAL_SETTLEMENT = String(args.skipSettlement || 'false') === 'true';
|
|
|
|
const EMAILS = {
|
|
DD_ADMIN: 'lince@gmail.com',
|
|
DEALER: 'dealer@royalenfield.com',
|
|
ASM: 'asm.sdelhi@royalenfield.com',
|
|
RBM: 'rbm.ncr@royalenfield.com',
|
|
ZBH: 'yashwin@gmail.com',
|
|
DD_LEAD: 'ddlead@royalenfield.com',
|
|
NBH: 'nbh@royalenfield.com',
|
|
LEGAL: 'legal@royalenfield.com',
|
|
FINANCE: 'finance@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 isInternal = email.endsWith('@royalenfield.com') ||
|
|
email === 'lince@gmail.com' ||
|
|
email === 'yashwin@gmail.com';
|
|
const password = isInternal ? 'Admin@123' : 'Dealer@123';
|
|
|
|
const data = await apiRequest('/auth/login', 'POST', { email, 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 RESIGNATION E2E FLOW ---');
|
|
|
|
const adminToken = await login(EMAILS.DD_ADMIN);
|
|
const appsRes = await apiRequest('/onboarding/applications', 'GET', null, adminToken);
|
|
const onboardedApps = appsRes.data.filter(a => (a.overallStatus || a.status || '').toLowerCase() === 'onboarded');
|
|
|
|
if (onboardedApps.length === 0) throw new Error('No onboarded applications found for resignation test.');
|
|
|
|
// 1.0 Find an active Dealer and Login
|
|
let targetApp = null;
|
|
let dealerToken = null;
|
|
|
|
for (const app of onboardedApps) {
|
|
try {
|
|
process.stdout.write(`Testing login for ${app.email}... `);
|
|
const dealerData = await apiRequest('/auth/login', 'POST', {
|
|
email: app.email,
|
|
password: 'Dealer@123'
|
|
});
|
|
dealerToken = dealerData.token;
|
|
targetApp = app;
|
|
console.log('SUCCESS');
|
|
break;
|
|
} catch (e) {
|
|
console.log('FAILED (Deactivated)');
|
|
}
|
|
}
|
|
|
|
if (!targetApp) throw new Error('All onboarded applications are deactivated. Run onboarding first.');
|
|
|
|
console.log(`Targeting Application: ${targetApp.applicantName} (${targetApp.id}) - Email: ${targetApp.email}`);
|
|
await delay();
|
|
|
|
// 1.1 Discover Dealer's Outlet
|
|
console.log(`[STEP 1.1] Discovering Outlets for Dealer...`);
|
|
const dealerDashboard = await apiRequest('/dealer/dashboard', 'GET', null, dealerToken);
|
|
const targetOutlet = dealerDashboard.data.outlets[0];
|
|
await delay();
|
|
|
|
if (!targetOutlet) throw new Error('No outlets found for this dealer. Ensure they are fully onboarded.');
|
|
console.log(`Found Target Outlet: ${targetOutlet.name} (${targetOutlet.code})`);
|
|
|
|
console.log(`[STEP 1.2] Dealer Submitting Resignation for Outlet...`);
|
|
let resignationId = args.resignationId;
|
|
if (!resignationId) {
|
|
try {
|
|
const createRes = await apiRequest('/self-service/resignations', 'POST', {
|
|
outletId: targetOutlet.id,
|
|
resignationType: 'Voluntary',
|
|
lastOperationalDateSales: new Date().toISOString().split('T')[0],
|
|
lastOperationalDateServices: new Date().toISOString().split('T')[0],
|
|
reason: 'Focusing on other business ventures',
|
|
remarks: 'Initiating voluntary resignation for E2E validation.'
|
|
}, dealerToken);
|
|
resignationId = createRes.resignation.id;
|
|
log(1, `Resignation Created. ID: ${resignationId}`);
|
|
} catch (e) {
|
|
if (e.message.includes('already has an active resignation request')) {
|
|
console.log(`[STEP 1.2] Active resignation already exists. Fetching...`);
|
|
const activeResRes = await apiRequest('/self-service/resignations', 'GET', null, dealerToken);
|
|
const activeRes = (activeResRes.resignations || activeResRes.data).find(r => r.outletId === targetOutlet.id && !['Completed', 'Rejected'].includes(r.status));
|
|
resignationId = activeRes.id;
|
|
log(1, `Resuming with existing Resignation: ${resignationId}`);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
} else {
|
|
log(1, `Resuming provided resignation: ${resignationId}`);
|
|
}
|
|
|
|
await delay();
|
|
|
|
const approvals = [
|
|
{ name: 'ASM', email: EMAILS.ASM, remarks: 'Verified physical assets and dealer intent. Recommended for resignation.' },
|
|
{ name: 'RBM', email: EMAILS.RBM, remarks: 'No active sales pipeline issues in the territory. Transition plan discussed.' },
|
|
{ name: 'ZBH', email: EMAILS.ZBH, remarks: 'Zone-level resource reallocation planned. Approved.' },
|
|
{ name: 'DD Lead', email: EMAILS.DD_LEAD, remarks: 'Dealer development criteria satisfied for exit.' },
|
|
{ name: 'NBH', email: EMAILS.NBH, remarks: 'HQ clearance granted. Proceed with F&F initiation.' },
|
|
{ name: 'DD Admin', email: EMAILS.DD_ADMIN, remarks: 'Administrative checks complete. Initiating termination of commercial contract.' },
|
|
{ name: 'Legal Admin', email: EMAILS.LEGAL, remarks: 'Legal documentation verified. No active litigation found.' }
|
|
];
|
|
|
|
// Fetch resignation data to determine current stage for skipping
|
|
const resignationData = await apiRequest(`/self-service/resignations/${resignationId}`, 'GET', null, adminToken);
|
|
const currentStage = resignationData.resignation.currentStage;
|
|
|
|
console.log(`Current Stage: ${currentStage}`);
|
|
|
|
const stageOrder = [
|
|
'ASM', 'RBM', 'ZBH', 'DD Lead', 'NBH', 'DD Admin', 'Legal Admin', 'F&F Initiated', 'Completed'
|
|
];
|
|
|
|
const startIndex = stageOrder.indexOf(currentStage) === -1 ? 0 : stageOrder.indexOf(currentStage);
|
|
|
|
let currentStep = 2;
|
|
for (let i = startIndex; i < approvals.length; i++) {
|
|
const actor = approvals[i];
|
|
log(currentStep, `${actor.name} (${actor.email}) approving...`);
|
|
const token = await login(actor.email);
|
|
const res = await apiRequest(`/self-service/resignations/${resignationId}/approve`, 'PUT', {
|
|
remarks: actor.remarks,
|
|
force: true
|
|
}, token);
|
|
log(currentStep, `${actor.name} Result: ${res.message || 'SUCCESS'}`);
|
|
currentStep++;
|
|
await delay();
|
|
}
|
|
|
|
// --- NEW: F&F CLEARANCE LOOP (16 DEPARTMENTS) ---
|
|
if (!SHOULD_SKIP_CLEARANCES) {
|
|
console.log('[STEP 9] Starting 16-Department F&F Clearance Flow...');
|
|
}
|
|
|
|
// Re-fetch to ensure we have the F&F ID regardless of start point
|
|
const finalResData = await apiRequest(`/self-service/resignations/${resignationId}`, 'GET', null, adminToken);
|
|
const fnfId = finalResData.resignation.settlement?.id;
|
|
|
|
if (!fnfId) {
|
|
throw new Error(`F&F Settlement ID not found for Resignation ${resignationId}. Ensure it reached F&F Initiation stage.`);
|
|
}
|
|
|
|
console.log(`F&F Settlement ID: ${fnfId}`);
|
|
await delay();
|
|
|
|
|
|
|
|
const departments = [
|
|
{ name: 'Warranty Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'No pending claims.' },
|
|
{ name: 'Accessories Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Stock returned and verified.' },
|
|
{ name: 'Sales Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Allocations transferred.' },
|
|
{ name: 'RTO Department', status: 'Dues', amount: 1500, type: 'Recovery', remarks: 'Pending RTO tax recovery.' },
|
|
{ name: 'Service Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Service tools handed over.' },
|
|
{ name: 'Parts Department', status: 'Dues', amount: 45000, type: 'Payable', remarks: 'Parts credit note adjustment.' },
|
|
{ name: 'Finance Department', status: 'Dues', amount: 25000, type: 'Recovery', remarks: 'Short-term loan interest.' },
|
|
{ name: 'Insurance Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Policy renewals handled.' },
|
|
{ name: 'Inventory Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Physical inventory reconciled.' },
|
|
{ name: 'Marketing Department', status: 'Dues', amount: 5000, type: 'Recovery', remarks: 'Glow-sign board removal cost.' },
|
|
{ name: 'HR Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Dealer staff settlement verified.' },
|
|
{ name: 'IT Department', status: 'Dues', amount: 12000, type: 'Recovery', remarks: 'Laptop / DMS hardware dues.' },
|
|
{ name: 'Legal Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Legal NOC issued.' },
|
|
{ name: 'Quality Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Quality audit passed.' },
|
|
{ name: 'Logistics Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Last vehicle transit clear.' },
|
|
{ name: 'Customer Relations Department', status: 'Cleared', amount: 0, type: 'Recovery', remarks: 'Customer complaints resolved.' }
|
|
];
|
|
|
|
if (!SHOULD_SKIP_CLEARANCES) {
|
|
for (const dept of departments) {
|
|
log('9.1', `Clearing Dept: ${dept.name} [${dept.status}] - ${dept.remarks}`);
|
|
await apiRequest(`/self-service/resignations/${resignationId}/clearance`, 'PUT', {
|
|
department: dept.name,
|
|
status: dept.status,
|
|
remarks: dept.remarks,
|
|
amount: dept.amount,
|
|
type: dept.type
|
|
}, adminToken);
|
|
await delay(100);
|
|
}
|
|
log(9, 'All 16 Departments Cleared.');
|
|
await delay();
|
|
}
|
|
|
|
// --- FINAL FINANCE SETTLEMENT ---
|
|
if (!SHOULD_SKIP_FINAL_SETTLEMENT) {
|
|
console.log('[STEP 10] Finance Finalizing Settlement...');
|
|
const financeToken = await login(EMAILS.FINANCE);
|
|
await apiRequest(`/settlement/fnf/${fnfId}`, 'PUT', {
|
|
status: 'Completed',
|
|
finalSettlementAmount: Number(args.finalSettlementAmount || 415173),
|
|
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] 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
|
|
console.log(`[FINAL STEP] Verifying Account Deactivation...`);
|
|
|
|
// Get updated user status
|
|
const userRes = await apiRequest('/admin/users', 'GET', null, adminToken);
|
|
|
|
// Fetch dealer to get its associated 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}`);
|
|
} else {
|
|
console.log(`[VERIFICATION] Failed: Account ${targetApp.email} is still active. Status: ${dealerU?.status || 'unknown'}`);
|
|
throw new Error('Automated account deactivation check failed.');
|
|
}
|
|
|
|
console.log('--- DEALER RESIGNATION E2E FLOW COMPLETED SUCCESSFULLY ---');
|
|
|
|
} catch (error) {
|
|
console.error('Workflow failed:', error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
run();
|