alpe-temp 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -1,7 +1,7 @@
1
1
  PORT=3000
2
2
  NODE_ENV=development
3
3
 
4
- MONGODB_URI=mongodb://localhost:27017/EPMS
4
+ MONGODB_URI=mongodb://localhost:27017/HRMS
5
5
 
6
6
  JWT_SECRET=your-secret-key-change-this
7
7
  JWT_EXPIRES_IN=7d
File without changes
@@ -0,0 +1,6 @@
1
+ MongoDB connected → localhost
2
+
3
+ Server running on http://localhost:3000
4
+ Environment : development
5
+ Health : http://localhost:3000/health
6
+
@@ -1,3 +1,4 @@
1
+ const ExcelJS = require('exceljs');
1
2
  const Employee = require('../employee/employee.model');
2
3
  const res_ = require('../../utils/response');
3
4
 
@@ -24,6 +25,152 @@ const ReportsController = {
24
25
  return res_.error(res, err.message, 500);
25
26
  }
26
27
  },
28
+
29
+ async employeesOnLeaveExcel(req, res) {
30
+ try {
31
+ const employees = await Employee.find({ empStatus: 'on leave' })
32
+ .populate('department')
33
+ .populate('position')
34
+ .sort({ 'department.departName': 1, empFirstName: 1 });
35
+
36
+ const workbook = new ExcelJS.Workbook();
37
+ workbook.creator = 'DAB Enterprise HRMS';
38
+ workbook.created = new Date();
39
+
40
+ const ws = workbook.addWorksheet('Employees on Leave', {
41
+ pageSetup: { orientation: 'landscape', fitToPage: true },
42
+ });
43
+
44
+ const primaryColor = '1F4E79';
45
+ const accentColor = 'D6E4F0';
46
+ const borderStyle = {
47
+ style: 'thin',
48
+ color: { argb: 'B0B0B0' },
49
+ };
50
+ const allBorders = {
51
+ top: borderStyle,
52
+ bottom: borderStyle,
53
+ left: borderStyle,
54
+ right: borderStyle,
55
+ };
56
+
57
+ const mergeAndStyle = (ws, rowNum, colStart, colEnd, value, opts = {}) => {
58
+ const cell = ws.getCell(`${colStart}${rowNum}`);
59
+ if (colStart !== colEnd) ws.mergeCells(`${colStart}${rowNum}:${colEnd}${rowNum}`);
60
+ cell.value = value;
61
+ cell.font = { name: 'Calibri', size: opts.fontSize || 11, bold: opts.bold || false, color: opts.color ? { argb: opts.color } : undefined };
62
+ cell.alignment = { vertical: 'middle', horizontal: opts.align || 'left', wrapText: opts.wrap || false };
63
+ if (opts.fill) cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: opts.fill } };
64
+ if (opts.border !== false) cell.border = allBorders;
65
+ return cell;
66
+ };
67
+
68
+ ws.getColumn(1).width = 5;
69
+ ws.getColumn(2).width = 16;
70
+ ws.getColumn(3).width = 16;
71
+ ws.getColumn(4).width = 12;
72
+ ws.getColumn(5).width = 14;
73
+ ws.getColumn(6).width = 20;
74
+ ws.getColumn(7).width = 16;
75
+ ws.getColumn(8).width = 20;
76
+
77
+ const row1 = ws.getRow(1);
78
+ row1.height = 36;
79
+ mergeAndStyle(ws, 1, 'A', 'H', 'DAB Enterprise LTD — Employee Status Report', {
80
+ fontSize: 16, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'center',
81
+ });
82
+
83
+ ws.getRow(2).height = 22;
84
+ mergeAndStyle(ws, 2, 'A', 'H', `Employees currently on leave — Generated: ${new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', yyyy: 'numeric' })} at ${new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}`, {
85
+ fontSize: 10, color: primaryColor, fill: accentColor, align: 'center',
86
+ });
87
+
88
+ let rowNum = 4;
89
+
90
+ const grouped = {};
91
+ for (const emp of employees) {
92
+ const deptName = emp.department ? emp.department.departName : 'Unassigned';
93
+ if (!grouped[deptName]) grouped[deptName] = [];
94
+ grouped[deptName].push(emp);
95
+ }
96
+
97
+ const deptNames = Object.keys(grouped);
98
+ if (deptNames.length === 0) {
99
+ mergeAndStyle(ws, rowNum, 'A', 'H', 'No employees are currently on leave.', {
100
+ fontSize: 11, align: 'center', border: true,
101
+ });
102
+ } else {
103
+ deptNames.forEach((deptName, di) => {
104
+ const emps = grouped[deptName];
105
+
106
+ if (di > 0) rowNum += 1;
107
+
108
+ ws.getRow(rowNum).height = 24;
109
+ mergeAndStyle(ws, rowNum, 'A', 'E', deptName, {
110
+ fontSize: 12, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'left',
111
+ });
112
+ mergeAndStyle(ws, rowNum, 'F', 'H', `${emps.length} employee${emps.length > 1 ? 's' : ''}`, {
113
+ fontSize: 12, bold: true, color: 'FFFFFF', fill: primaryColor, align: 'right',
114
+ });
115
+
116
+ rowNum += 1;
117
+ ws.getRow(rowNum).height = 20;
118
+
119
+ const headers = ['#', 'First Name', 'Last Name', 'Gender', 'Position', 'Email', 'Phone', 'Department'];
120
+ const cols = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
121
+ const headerFill = '2E75B6';
122
+ headers.forEach((h, i) => {
123
+ mergeAndStyle(ws, rowNum, cols[i], cols[i], h, {
124
+ fontSize: 10, bold: true, color: 'FFFFFF', fill: headerFill, align: 'center',
125
+ });
126
+ });
127
+
128
+ emps.forEach((emp, idx) => {
129
+ rowNum += 1;
130
+ ws.getRow(rowNum).height = 20;
131
+ const rowFill = idx % 2 === 0 ? 'F2F7FB' : undefined;
132
+
133
+ const data = [
134
+ (idx + 1).toString(),
135
+ emp.empFirstName || '',
136
+ emp.empLastName || '',
137
+ emp.empGender ? emp.empGender.charAt(0).toUpperCase() + emp.empGender.slice(1) : '',
138
+ emp.position ? emp.position.posName : '\u2014',
139
+ emp.empEmail || '\u2014',
140
+ emp.empTelephone || '\u2014',
141
+ emp.department ? emp.department.departName : 'Unassigned',
142
+ ];
143
+
144
+ data.forEach((val, i) => {
145
+ mergeAndStyle(ws, rowNum, cols[i], cols[i], val, {
146
+ fontSize: 10, fill: rowFill, align: i === 0 ? 'center' : 'left',
147
+ });
148
+ });
149
+ });
150
+ });
151
+
152
+ rowNum += 2;
153
+ ws.getRow(rowNum).height = 22;
154
+ mergeAndStyle(ws, rowNum, 'A', 'H', `Total employees on leave: ${employees.length}`, {
155
+ fontSize: 11, bold: true, color: primaryColor, fill: accentColor, align: 'right',
156
+ });
157
+
158
+ rowNum += 2;
159
+ ws.getRow(rowNum).height = 18;
160
+ mergeAndStyle(ws, rowNum, 'A', 'H', 'Report generated by DAB Enterprise HRMS System', {
161
+ fontSize: 9, color: '808080', align: 'center', border: false,
162
+ });
163
+ }
164
+
165
+ res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
166
+ res.setHeader('Content-Disposition', `attachment; filename=employees-on-leave-report-${new Date().toISOString().slice(0, 10)}.xlsx`);
167
+
168
+ await workbook.xlsx.write(res);
169
+ res.end();
170
+ } catch (err) {
171
+ return res_.error(res, err.message, 500);
172
+ }
173
+ },
27
174
  };
