136 lines
5.6 KiB
JavaScript
136 lines
5.6 KiB
JavaScript
'use strict';
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { verifyToken } = require('../middleware/auth');
|
|
const store = () => require('../services/salaryStore');
|
|
|
|
router.post('/upload', verifyToken, async (req, res) => {
|
|
try {
|
|
const { period, sheets } = req.body;
|
|
if (!period || !sheets || typeof sheets !== 'object')
|
|
return res.status(400).json({ success: false, message: 'period and sheets required' });
|
|
await store().clearPeriod(period);
|
|
const results = [];
|
|
for (const [sheetName, rawRows] of Object.entries(sheets)) {
|
|
const parsed = parseSheetRows(rawRows);
|
|
if (!parsed.length) { results.push({ sheet: sheetName, rows: 0, skipped: true }); continue; }
|
|
await store().saveSheetRows(period, sheetName, parsed);
|
|
results.push({ sheet: sheetName, rows: parsed.length });
|
|
}
|
|
res.json({ success: true, data: results });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/periods', verifyToken, async (req, res) => {
|
|
try { res.json({ success: true, data: await store().getPeriods() }); }
|
|
catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/sheets', verifyToken, async (req, res) => {
|
|
try { res.json({ success: true, data: await store().getSheets(req.query.period || '') }); }
|
|
catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/rows', verifyToken, async (req, res) => {
|
|
try { res.json({ success: true, data: await store().getRows(req.query.period || '', req.query.sheet || '') }); }
|
|
catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/config', verifyToken, async (req, res) => {
|
|
try {
|
|
const [config, bbRatios] = await Promise.all([store().getConfig(), store().getBBRatios()]);
|
|
res.json({ success: true, config, bbRatios });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.post('/config', verifyToken, async (req, res) => {
|
|
try {
|
|
const { entries = [], bbRatios = [] } = req.body;
|
|
if (entries.length) await store().saveConfig(entries);
|
|
for (const b of bbRatios) await store().saveBBRatio(b.sheet_name, b.bb_pct, b.capd_pct);
|
|
res.json({ success: true });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/summary', verifyToken, async (req, res) => {
|
|
try { res.json({ success: true, data: await store().getSummary(req.query.period || '') }); }
|
|
catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.get('/dept-mappings', verifyToken, async (req, res) => {
|
|
try {
|
|
const [mappings, depts] = await Promise.all([
|
|
store().getDeptMappings(),
|
|
req.query.period ? store().getDistinctDepts(req.query.period) : Promise.resolve([]),
|
|
]);
|
|
res.json({ success: true, mappings, depts });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
router.post('/dept-mapping', verifyToken, async (req, res) => {
|
|
try {
|
|
const { dept_name, cost_centre } = req.body;
|
|
if (!dept_name) return res.status(400).json({ success: false, message: 'dept_name required' });
|
|
await store().saveDeptMapping(dept_name, cost_centre || '');
|
|
res.json({ success: true });
|
|
} catch (err) { res.status(500).json({ success: false, message: err.message }); }
|
|
});
|
|
|
|
// Parse raw 2D array from client-side xlsx into structured employee rows
|
|
function parseSheetRows(rawRows) {
|
|
if (!Array.isArray(rawRows) || rawRows.length === 0) return [];
|
|
|
|
let hIdx = -1;
|
|
for (let i = 0; i < Math.min(rawRows.length, 25); i++) {
|
|
const row = (rawRows[i] || []).map(c => String(c || '').toLowerCase().trim());
|
|
const hasNet = row.some(c => c.includes('net pay') || c === 'netpay');
|
|
const hasId = row.some(c => c === 'name' || c.includes('employee code') || c.includes('emp code'));
|
|
if (hasNet && hasId) { hIdx = i; break; }
|
|
if (hasNet && row.filter(Boolean).length >= 8) { hIdx = i; break; }
|
|
}
|
|
if (hIdx < 0) return [];
|
|
|
|
const headers = (rawRows[hIdx] || []).map(c => String(c || '').toLowerCase().trim());
|
|
const find = (...kws) => {
|
|
for (const kw of kws) {
|
|
const i = headers.findIndex(h => h.includes(kw));
|
|
if (i >= 0) return i;
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
const cSr = find('sr.no', 'sr no', 'srno');
|
|
const cCode = find('employee code', 'emp code', 'empcode');
|
|
const cName = find('name');
|
|
// COST CENTER column (sheets 6-8 style) takes priority over generic dept/section
|
|
const cCC = find('cost center', 'cost centre', 'costcenter', 'cost_center');
|
|
const cDept = cCC >= 0 ? cCC : find('dept', 'deptt', 'section', 'sect');
|
|
const cGross = find('gross salary', 'total gross', 'gross sal', 'grosssalary');
|
|
const cNet = find('net pay', 'netpay', 'net_pay');
|
|
if (cNet < 0) return [];
|
|
|
|
const rows = [];
|
|
for (let i = hIdx + 1; i < rawRows.length; i++) {
|
|
const row = rawRows[i] || [];
|
|
const sr = String(row[cSr] || '').trim();
|
|
const name = cName >= 0 ? String(row[cName] || '').trim() : '';
|
|
const net = parseFloat(row[cNet]) || 0;
|
|
|
|
if (!name) continue;
|
|
const first = String(row[0] || '').toLowerCase();
|
|
if (first.includes('total') || name.toLowerCase().includes('total')) continue;
|
|
|
|
rows.push({
|
|
sr_no: sr,
|
|
employee_code: cCode >= 0 ? String(row[cCode] || '').trim() : '',
|
|
employee_name: name,
|
|
department: cDept >= 0 ? String(row[cDept] || '').trim() : '',
|
|
gross_salary: cGross >= 0 ? (parseFloat(row[cGross]) || 0) : 0,
|
|
net_pay: net,
|
|
});
|
|
}
|
|
return rows.filter(r => r.employee_name);
|
|
}
|
|
|
|
module.exports = router;
|