'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;