28
175
 
29
176
  module.exports = ReportsController;
@@ -6,5 +6,6 @@ const router = Router();
6
6
  router.use(authenticate);
7
7
 
8
8
  router.get('/employees-on-leave', ReportsController.employeesOnLeave);
9
+ router.get('/employees-on-leave/excel', ReportsController.employeesOnLeaveExcel);
9
10
 
10
11
  module.exports = router;
@@ -0,0 +1,294 @@
1
+ const http = require('http');
2
+
3
+ const BASE = 'http://localhost:3000';
4
+ let sessionCookie = '';
5
+
6
+ async function request(method, path, body = null) {
7
+ return new Promise((resolve, reject) => {
8
+ const url = new URL(path, BASE);
9
+ const opts = {
10
+ method,
11
+ hostname: url.hostname,
12
+ port: url.port,
13
+ path: url.pathname + url.search,
14
+ headers: { 'Content-Type': 'application/json' },
15
+ };
16
+ if (sessionCookie) opts.headers.Cookie = sessionCookie;
17
+
18
+ const req = http.request(opts, (res) => {
19
+ let data = '';
20
+ const setCookie = res.headers['set-cookie'];
21
+ if (setCookie) {
22
+ sessionCookie = setCookie.map(c => c.split(';')[0]).join('; ');
23
+ }
24
+ res.on('data', (chunk) => data += chunk);
25
+ res.on('end', () => {
26
+ try {
27
+ resolve({ status: res.statusCode, body: JSON.parse(data) });
28
+ } catch {
29
+ resolve({ status: res.statusCode, body: data });
30
+ }
31
+ });
32
+ });
33
+ req.on('error', reject);
34
+ if (body) req.write(JSON.stringify(body));
35
+ req.end();
36
+ });
37
+ }
38
+
39
+ let testsPassed = 0;
40
+ let testsFailed = 0;
41
+
42
+ function test(name, fn) {
43
+ return fn().then((result) => {
44
+ if (result.pass) {
45
+ console.log(` ✓ ${name}`);
46
+ testsPassed++;
47
+ } else {
48
+ console.log(` ✗ ${name}: ${result.reason}`);
49
+ testsFailed++;
50
+ }
51
+ }).catch((err) => {
52
+ console.log(` ✗ ${name}: threw — ${err.message}`);
53
+ testsFailed++;
54
+ });
55
+ }
56
+
57
+ async function run() {
58
+ console.log('\n══════════════════════════════════════════');
59
+ console.log(' COMPREHENSIVE API ROUTE TEST');
60
+ console.log('══════════════════════════════════════════\n');
61
+
62
+ // ─── 1. Health ─────────────────────────────────────
63
+ console.log('── Health ────────────────────────────────');
64
+ await test('GET /health returns ok', async () => {
65
+ const res = await request('GET', '/health');
66
+ return res.status === 200 && res.body.status === 'ok'
67
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
68
+ });
69
+
70
+ // ─── 2. Auth Routes ────────────────────────────────
71
+ console.log('\n── Auth ──────────────────────────────────');
72
+ const testUser = { userName: 'testuser_' + Date.now(), password: 'test1234' };
73
+ let createdUser = null;
74
+
75
+ await test('POST /api/auth/register creates a user', async () => {
76
+ const res = await request('POST', '/api/auth/register', testUser);
77
+ if (res.status === 201 && res.body.success) {
78
+ createdUser = res.body.data;
79
+ return { pass: true };
80
+ }
81
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
82
+ });
83
+
84
+ await test('POST /api/auth/register rejects duplicate username', async () => {
85
+ const res = await request('POST', '/api/auth/register', testUser);
86
+ return res.status === 409
87
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
88
+ });
89
+
90
+ await test('POST /api/auth/login succeeds', async () => {
91
+ const res = await request('POST', '/api/auth/login', testUser);
92
+ return res.status === 200 && res.body.success
93
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
94
+ });
95
+
96
+ await test('POST /api/auth/login rejects bad password', async () => {
97
+ const res = await request('POST', '/api/auth/login', { userName: testUser.userName, password: 'wrong' });
98
+ return res.status === 401
99
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
100
+ });
101
+
102
+ await test('GET /api/auth/me returns logged-in user', async () => {
103
+ const res = await request('GET', '/api/auth/me');
104
+ return res.status === 200 && res.body.data && res.body.data.user
105
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
106
+ });
107
+
108
+ // ─── 3. Employee Routes ────────────────────────────
109
+ console.log('\n── Employees ─────────────────────────────');
110
+ let createdEmployeeId = null;
111
+
112
+ await test('GET /api/employees lists employees', async () => {
113
+ const res = await request('GET', '/api/employees');
114
+ if (res.status === 200 && res.body.success) {
115
+ if (res.body.data.employees.length > 0) createdEmployeeId = res.body.data.employees[0]._id;
116
+ return { pass: true };
117
+ }
118
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
119
+ });
120
+
121
+ await test('POST /api/employees creates employee', async () => {
122
+ const res = await request('POST', '/api/employees', {
123
+ empFirstName: 'Test', empLastName: 'User',
124
+ empGender: 'male', empDateOfBirth: '1990-01-01',
125
+ empEmail: 'test@test.com', empTelephone: '0788000000',
126
+ empHireDate: '2024-01-01', empStatus: 'on mission',
127
+ });
128
+ if (res.status === 201 && res.body.success) {
129
+ createdEmployeeId = res.body.data._id;
130
+ return { pass: true };
131
+ }
132
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
133
+ });
134
+
135
+ await test('GET /api/employees/:id returns employee', async () => {
136
+ if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
137
+ const res = await request('GET', `/api/employees/${createdEmployeeId}`);
138
+ return res.status === 200 && res.body.success
139
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
140
+ });
141
+
142
+ await test('PUT /api/employees/:id updates employee', async () => {
143
+ if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
144
+ const res = await request('PUT', `/api/employees/${createdEmployeeId}`, { empFirstName: 'Updated' });
145
+ return res.status === 200 && res.body.success
146
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
147
+ });
148
+
149
+ await test('DELETE /api/employees/:id deletes employee', async () => {
150
+ if (!createdEmployeeId) return { pass: false, reason: 'No employee ID' };
151
+ const res = await request('DELETE', `/api/employees/${createdEmployeeId}`);
152
+ return res.status === 200 && res.body.success
153
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
154
+ });
155
+
156
+ await test('GET /api/employees?search= filters results', async () => {
157
+ const res = await request('GET', '/api/employees?search=alice');
158
+ return res.status === 200 && res.body.success
159
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
160
+ });
161
+
162
+ // ─── 4. Department Routes ─────────────────────────
163
+ console.log('\n── Departments ───────────────────────────');
164
+ let createdDeptId = null;
165
+
166
+ await test('GET /api/departments lists departments', async () => {
167
+ const res = await request('GET', '/api/departments');
168
+ if (res.status === 200 && res.body.success) {
169
+ if (res.body.data.departments.length > 0) createdDeptId = res.body.data.departments[0]._id;
170
+ return { pass: true };
171
+ }
172
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
173
+ });
174
+
175
+ await test('POST /api/departments creates department', async () => {
176
+ const res = await request('POST', '/api/departments', { departName: 'Test Dept ' + Date.now() });
177
+ if (res.status === 201 && res.body.success) {
178
+ createdDeptId = res.body.data._id;
179
+ return { pass: true };
180
+ }
181
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
182
+ });
183
+
184
+ await test('GET /api/departments/:id returns department', async () => {
185
+ if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
186
+ const res = await request('GET', `/api/departments/${createdDeptId}`);
187
+ return res.status === 200 && res.body.success
188
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
189
+ });
190
+
191
+ await test('PUT /api/departments/:id updates department', async () => {
192
+ if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
193
+ const res = await request('PUT', `/api/departments/${createdDeptId}`, { departName: 'Updated Dept' });
194
+ return res.status === 200 && res.body.success
195
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
196
+ });
197
+
198
+ await test('DELETE /api/departments/:id deletes department', async () => {
199
+ if (!createdDeptId) return { pass: false, reason: 'No dept ID' };
200
+ const res = await request('DELETE', `/api/departments/${createdDeptId}`);
201
+ return res.status === 200 && res.body.success
202
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
203
+ });
204
+
205
+ // ─── 5. Position Routes ───────────────────────────
206
+ console.log('\n── Positions ─────────────────────────────');
207
+ let createdPosId = null;
208
+
209
+ await test('GET /api/positions lists positions', async () => {
210
+ const res = await request('GET', '/api/positions');
211
+ if (res.status === 200 && res.body.success) {
212
+ if (res.body.data.positions.length > 0) createdPosId = res.body.data.positions[0]._id;
213
+ return { pass: true };
214
+ }
215
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
216
+ });
217
+
218
+ await test('POST /api/positions creates position', async () => {
219
+ const res = await request('POST', '/api/positions', { posName: 'Test Pos ' + Date.now(), requiredQualification: 'Test' });
220
+ if (res.status === 201 && res.body.success) {
221
+ createdPosId = res.body.data._id;
222
+ return { pass: true };
223
+ }
224
+ return { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
225
+ });
226
+
227
+ await test('GET /api/positions/:id returns position', async () => {
228
+ if (!createdPosId) return { pass: false, reason: 'No pos ID' };
229
+ const res = await request('GET', `/api/positions/${createdPosId}`);
230
+ return res.status === 200 && res.body.success
231
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
232
+ });
233
+
234
+ await test('PUT /api/positions/:id updates position', async () => {
235
+ if (!createdPosId) return { pass: false, reason: 'No pos ID' };
236
+ const res = await request('PUT', `/api/positions/${createdPosId}`, { posName: 'Updated Pos' });
237
+ return res.status === 200 && res.body.success
238
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
239
+ });
240
+
241
+ await test('DELETE /api/positions/:id deletes position', async () => {
242
+ if (!createdPosId) return { pass: false, reason: 'No pos ID' };
243
+ const res = await request('DELETE', `/api/positions/${createdPosId}`);
244
+ return res.status === 200 && res.body.success
245
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
246
+ });
247
+
248
+ // ─── 6. Report Routes ─────────────────────────────
249
+ console.log('\n── Reports ───────────────────────────────');
250
+
251
+ await test('GET /api/reports/employees-on-leave returns data', async () => {
252
+ const res = await request('GET', '/api/reports/employees-on-leave');
253
+ return res.status === 200 && res.body.success
254
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
255
+ });
256
+
257
+ await test('GET /api/reports/employees-on-leave/excel returns file', async () => {
258
+ const res = await request('GET', '/api/reports/employees-on-leave/excel');
259
+ return res.status === 200 && res.body.includes('workbook')
260
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
261
+ });
262
+
263
+ // ─── 7. Auth Logout ───────────────────────────────
264
+ console.log('\n── Auth Logout ───────────────────────────');
265
+
266
+ await test('POST /api/auth/logout succeeds', async () => {
267
+ const res = await request('POST', '/api/auth/logout');
268
+ return res.status === 200 && res.body.success
269
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}: ${JSON.stringify(res.body)}` };
270
+ });
271
+
272
+ await test('GET /api/employees fails after logout (no auth)', async () => {
273
+ const res = await request('GET', '/api/employees');
274
+ return res.status === 401
275
+ ? { pass: true } : { pass: false, reason: `Got ${res.status} (expected 401)` };
276
+ });
277
+
278
+ // ─── 8. 404 Handler ───────────────────────────────
279
+ console.log('\n── 404 Handling ──────────────────────────');
280
+
281
+ await test('GET /api/nonexistent returns 404', async () => {
282
+ const res = await request('GET', '/api/nonexistent');
283
+ return res.status === 404
284
+ ? { pass: true } : { pass: false, reason: `Got ${res.status}` };
285
+ });
286
+
287
+ // ─── Summary ──────────────────────────────────────
288
+ console.log('\n══════════════════════════════════════════');
289
+ console.log(` Results: ${testsPassed} passed, ${testsFailed} failed, ${testsPassed + testsFailed} total`);
290
+ console.log('══════════════════════════════════════════\n');
291
+ process.exit(testsFailed > 0 ? 1 : 0);
292
+ }
293
+
294
+ run();