alpe-temp 1.0.2 → 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 +1 -1
- package/backend-project/package-lock.json +131 -0
- package/backend-project/package.json +3 -1
- package/backend-project/server-err.txt +0 -0
- package/backend-project/server-out.txt +6 -0
- 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 +159 -24
- package/backend-project/src/modules/reports/reports.routes.js +3 -2
- package/backend-project/src/seed.js +69 -15
- package/backend-project/src/utils/token.js +1 -27
- package/backend-project/test-all-routes.js +294 -0
- package/bin/epms.js +57 -92
- package/frontend-project/dist/assets/index-CRG9iE0k.css +1 -0
- package/frontend-project/dist/assets/{index-B08ICGra.js → index-LpBGz8lQ.js} +7 -7
- package/frontend-project/dist/index.html +3 -4
- package/frontend-project/index.html +1 -2
- package/frontend-project/src/Auth/Login.jsx +15 -25
- package/frontend-project/src/Auth/Register.jsx +91 -183
- package/frontend-project/src/Intro.jsx +4 -9
- package/frontend-project/src/LayOut.jsx +10 -23
- package/frontend-project/src/api/ApiClient.js +20 -60
- package/frontend-project/src/config.js +4 -4
- package/frontend-project/src/layouts/BottomNav.jsx +23 -106
- package/frontend-project/src/layouts/TopNav.jsx +19 -99
- 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 +112 -67
- package/package.json +2 -12
- 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-D_cqT2Z6.css +0 -1
|
@@ -1,41 +1,18 @@
|
|
|
1
|
-
const
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
2
|
|
|
3
|
-
const userSchema = new Schema(
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
trim: true,
|
|
9
|
-
},
|
|
10
|
-
email: {
|
|
11
|
-
type: String,
|
|
12
|
-
required: true,
|
|
13
|
-
unique: true,
|
|
14
|
-
lowercase: true,
|
|
15
|
-
trim: true,
|
|
16
|
-
},
|
|
17
|
-
password: {
|
|
18
|
-
type: String,
|
|
19
|
-
required: true,
|
|
20
|
-
},
|
|
21
|
-
role: {
|
|
22
|
-
type: String,
|
|
23
|
-
enum: ['user', 'admin'],
|
|
24
|
-
default: 'user',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{ timestamps: true }
|
|
28
|
-
);
|
|
3
|
+
const userSchema = new mongoose.Schema({
|
|
4
|
+
userName: { type: String, required: true, unique: true, trim: true },
|
|
5
|
+
password: { type: String, required: true },
|
|
6
|
+
employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee' },
|
|
7
|
+
}, { timestamps: true });
|
|
29
8
|
|
|
30
|
-
// Never expose the password hash in query results
|
|
31
9
|
userSchema.methods.toSafeObject = function () {
|
|
32
10
|
return {
|
|
33
|
-
id:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
role: this.role,
|
|
11
|
+
id: this._id,
|
|
12
|
+
userName: this.userName,
|
|
13
|
+
employee: this.employee,
|
|
37
14
|
createdAt: this.createdAt,
|
|
38
15
|
};
|
|
39
16
|
};
|
|
40
17
|
|
|
41
|
-
module.exports = model('User', userSchema);
|
|
18
|
+
module.exports = mongoose.model('User', userSchema);
|
|
@@ -10,7 +10,6 @@ const DepartmentController = {
|
|
|
10
10
|
return res_.error(res, err.message, err.statusCode || 500);
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
-
|
|
14
13
|
async list(req, res) {
|
|
15
14
|
try {
|
|
16
15
|
const departments = await DepartmentService.list();
|
|
@@ -19,7 +18,6 @@ const DepartmentController = {
|
|
|
19
18
|
return res_.error(res, err.message, 500);
|
|
20
19
|
}
|
|
21
20
|
},
|
|
22
|
-
|
|
23
21
|
async getById(req, res) {
|
|
24
22
|
try {
|
|
25
23
|
const dept = await DepartmentService.getById(req.params.id);
|
|
@@ -29,7 +27,6 @@ const DepartmentController = {
|
|
|
29
27
|
return res_.error(res, err.message, 500);
|
|
30
28
|
}
|
|
31
29
|
},
|
|
32
|
-
|
|
33
30
|
async update(req, res) {
|
|
34
31
|
try {
|
|
35
32
|
const dept = await DepartmentService.update(req.params.id, req.body);
|
|
@@ -39,7 +36,6 @@ const DepartmentController = {
|
|
|
39
36
|
return res_.error(res, err.message, 500);
|
|
40
37
|
}
|
|
41
38
|
},
|
|
42
|
-
|
|
43
39
|
async remove(req, res) {
|
|
44
40
|
try {
|
|
45
41
|
const dept = await DepartmentService.remove(req.params.id);
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
const mongoose = require('mongoose');
|
|
2
2
|
|
|
3
3
|
const departmentSchema = new mongoose.Schema({
|
|
4
|
-
|
|
5
|
-
departmentName: { type: String, required: true, trim: true },
|
|
6
|
-
grossSalary: { type: Number, required: true },
|
|
7
|
-
totalDeduction: { type: Number, required: true, default: 0 },
|
|
4
|
+
departName: { type: String, required: true, unique: true, trim: true },
|
|
8
5
|
}, { timestamps: true });
|
|
9
6
|
|
|
10
7
|
module.exports = mongoose.model('Department', departmentSchema);
|
|
@@ -4,23 +4,15 @@ const DepartmentService = {
|
|
|
4
4
|
async create(data) {
|
|
5
5
|
return Department.create(data);
|
|
6
6
|
},
|
|
7
|
-
|
|
8
7
|
async list() {
|
|
9
|
-
return Department.find().sort({
|
|
10
|
-
},
|
|
11
|
-
|
|
12
|
-
async getByCode(code) {
|
|
13
|
-
return Department.findOne({ departmentCode: code });
|
|
8
|
+
return Department.find().sort({ departName: 1 });
|
|
14
9
|
},
|
|
15
|
-
|
|
16
10
|
async getById(id) {
|
|
17
11
|
return Department.findById(id);
|
|
18
12
|
},
|
|
19
|
-
|
|
20
13
|
async update(id, data) {
|
|
21
14
|
return Department.findByIdAndUpdate(id, data, { new: true });
|
|
22
15
|
},
|
|
23
|
-
|
|
24
16
|
async remove(id) {
|
|
25
17
|
return Department.findByIdAndDelete(id);
|
|
26
18
|
},
|
|
@@ -13,7 +13,8 @@ const EmployeeController = {
|
|
|
13
13
|
|
|
14
14
|
async list(req, res) {
|
|
15
15
|
try {
|
|
16
|
-
const
|
|
16
|
+
const { search } = req.query;
|
|
17
|
+
const employees = await EmployeeService.list(search);
|
|
17
18
|
return res_.success(res, { employees });
|
|
18
19
|
} catch (err) {
|
|
19
20
|
return res_.error(res, err.message, 500);
|
|
@@ -49,15 +50,6 @@ const EmployeeController = {
|
|
|
49
50
|
return res_.error(res, err.message, 500);
|
|
50
51
|
}
|
|
51
52
|
},
|
|
52
|
-
|
|
53
|
-
async count(req, res) {
|
|
54
|
-
try {
|
|
55
|
-
const count = await EmployeeService.count();
|
|
56
|
-
return res_.success(res, { count });
|
|
57
|
-
} catch (err) {
|
|
58
|
-
return res_.error(res, err.message, 500);
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
53
|
};
|
|
62
54
|
|
|
63
55
|
module.exports = EmployeeController;
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
const mongoose = require('mongoose');
|
|
2
2
|
|
|
3
3
|
const employeeSchema = new mongoose.Schema({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
empFirstName: { type: String, required: true, trim: true },
|
|
5
|
+
empLastName: { type: String, required: true, trim: true },
|
|
6
|
+
empGender: { type: String, enum: ['male', 'female'], required: true },
|
|
7
|
+
empDateOfBirth: { type: Date },
|
|
8
|
+
empEmail: { type: String, trim: true, lowercase: true },
|
|
9
|
+
empTelephone: { type: String, trim: true },
|
|
10
|
+
empAddress: { type: String, trim: true },
|
|
11
|
+
empHireDate: { type: Date, required: true },
|
|
12
|
+
empStatus: {
|
|
13
|
+
type: String,
|
|
14
|
+
enum: ['on leave', 'left', 'blacklisted', 'deceased', 'on mission'],
|
|
15
|
+
default: 'on mission',
|
|
16
|
+
},
|
|
17
|
+
department: { type: mongoose.Schema.Types.ObjectId, ref: 'Department' },
|
|
18
|
+
position: { type: mongoose.Schema.Types.ObjectId, ref: 'Position' },
|
|
13
19
|
}, { timestamps: true });
|
|
14
20
|
|
|
15
21
|
module.exports = mongoose.model('Employee', employeeSchema);
|
|
@@ -3,14 +3,12 @@ const EmployeeController = require('./employee.controller');
|
|
|
3
3
|
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
4
|
|
|
5
5
|
const router = Router();
|
|
6
|
-
|
|
7
6
|
router.use(authenticate);
|
|
8
7
|
|
|
9
|
-
router.post('/',
|
|
10
|
-
router.get('/',
|
|
11
|
-
router.get('
|
|
12
|
-
router.
|
|
13
|
-
router.put('/:id', EmployeeController.update);
|
|
8
|
+
router.post('/', EmployeeController.create);
|
|
9
|
+
router.get('/', EmployeeController.list);
|
|
10
|
+
router.get('/:id', EmployeeController.getById);
|
|
11
|
+
router.put('/:id', EmployeeController.update);
|
|
14
12
|
router.delete('/:id', EmployeeController.remove);
|
|
15
13
|
|
|
16
14
|
module.exports = router;
|
|
@@ -3,19 +3,34 @@ const Employee = require('./employee.model');
|
|
|
3
3
|
const EmployeeService = {
|
|
4
4
|
async create(data) {
|
|
5
5
|
const emp = await Employee.create(data);
|
|
6
|
-
return Employee.populate(emp, { path: 'department' });
|
|
6
|
+
return Employee.populate(emp, [{ path: 'department' }, { path: 'position' }]);
|
|
7
7
|
},
|
|
8
8
|
|
|
9
|
-
async list() {
|
|
10
|
-
|
|
9
|
+
async list(search) {
|
|
10
|
+
const filter = {};
|
|
11
|
+
if (search) {
|
|
12
|
+
const regex = new RegExp(search, 'i');
|
|
13
|
+
filter.$or = [
|
|
14
|
+
{ empFirstName: regex },
|
|
15
|
+
{ empLastName: regex },
|
|
16
|
+
{ empEmail: regex },
|
|
17
|
+
{ empTelephone: regex },
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
return Employee.find(filter)
|
|
21
|
+
.populate('department')
|
|
22
|
+
.populate('position')
|
|
23
|
+
.sort({ createdAt: -1 });
|
|
11
24
|
},
|
|
12
25
|
|
|
13
26
|
async getById(id) {
|
|
14
|
-
return Employee.findById(id).populate('department');
|
|
27
|
+
return Employee.findById(id).populate('department').populate('position');
|
|
15
28
|
},
|
|
16
29
|
|
|
17
30
|
async update(id, data) {
|
|
18
|
-
return Employee.findByIdAndUpdate(id, data, { new: true })
|
|
31
|
+
return Employee.findByIdAndUpdate(id, data, { new: true })
|
|
32
|
+
.populate('department')
|
|
33
|
+
.populate('position');
|
|
19
34
|
},
|
|
20
35
|
|
|
21
36
|
async remove(id) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const PositionService = require('./position.service');
|
|
2
|
+
const res_ = require('../../utils/response');
|
|
3
|
+
|
|
4
|
+
const PositionController = {
|
|
5
|
+
async create(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const pos = await PositionService.create(req.body);
|
|
8
|
+
return res_.created(res, pos, 'Position created');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
return res_.error(res, err.message, err.statusCode || 500);
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
async list(req, res) {
|
|
14
|
+
try {
|
|
15
|
+
const positions = await PositionService.list();
|
|
16
|
+
return res_.success(res, { positions });
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return res_.error(res, err.message, 500);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
async getById(req, res) {
|
|
22
|
+
try {
|
|
23
|
+
const pos = await PositionService.getById(req.params.id);
|
|
24
|
+
if (!pos) return res_.notFound(res, 'Position not found');
|
|
25
|
+
return res_.success(res, pos);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return res_.error(res, err.message, 500);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async update(req, res) {
|
|
31
|
+
try {
|
|
32
|
+
const pos = await PositionService.update(req.params.id, req.body);
|
|
33
|
+
if (!pos) return res_.notFound(res, 'Position not found');
|
|
34
|
+
return res_.success(res, pos, 'Position updated');
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return res_.error(res, err.message, 500);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async remove(req, res) {
|
|
40
|
+
try {
|
|
41
|
+
const pos = await PositionService.remove(req.params.id);
|
|
42
|
+
if (!pos) return res_.notFound(res, 'Position not found');
|
|
43
|
+
return res_.success(res, null, 'Position deleted');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return res_.error(res, err.message, 500);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
module.exports = PositionController;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const positionSchema = new mongoose.Schema({
|
|
4
|
+
posName: { type: String, required: true, unique: true, trim: true },
|
|
5
|
+
requiredQualification: { type: String, trim: true },
|
|
6
|
+
}, { timestamps: true });
|
|
7
|
+
|
|
8
|
+
module.exports = mongoose.model('Position', positionSchema);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const PositionController = require('./position.controller');
|
|
3
|
+
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
router.use(authenticate);
|
|
7
|
+
|
|
8
|
+
router.post('/', PositionController.create);
|
|
9
|
+
router.get('/', PositionController.list);
|
|
10
|
+
router.get('/:id', PositionController.getById);
|
|
11
|
+
router.put('/:id', PositionController.update);
|
|
12
|
+
router.delete('/:id', PositionController.remove);
|
|
13
|
+
|
|
14
|
+
module.exports = router;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const Position = require('./position.model');
|
|
2
|
+
|
|
3
|
+
const PositionService = {
|
|
4
|
+
async create(data) {
|
|
5
|
+
return Position.create(data);
|
|
6
|
+
},
|
|
7
|
+
async list() {
|
|
8
|
+
return Position.find().sort({ posName: 1 });
|
|
9
|
+
},
|
|
10
|
+
async getById(id) {
|
|
11
|
+
return Position.findById(id);
|
|
12
|
+
},
|
|
13
|
+
async update(id, data) {
|
|
14
|
+
return Position.findByIdAndUpdate(id, data, { new: true });
|
|
15
|
+
},
|
|
16
|
+
async remove(id) {
|
|
17
|
+
return Position.findByIdAndDelete(id);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = PositionService;
|
|
@@ -1,37 +1,172 @@
|
|
|
1
|
-
const
|
|
1
|
+
const ExcelJS = require('exceljs');
|
|
2
|
+
const Employee = require('../employee/employee.model');
|
|
2
3
|
const res_ = require('../../utils/response');
|
|
3
4
|
|
|
4
5
|
const ReportsController = {
|
|
5
|
-
async
|
|
6
|
+
async employeesOnLeave(req, res) {
|
|
6
7
|
try {
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return res_.success(res, { payroll });
|
|
8
|
+
const employees = await Employee.find({ empStatus: 'on leave' })
|
|
9
|
+
.populate('department')
|
|
10
|
+
.populate('position')
|
|
11
|
+
.sort({ 'department.departName': 1, empFirstName: 1 });
|
|
12
|
+
|
|
13
|
+
const grouped = {};
|
|
14
|
+
for (const emp of employees) {
|
|
15
|
+
const deptName = emp.department ? emp.department.departName : 'Unassigned';
|
|
16
|
+
if (!grouped[deptName]) grouped[deptName] = [];
|
|
17
|
+
grouped[deptName].push(emp);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return res_.success(res, {
|
|
21
|
+
departments: grouped,
|
|
22
|
+
total: employees.length,
|
|
23
|
+
});
|
|
25
24
|
} catch (err) {
|
|
26
25
|
return res_.error(res, err.message, 500);
|
|
27
26
|
}
|
|
28
27
|
},
|
|
29
28
|
|
|
30
|
-
async
|
|
29
|
+
async employeesOnLeaveExcel(req, res) {
|
|
31
30
|
try {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
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();
|
|
35
170
|
} catch (err) {
|
|
36
171
|
return res_.error(res, err.message, 500);
|
|
37
172
|
}
|
|
@@ -3,8 +3,9 @@ const ReportsController = require('./reports.controller');
|
|
|
3
3
|
const { authenticate } = require('../../middleware/auth.middleware');
|
|
4
4
|
|
|
5
5
|
const router = Router();
|
|
6
|
-
|
|
7
6
|
router.use(authenticate);
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
router.get('/employees-on-leave', ReportsController.employeesOnLeave);
|
|
9
|
+
router.get('/employees-on-leave/excel', ReportsController.employeesOnLeaveExcel);
|
|
9
10
|
|
|
10
11
|
module.exports = router;
|
|
@@ -1,29 +1,83 @@
|
|
|
1
1
|
require('dotenv').config();
|
|
2
2
|
const mongoose = require('mongoose');
|
|
3
|
+
const bcrypt = require('bcryptjs');
|
|
3
4
|
const db = require('./config/db');
|
|
5
|
+
const env = require('./config/env');
|
|
4
6
|
const Department = require('./modules/department/department.model');
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
{ departmentCode: 'ST', departmentName: 'Stock', grossSalary: 200000, totalDeduction: 20000 },
|
|
9
|
-
{ departmentCode: 'MC', departmentName: 'Mechanic', grossSalary: 450000, totalDeduction: 45000 },
|
|
10
|
-
{ departmentCode: 'ADMS', departmentName: 'Administration Staff', grossSalary: 600000, totalDeduction: 60000 },
|
|
11
|
-
];
|
|
7
|
+
const Position = require('./modules/position/position.model');
|
|
8
|
+
const Employee = require('./modules/employee/employee.model');
|
|
9
|
+
const User = require('./modules/auth/user.model');
|
|
12
10
|
|
|
13
11
|
async function seed() {
|
|
14
12
|
try {
|
|
15
13
|
await db.connect();
|
|
16
14
|
console.log('Connected to MongoDB');
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
const departments = await Department.insertMany([
|
|
17
|
+
{ departName: 'Sales' },
|
|
18
|
+
{ departName: 'Warehouse' },
|
|
19
|
+
{ departName: 'Administration' },
|
|
20
|
+
{ departName: 'Human Resources' },
|
|
21
|
+
]);
|
|
22
|
+
console.log(`Created ${departments.length} departments`);
|
|
23
|
+
|
|
24
|
+
const positions = await Position.insertMany([
|
|
25
|
+
{ posName: 'Sales Representative', requiredQualification: 'Bachelor in Business' },
|
|
26
|
+
{ posName: 'Warehouse Manager', requiredQualification: 'Diploma in Logistics' },
|
|
27
|
+
{ posName: 'HR Officer', requiredQualification: 'Bachelor in HRM' },
|
|
28
|
+
{ posName: 'Accountant', requiredQualification: 'Bachelor in Accounting' },
|
|
29
|
+
{ posName: 'Cashier', requiredQualification: 'High School Diploma' },
|
|
30
|
+
]);
|
|
31
|
+
console.log(`Created ${positions.length} positions`);
|
|
32
|
+
|
|
33
|
+
const employees = await Employee.insertMany([
|
|
34
|
+
{
|
|
35
|
+
empFirstName: 'Alice', empLastName: 'Mukamana',
|
|
36
|
+
empGender: 'female', empDateOfBirth: new Date('1990-05-12'),
|
|
37
|
+
empEmail: 'alice@dabenterprise.rw', empTelephone: '0788123456',
|
|
38
|
+
empAddress: 'Kigali', empHireDate: new Date('2022-01-15'),
|
|
39
|
+
empStatus: 'on mission', department: departments[0]._id, position: positions[0]._id,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
empFirstName: 'Bob', empLastName: 'Habimana',
|
|
43
|
+
empGender: 'male', empDateOfBirth: new Date('1985-08-22'),
|
|
44
|
+
empEmail: 'bob@dabenterprise.rw', empTelephone: '0788123457',
|
|
45
|
+
empAddress: 'Kicukiro', empHireDate: new Date('2021-03-01'),
|
|
46
|
+
empStatus: 'on leave', department: departments[0]._id, position: positions[0]._id,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
empFirstName: 'Claire', empLastName: 'Uwimana',
|
|
50
|
+
empGender: 'female', empDateOfBirth: new Date('1992-11-03'),
|
|
51
|
+
empEmail: 'claire@dabenterprise.rw', empTelephone: '0788123458',
|
|
52
|
+
empAddress: 'Remera', empHireDate: new Date('2023-06-10'),
|
|
53
|
+
empStatus: 'on leave', department: departments[1]._id, position: positions[1]._id,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
empFirstName: 'David', empLastName: 'Niyonzima',
|
|
57
|
+
empGender: 'male', empDateOfBirth: new Date('1988-02-14'),
|
|
58
|
+
empEmail: 'david@dabenterprise.rw', empTelephone: '0788123459',
|
|
59
|
+
empAddress: 'Nyamirambo', empHireDate: new Date('2020-09-20'),
|
|
60
|
+
empStatus: 'on leave', department: departments[2]._id, position: positions[3]._id,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
empFirstName: 'Grace', empLastName: 'Uwase',
|
|
64
|
+
empGender: 'female', empDateOfBirth: new Date('1995-07-30'),
|
|
65
|
+
empEmail: 'grace@dabenterprise.rw', empTelephone: '0788123460',
|
|
66
|
+
empAddress: 'Kanombe', empHireDate: new Date('2024-01-05'),
|
|
67
|
+
empStatus: 'on mission', department: departments[3]._id, position: positions[2]._id,
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
console.log(`Created ${employees.length} employees`);
|
|
71
|
+
|
|
72
|
+
const existingUser = await User.findOne({ userName: 'admin' });
|
|
73
|
+
if (!existingUser) {
|
|
74
|
+
const hashed = await bcrypt.hash('admin123', env.BCRYPT_ROUNDS);
|
|
75
|
+
await User.create({ userName: 'admin', password: hashed, employee: employees[0]._id });
|
|
76
|
+
console.log('Created user: admin / admin123');
|
|
77
|
+
} else {
|
|
78
|
+
console.log('User admin already exists');
|
|
26
79
|
}
|
|
80
|
+
|
|
27
81
|
console.log('Seed complete!');
|
|
28
82
|
} catch (err) {
|
|
29
83
|
console.error('Seed failed:', err.message);
|