alpe-temp 1.0.2 → 1.0.3
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/backend-project/package-lock.json +131 -0
- package/backend-project/package.json +3 -1
- package/backend-project/src/app.js +33 -55
- package/backend-project/src/config/app.config.js +1 -49
- package/backend-project/src/config/env.js +2 -10
- package/backend-project/src/middleware/auth.middleware.js +3 -26
- package/backend-project/src/modules/auth/auth.controller.js +15 -19
- package/backend-project/src/modules/auth/auth.routes.js +4 -8
- package/backend-project/src/modules/auth/auth.service.js +9 -31
- package/backend-project/src/modules/auth/user.model.js +10 -33
- package/backend-project/src/modules/department/department.controller.js +0 -4
- package/backend-project/src/modules/department/department.model.js +1 -4
- package/backend-project/src/modules/department/department.routes.js +0 -1
- package/backend-project/src/modules/department/department.service.js +1 -9
- package/backend-project/src/modules/employee/employee.controller.js +2 -10
- package/backend-project/src/modules/employee/employee.model.js +15 -9
- package/backend-project/src/modules/employee/employee.routes.js +4 -6
- package/backend-project/src/modules/employee/employee.service.js +20 -5
- package/backend-project/src/modules/position/position.controller.js +50 -0
- package/backend-project/src/modules/position/position.model.js +8 -0
- package/backend-project/src/modules/position/position.routes.js +14 -0
- package/backend-project/src/modules/position/position.service.js +21 -0
- package/backend-project/src/modules/reports/reports.controller.js +16 -28
- package/backend-project/src/modules/reports/reports.routes.js +2 -2
- package/backend-project/src/seed.js +69 -15
- package/backend-project/src/utils/token.js +1 -27
- package/frontend-project/dist/assets/index-BXwcQ8Za.css +1 -0
- package/frontend-project/dist/assets/index-Bo0aORq7.js +20 -0
- package/frontend-project/dist/index.html +3 -3
- package/frontend-project/index.html +1 -1
- package/frontend-project/src/Auth/Login.jsx +15 -25
- package/frontend-project/src/Auth/Register.jsx +92 -183
- package/frontend-project/src/Intro.jsx +4 -9
- package/frontend-project/src/LayOut.jsx +10 -23
- package/frontend-project/src/api/ApiClient.js +19 -60
- package/frontend-project/src/layouts/BottomNav.jsx +22 -105
- package/frontend-project/src/layouts/TopNav.jsx +19 -98
- package/frontend-project/src/layouts/useShell.js +30 -44
- package/frontend-project/src/main.jsx +2 -3
- package/frontend-project/src/pages/Department.jsx +21 -58
- package/frontend-project/src/pages/Employee.jsx +131 -113
- package/frontend-project/src/pages/Home.jsx +36 -36
- package/frontend-project/src/pages/Position.jsx +161 -0
- package/frontend-project/src/pages/Reports.jsx +81 -68
- package/package.json +4 -2
- package/server-test-err.txt +0 -0
- package/server-test-out.txt +0 -0
- package/backend-project/src/modules/_example/example.controller.js +0 -82
- package/backend-project/src/modules/_example/example.model.js +0 -47
- package/backend-project/src/modules/_example/example.routes.js +0 -43
- package/backend-project/src/modules/_example/example.service.js +0 -58
- package/backend-project/src/modules/excel/excel.controller.js +0 -61
- package/backend-project/src/modules/excel/excel.routes.js +0 -13
- package/backend-project/src/modules/excel/excel.service.js +0 -303
- package/backend-project/src/modules/salary/salary.controller.js +0 -70
- package/backend-project/src/modules/salary/salary.model.js +0 -23
- package/backend-project/src/modules/salary/salary.routes.js +0 -16
- package/backend-project/src/modules/salary/salary.service.js +0 -44
- package/frontend-project/dist/assets/index-B08ICGra.js +0 -20
- package/frontend-project/dist/assets/index-D_cqT2Z6.css +0 -1
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
const { Router } = require('express');
|
|
2
|
-
const ExcelController = require('./excel.controller');
|
|
3
|
-
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
-
|
|
5
|
-
const router = Router();
|
|
6
|
-
|
|
7
|
-
// All export routes require authentication
|
|
8
|
-
router.use(authenticate);
|
|
9
|
-
|
|
10
|
-
router.get('/export/users', ExcelController.exportUsers);
|
|
11
|
-
router.post('/export/custom', ExcelController.exportCustom);
|
|
12
|
-
|
|
13
|
-
module.exports = router;
|
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* excel.service.js
|
|
3
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
-
* A fast, reusable Excel export utility built on ExcelJS.
|
|
5
|
-
*
|
|
6
|
-
* QUICK START
|
|
7
|
-
* ───────────
|
|
8
|
-
* const ExcelService = require('./excel.service');
|
|
9
|
-
*
|
|
10
|
-
* // 1. Stream directly to an HTTP response
|
|
11
|
-
* await ExcelService.exportToResponse(res, 'users-report', [
|
|
12
|
-
* {
|
|
13
|
-
* name: 'Users',
|
|
14
|
-
* columns: [
|
|
15
|
-
* { header: 'ID', key: 'id', width: 36 },
|
|
16
|
-
* { header: 'Name', key: 'name', width: 25 },
|
|
17
|
-
* { header: 'Email', key: 'email', width: 35 },
|
|
18
|
-
* ],
|
|
19
|
-
* rows: users, // array of objects
|
|
20
|
-
* },
|
|
21
|
-
* ]);
|
|
22
|
-
*
|
|
23
|
-
* // 2. Save to disk and get the file path back
|
|
24
|
-
* const filePath = await ExcelService.exportToFile('monthly-report', sheets, {
|
|
25
|
-
* outputDir: './exports',
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* // 3. Get a raw Buffer (e.g. to upload to S3)
|
|
29
|
-
* const buffer = await ExcelService.exportToBuffer('report', sheets);
|
|
30
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const ExcelJS = require('exceljs');
|
|
34
|
-
const fs = require('fs');
|
|
35
|
-
const path = require('path');
|
|
36
|
-
const env = require('../../config/env');
|
|
37
|
-
|
|
38
|
-
// ─── Colour palette ──────────────────────────────────────────────────────────
|
|
39
|
-
const THEME = {
|
|
40
|
-
headerBg: 'FF2563EB', // Blue-600
|
|
41
|
-
headerFg: 'FFFFFFFF', // White
|
|
42
|
-
altRowBg: 'FFF0F4FF', // Light blue-50
|
|
43
|
-
borderColor:'FFB0C4DE', // Muted blue-200
|
|
44
|
-
totalBg: 'FFEFF6FF', // Blue-50
|
|
45
|
-
totalFg: 'FF1D4ED8', // Blue-700
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// ─── Shared styles ────────────────────────────────────────────────────────────
|
|
49
|
-
const HEADER_STYLE = {
|
|
50
|
-
font: { name: 'Arial', bold: true, size: 11, color: { argb: THEME.headerFg } },
|
|
51
|
-
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.headerBg } },
|
|
52
|
-
alignment: { horizontal: 'center', vertical: 'middle', wrapText: false },
|
|
53
|
-
border: {
|
|
54
|
-
top: { style: 'thin', color: { argb: THEME.borderColor } },
|
|
55
|
-
bottom: { style: 'medium', color: { argb: THEME.headerBg } },
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const CELL_BORDER = {
|
|
60
|
-
bottom: { style: 'hair', color: { argb: THEME.borderColor } },
|
|
61
|
-
right: { style: 'hair', color: { argb: THEME.borderColor } },
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
/** Apply header row styling */
|
|
67
|
-
const styleHeaderRow = (row) => {
|
|
68
|
-
row.height = 28;
|
|
69
|
-
row.eachCell((cell) => {
|
|
70
|
-
Object.assign(cell, HEADER_STYLE);
|
|
71
|
-
cell.font = { ...HEADER_STYLE.font };
|
|
72
|
-
cell.fill = { ...HEADER_STYLE.fill, fgColor: { argb: THEME.headerBg } };
|
|
73
|
-
cell.alignment = { ...HEADER_STYLE.alignment };
|
|
74
|
-
cell.border = { ...HEADER_STYLE.border };
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/** Apply alternating row colours + light borders */
|
|
79
|
-
const styleDataRow = (row, rowIndex) => {
|
|
80
|
-
row.height = 20;
|
|
81
|
-
const isAlt = rowIndex % 2 === 0;
|
|
82
|
-
row.eachCell({ includeEmpty: true }, (cell) => {
|
|
83
|
-
cell.font = { name: 'Arial', size: 10 };
|
|
84
|
-
cell.alignment = { vertical: 'middle' };
|
|
85
|
-
cell.border = CELL_BORDER;
|
|
86
|
-
if (isAlt) {
|
|
87
|
-
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.altRowBg } };
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/** Format a column's cells according to a `type` hint */
|
|
93
|
-
const applyColumnFormat = (cell, value, type) => {
|
|
94
|
-
switch (type) {
|
|
95
|
-
case 'currency':
|
|
96
|
-
cell.numFmt = '"$"#,##0.00;[Red]("$"#,##0.00)';
|
|
97
|
-
cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
|
|
98
|
-
break;
|
|
99
|
-
case 'percent':
|
|
100
|
-
cell.numFmt = '0.0%';
|
|
101
|
-
cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
|
|
102
|
-
break;
|
|
103
|
-
case 'date':
|
|
104
|
-
cell.numFmt = 'yyyy-mm-dd';
|
|
105
|
-
cell.value = value instanceof Date ? value : new Date(value);
|
|
106
|
-
break;
|
|
107
|
-
case 'number':
|
|
108
|
-
cell.numFmt = '#,##0.##';
|
|
109
|
-
cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
|
|
110
|
-
break;
|
|
111
|
-
default:
|
|
112
|
-
cell.value = value ?? '';
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* buildWorkbook
|
|
118
|
-
* Populates a workbook from a `sheets` array definition.
|
|
119
|
-
*
|
|
120
|
-
* @param {ExcelJS.Workbook} wb
|
|
121
|
-
* @param {SheetDef[]} sheets
|
|
122
|
-
*
|
|
123
|
-
* SheetDef {
|
|
124
|
-
* name: string – tab name
|
|
125
|
-
* columns: ColumnDef[] – column definitions
|
|
126
|
-
* rows: object[] – data rows
|
|
127
|
-
* totals?: Record<string, string> – key → Excel formula string, e.g. { amount: '=SUM(C2:C100)' }
|
|
128
|
-
* freezeHeader?: boolean – default true
|
|
129
|
-
* autoFilter?: boolean – default true
|
|
130
|
-
* extraSheets?: SheetDef[] – nested; same API
|
|
131
|
-
* }
|
|
132
|
-
*
|
|
133
|
-
* ColumnDef {
|
|
134
|
-
* header: string
|
|
135
|
-
* key: string
|
|
136
|
-
* width?: number (default 18)
|
|
137
|
-
* type?: 'text' | 'number' | 'currency' | 'percent' | 'date'
|
|
138
|
-
* hidden?: boolean
|
|
139
|
-
* }
|
|
140
|
-
*/
|
|
141
|
-
const buildWorkbook = (wb, sheets) => {
|
|
142
|
-
for (const sheetDef of sheets) {
|
|
143
|
-
const {
|
|
144
|
-
name,
|
|
145
|
-
columns,
|
|
146
|
-
rows = [],
|
|
147
|
-
totals,
|
|
148
|
-
freezeHeader = true,
|
|
149
|
-
autoFilter = true,
|
|
150
|
-
} = sheetDef;
|
|
151
|
-
|
|
152
|
-
const ws = wb.addWorksheet(name, {
|
|
153
|
-
pageSetup: { fitToPage: true, fitToWidth: 1, orientation: 'landscape' },
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// ── Columns ──────────────────────────────────────────────────────────────
|
|
157
|
-
ws.columns = columns.map(({ header, key, width = 18, hidden = false }) => ({
|
|
158
|
-
header,
|
|
159
|
-
key,
|
|
160
|
-
width,
|
|
161
|
-
hidden,
|
|
162
|
-
}));
|
|
163
|
-
|
|
164
|
-
// ── Header row ───────────────────────────────────────────────────────────
|
|
165
|
-
styleHeaderRow(ws.getRow(1));
|
|
166
|
-
|
|
167
|
-
// ── Data rows ────────────────────────────────────────────────────────────
|
|
168
|
-
rows.forEach((rowData, idx) => {
|
|
169
|
-
const row = ws.addRow({});
|
|
170
|
-
columns.forEach(({ key, type = 'text' }, colIdx) => {
|
|
171
|
-
const cell = row.getCell(colIdx + 1);
|
|
172
|
-
applyColumnFormat(cell, rowData[key], type);
|
|
173
|
-
});
|
|
174
|
-
styleDataRow(row, idx + 1);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// ── Totals row ───────────────────────────────────────────────────────────
|
|
178
|
-
if (totals && Object.keys(totals).length) {
|
|
179
|
-
const totalRow = ws.addRow({});
|
|
180
|
-
totalRow.height = 22;
|
|
181
|
-
columns.forEach(({ key }, colIdx) => {
|
|
182
|
-
const cell = totalRow.getCell(colIdx + 1);
|
|
183
|
-
if (totals[key]) {
|
|
184
|
-
cell.value = { formula: totals[key] };
|
|
185
|
-
cell.font = { name: 'Arial', bold: true, size: 10, color: { argb: THEME.totalFg } };
|
|
186
|
-
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.totalBg } };
|
|
187
|
-
} else if (colIdx === 0) {
|
|
188
|
-
cell.value = 'TOTAL';
|
|
189
|
-
cell.font = { name: 'Arial', bold: true, size: 10 };
|
|
190
|
-
cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.totalBg } };
|
|
191
|
-
}
|
|
192
|
-
cell.border = CELL_BORDER;
|
|
193
|
-
cell.alignment = { vertical: 'middle' };
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ── Auto-filter on header ─────────────────────────────────────────────────
|
|
198
|
-
if (autoFilter && columns.length) {
|
|
199
|
-
ws.autoFilter = {
|
|
200
|
-
from: { row: 1, column: 1 },
|
|
201
|
-
to: { row: 1, column: columns.length },
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── Freeze header row ────────────────────────────────────────────────────
|
|
206
|
-
if (freezeHeader) {
|
|
207
|
-
ws.views = [{ state: 'frozen', ySplit: 1 }];
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
213
|
-
|
|
214
|
-
const ExcelService = {
|
|
215
|
-
/**
|
|
216
|
-
* exportToResponse
|
|
217
|
-
* Stream the workbook directly to an Express `res` object.
|
|
218
|
-
*
|
|
219
|
-
* @param {import('express').Response} res
|
|
220
|
-
* @param {string} filename – without extension
|
|
221
|
-
* @param {SheetDef[]} sheets
|
|
222
|
-
*/
|
|
223
|
-
async exportToResponse(res, filename, sheets) {
|
|
224
|
-
const wb = new ExcelJS.Workbook();
|
|
225
|
-
wb.creator = 'App Export';
|
|
226
|
-
wb.created = new Date();
|
|
227
|
-
buildWorkbook(wb, sheets);
|
|
228
|
-
|
|
229
|
-
const safe = filename.replace(/[^a-z0-9_-]/gi, '_');
|
|
230
|
-
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
231
|
-
res.setHeader('Content-Disposition', `attachment; filename="${safe}.xlsx"`);
|
|
232
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
233
|
-
|
|
234
|
-
await wb.xlsx.write(res);
|
|
235
|
-
res.end();
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* exportToBuffer
|
|
240
|
-
* Returns the workbook as a Node.js Buffer (for uploads, email attachments, etc.)
|
|
241
|
-
*
|
|
242
|
-
* @param {string} filename – metadata only
|
|
243
|
-
* @param {SheetDef[]} sheets
|
|
244
|
-
* @returns {Promise<Buffer>}
|
|
245
|
-
*/
|
|
246
|
-
async exportToBuffer(filename, sheets) {
|
|
247
|
-
const wb = new ExcelJS.Workbook();
|
|
248
|
-
wb.creator = filename;
|
|
249
|
-
wb.created = new Date();
|
|
250
|
-
buildWorkbook(wb, sheets);
|
|
251
|
-
return wb.xlsx.writeBuffer();
|
|
252
|
-
},
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* exportToFile
|
|
256
|
-
* Saves the workbook to disk and returns the absolute file path.
|
|
257
|
-
*
|
|
258
|
-
* @param {string} filename
|
|
259
|
-
* @param {SheetDef[]} sheets
|
|
260
|
-
* @param {{ outputDir?: string }} options
|
|
261
|
-
* @returns {Promise<string>} absolute path to the saved file
|
|
262
|
-
*/
|
|
263
|
-
async exportToFile(filename, sheets, options = {}) {
|
|
264
|
-
const outputDir = options.outputDir || env.EXCEL_OUTPUT_DIR;
|
|
265
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
266
|
-
|
|
267
|
-
const safe = filename.replace(/[^a-z0-9_-]/gi, '_');
|
|
268
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
269
|
-
const filePath = path.resolve(outputDir, `${safe}_${stamp}.xlsx`);
|
|
270
|
-
|
|
271
|
-
const wb = new ExcelJS.Workbook();
|
|
272
|
-
wb.creator = filename;
|
|
273
|
-
wb.created = new Date();
|
|
274
|
-
buildWorkbook(wb, sheets);
|
|
275
|
-
await wb.xlsx.writeFile(filePath);
|
|
276
|
-
|
|
277
|
-
return filePath;
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* buildSheetDef
|
|
282
|
-
* Convenience factory — infers column keys from the first row of data.
|
|
283
|
-
*
|
|
284
|
-
* @param {string} name – sheet tab name
|
|
285
|
-
* @param {object[]} rows – data rows
|
|
286
|
-
* @param {object} opts – { columnWidths, columnTypes, totals, ... }
|
|
287
|
-
*/
|
|
288
|
-
buildSheetDef(name, rows, opts = {}) {
|
|
289
|
-
const { columnWidths = {}, columnTypes = {}, totals, freezeHeader, autoFilter } = opts;
|
|
290
|
-
const keys = rows.length ? Object.keys(rows[0]) : [];
|
|
291
|
-
|
|
292
|
-
const columns = keys.map((key) => ({
|
|
293
|
-
header: key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
294
|
-
key,
|
|
295
|
-
width: columnWidths[key] || 18,
|
|
296
|
-
type: columnTypes[key] || 'text',
|
|
297
|
-
}));
|
|
298
|
-
|
|
299
|
-
return { name, columns, rows, totals, freezeHeader, autoFilter };
|
|
300
|
-
},
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
module.exports = ExcelService;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const SalaryService = require('./salary.service');
|
|
2
|
-
const res_ = require('../../utils/response');
|
|
3
|
-
|
|
4
|
-
const SalaryController = {
|
|
5
|
-
async create(req, res) {
|
|
6
|
-
try {
|
|
7
|
-
const data = req.body;
|
|
8
|
-
data.netSalary = data.grossSalary - data.totalDeduction;
|
|
9
|
-
const sal = await SalaryService.create(data);
|
|
10
|
-
return res_.created(res, sal, 'Salary record created');
|
|
11
|
-
} catch (err) {
|
|
12
|
-
console.error(`[SalaryController.create]`, err);
|
|
13
|
-
return res_.error(res, err.message, err.statusCode || 500);
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
async list(req, res) {
|
|
18
|
-
try {
|
|
19
|
-
const salaries = await SalaryService.list();
|
|
20
|
-
return res_.success(res, { salaries });
|
|
21
|
-
} catch (err) {
|
|
22
|
-
return res_.error(res, err.message, 500);
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
async getById(req, res) {
|
|
27
|
-
try {
|
|
28
|
-
const sal = await SalaryService.getById(req.params.id);
|
|
29
|
-
if (!sal) return res_.notFound(res, 'Salary record not found');
|
|
30
|
-
return res_.success(res, sal);
|
|
31
|
-
} catch (err) {
|
|
32
|
-
return res_.error(res, err.message, 500);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
async update(req, res) {
|
|
37
|
-
try {
|
|
38
|
-
const data = req.body;
|
|
39
|
-
if (data.grossSalary !== undefined && data.totalDeduction !== undefined) {
|
|
40
|
-
data.netSalary = data.grossSalary - data.totalDeduction;
|
|
41
|
-
}
|
|
42
|
-
const sal = await SalaryService.update(req.params.id, data);
|
|
43
|
-
if (!sal) return res_.notFound(res, 'Salary record not found');
|
|
44
|
-
return res_.success(res, sal, 'Salary record updated');
|
|
45
|
-
} catch (err) {
|
|
46
|
-
return res_.error(res, err.message, 500);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
async remove(req, res) {
|
|
51
|
-
try {
|
|
52
|
-
const sal = await SalaryService.remove(req.params.id);
|
|
53
|
-
if (!sal) return res_.notFound(res, 'Salary record not found');
|
|
54
|
-
return res_.success(res, null, 'Salary record deleted');
|
|
55
|
-
} catch (err) {
|
|
56
|
-
return res_.error(res, err.message, 500);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
async average(req, res) {
|
|
61
|
-
try {
|
|
62
|
-
const avg = await SalaryService.average();
|
|
63
|
-
return res_.success(res, { averageSalary: avg });
|
|
64
|
-
} catch (err) {
|
|
65
|
-
return res_.error(res, err.message, 500);
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
module.exports = SalaryController;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
const mongoose = require('mongoose');
|
|
2
|
-
|
|
3
|
-
const salarySchema = new mongoose.Schema({
|
|
4
|
-
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee', required: true },
|
|
5
|
-
grossSalary: { type: Number, required: true },
|
|
6
|
-
totalDeduction: { type: Number, required: true, default: 0 },
|
|
7
|
-
netSalary: { type: Number, required: true },
|
|
8
|
-
month: { type: String, required: true, trim: true },
|
|
9
|
-
}, { timestamps: true });
|
|
10
|
-
|
|
11
|
-
const Salary = mongoose.model('Salary', salarySchema);
|
|
12
|
-
|
|
13
|
-
// Drop stale indexes from previous schema versions
|
|
14
|
-
mongoose.connection.once('open', async () => {
|
|
15
|
-
try {
|
|
16
|
-
await Salary.collection.dropIndex('employeeNumber_1');
|
|
17
|
-
} catch {}
|
|
18
|
-
try {
|
|
19
|
-
await Salary.collection.dropIndex('salaryId_1');
|
|
20
|
-
} catch {}
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
module.exports = Salary;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const { Router } = require('express');
|
|
2
|
-
const SalaryController = require('./salary.controller');
|
|
3
|
-
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
-
|
|
5
|
-
const router = Router();
|
|
6
|
-
|
|
7
|
-
router.use(authenticate);
|
|
8
|
-
|
|
9
|
-
router.post('/', SalaryController.create);
|
|
10
|
-
router.get('/', SalaryController.list);
|
|
11
|
-
router.get('/avg', SalaryController.average);
|
|
12
|
-
router.get('/:id', SalaryController.getById);
|
|
13
|
-
router.put('/:id', SalaryController.update);
|
|
14
|
-
router.delete('/:id', SalaryController.remove);
|
|
15
|
-
|
|
16
|
-
module.exports = router;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const Salary = require('./salary.model');
|
|
2
|
-
|
|
3
|
-
const SalaryService = {
|
|
4
|
-
async create(data) {
|
|
5
|
-
const sal = await Salary.create(data);
|
|
6
|
-
return Salary.populate(sal, { path: 'employee', populate: { path: 'department' } });
|
|
7
|
-
},
|
|
8
|
-
|
|
9
|
-
async list() {
|
|
10
|
-
return Salary.find()
|
|
11
|
-
.populate({ path: 'employee', populate: { path: 'department' } })
|
|
12
|
-
.sort({ createdAt: -1 });
|
|
13
|
-
},
|
|
14
|
-
|
|
15
|
-
async getById(id) {
|
|
16
|
-
return Salary.findById(id)
|
|
17
|
-
.populate({ path: 'employee', populate: { path: 'department' } });
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
-
async update(id, data) {
|
|
21
|
-
return Salary.findByIdAndUpdate(id, data, { new: true })
|
|
22
|
-
.populate({ path: 'employee', populate: { path: 'department' } });
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
async remove(id) {
|
|
26
|
-
return Salary.findByIdAndDelete(id);
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async average() {
|
|
30
|
-
const result = await Salary.aggregate([
|
|
31
|
-
{ $group: { _id: null, avgSalary: { $avg: '$netSalary' } } },
|
|
32
|
-
]);
|
|
33
|
-
return result.length > 0 ? result[0].avgSalary : 0;
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
async getMonthlyReport(month) {
|
|
37
|
-
const match = month ? { month } : {};
|
|
38
|
-
return Salary.find(match)
|
|
39
|
-
.populate({ path: 'employee', populate: { path: 'department' } })
|
|
40
|
-
.sort({ month: -1, createdAt: -1 });
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
module.exports = SalaryService;
|