Files
sap-erp/routes/salary.js
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

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;