The Payroll Management System is a Spring Boot-based application designed to manage employee payrolls, including employee details, employment records, deductions, and payslips. The system supports role-based access control with JWT authentication, automated payroll generation, and email notifications for payslip approvals. It uses Spring Data JPA for database interactions and MySQL as the database.
- Employee Management: CRUD operations for employee personal information, with role-based security (ROLE_MANAGER, ROLE_ADMIN, ROLE_EMPLOYEE).
- Employment Management: Manage employment details such as department, position, and base salary.
- Deduction Management: Configure and apply deductions (e.g., EmployeeTax, Pension, MedicalInsurance, Housing, Transport) to payroll calculations.
- Payroll Processing: Generate payslips for active employees, ensuring deductions do not exceed gross salary. Approve payslips and send email notifications.
- Security: JWT-based authentication (using email and password) and role-based authorization.
- Email Notifications: Automated email notifications triggered by database routines when payslip status changes to PAID.
can be found in
The ERD represents the database schema:
erDiagram
Employee ||--o{ Employment : has
Employee ||--o{ PaySlip : receives
Employee {
Long id PK
String code UNIQUE
String firstName
String lastName
String email UNIQUE
String password
Role role
String mobile
LocalDate dateOfBirth
EmployeeStatus status "ACTIVE, DISABLED"
boolean emailVerified
}
Employment {
Long id PK
String code
Long employee_id FK
String department
String position
BigDecimal baseSalary
EmploymentStatus status "ACTIVE, INACTIVE"
LocalDate joiningDate
}
Deduction {
Long id PK
String code
String name
BigDecimal percentage
}
PaySlip {
Long id PK
Long employee_id FK
BigDecimal houseAmount
BigDecimal transportAmount
BigDecimal employeeTaxedAmount
BigDecimal pensionAmount
BigDecimal medicalInsuranceAmount
BigDecimal otherTaxedAmount
BigDecimal grossSalary
BigDecimal netSalary
int month
int year
PaySlipStatus status "PENDING, PAID"
}
The diagram is rendered automatically in GitHub and other Markdown viewers that support Mermaid.
- Presentation Layer: RESTful APIs via Spring MVC controllers.
- Security Layer: JWT-based authentication and role-based authorization using Spring Security.
- Service Layer: Business logic for employee, employment, deduction, and payroll management.
- Persistence Layer: Spring Data JPA with Hibernate for PostgreSQL database interactions.
- Integration Layer: SMTP server for email notifications, triggered by database routines.
- Backend Framework: Spring Boot 3.x
- ORM: Spring Data JPA with Hibernate
- Database: PostgreSQL
- Security: Spring Security with JWT (jjwt library)
- Email: Spring Boot Starter Mail
- Dependencies:
spring-boot-starter-web
spring-boot-starter-data-jpa
spring-boot-starter-security
spring-boot-starter-mail
postgresql
jjwt
lombok
- Build Tool: Maven
- Database Migration: Flyway (optional)
- Testing: JUnit, Mockito
- Email Testing: MailHog (recommended for local SMTP testing)
- Employee: id, code, firstName, lastName, email, password, roles, mobile, dateOfBirth, status (ACTIVE, DISABLED)
- Employment: id, code, employee (FK), department, position, baseSalary, status (ACTIVE, INACTIVE), joiningDate
- Deduction: id, code, name, percentage
- PaySlip: id, employee (FK), houseAmount, transportAmount, employeeTaxedAmount, pensionAmount, medicalInsuranceAmount, otherTaxedAmount, grossSalary, netSalary, month, year, status (PENDING, PAID)
- EmailNotification: id, employeeId, message, createdAt
- DTOs: EmployeeDTO, DeductionDTO, LoginRequest, JwtResponse
- Authentication: Via
/api/auth/login
using email and password, generating a JWT token. - Authorization: Role-based access control:
- ROLE_MANAGER: Manage employees, deductions, and generate payroll.
- ROLE_ADMIN: Approve payslips.
- ROLE_EMPLOYEE: View personal details and payslips.
- JWT: Signed with HS512, expires after 7 days, includes roles.
- Password Security: Hashed with BCrypt.
- CSRF/CORS: CSRF disabled for stateless APIs; CORS configurable.
- POST /api/auth/login: Authenticate and return JWT token.
- Request:
{"email": "string", "password": "string"}
- Response:
{"token": "string"}
- Access: Public
- Request:
- POST /api/employee: Create employee (ROLE_MANAGER).
- GET /api/employee/{id}: Get employee details (ROLE_MANAGER, ROLE_ADMIN, ROLE_EMPLOYEE for self).
- PUT /api/employee/{id}: Update employee (ROLE_MANAGER).
- DELETE /api/employee/{id}: Disable employee (ROLE_MANAGER).
- GET /api/employee/me: Get authenticated employee’s details (ROLE_EMPLOYEE, ROLE_MANAGER, ROLE_ADMIN).
- POST /api/employment: Create employment record (ROLE_MANAGER).
- GET /api/employment/{id}: Get employment details (ROLE_MANAGER, ROLE_ADMIN).
- PUT /api/employment/{id}: Update employment (ROLE_MANAGER).
- DELETE /api/employment/{id}: Deactivate employment (ROLE_MANAGER).
- POST /api/deduction: Create deduction (ROLE_MANAGER).
- GET /api/deduction/{id}: Get deduction (ROLE_MANAGER).
- GET /api/deduction/all: Get all deductions (ROLE_MANAGER).
- PUT /api/deduction/{id}: Update deduction (ROLE_MANAGER).
- DELETE /api/deduction/{id}: Delete deduction (ROLE_MANAGER).
- POST /api/payroll/generate/{month}/{year}: Generate payroll (ROLE_MANAGER).
- POST /api/payroll/approve/{id}: Approve payslip, trigger email (ROLE_ADMIN).
- GET /api/payroll/employee/{employeeId}/{month}/{year}: Get employee payslips (ROLE_EMPLOYEE for self, ROLE_MANAGER).
- GET /api/payroll/{month}/{year}: Get all payslips for month/year (ROLE_MANAGER).
- Prerequisites:
- Java 17+
- Maven 3.8+
- MySQL 8.0+
-
Download the zip:
-
Configure Database:
- Create a PostgreSQL database named
payroll
. - Update
src/main/resources/application.properties
:spring.datasource.url=jdbc:postgresql://localhost:5432/payroll spring.datasource.username=manager spring.datasource.password=12345678 spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
- Configure Email Testing with MailHog:
- Install and run MailHog using Docker:
docker run -d -p 1025:1025 -p 8025:8025 --name mailhog mailhog/mailhog
- Update
application.properties
for email testing:spring.mail.host=localhost spring.mail.port=1025 spring.mail.username= spring.mail.password= spring.mail.properties.mail.smtp.auth=false spring.mail.properties.mail.smtp.starttls.enable=false
- Access the MailHog web UI at
http://localhost:8025
to view captured emails. - For production, configure a real SMTP server (e.g., Gmail):
spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=your-email@gmail.com spring.mail.password=your-email-password spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true
- Initialize Deductions:
- The
data.sql
file initializes deductions:- EmployeeTax: 30%
- Pension: 6%
- MedicalInsurance: 5%
- Others: 5%
- Housing: 14%
- Transport: 14%
- Manage Database Triggers:
- The system uses a PostgreSQL trigger (
payslip_status_update
) to generate email notifications when a payslip's status changes to PAID. - To list all triggers in the database:
SELECT trigger_name, event_manipulation, event_object_table, action_statement FROM information_schema.triggers WHERE trigger_schema = 'public';
- To verify the
payslip_status_update
trigger:SELECT trigger_name, event_manipulation, event_object_table, action_statement FROM information_schema.triggers WHERE trigger_schema = 'public' AND trigger_name = 'payslip_status_update';
- To recreate the trigger if missing:
CREATE OR REPLACE FUNCTION notify_payslip_paid() RETURNS TRIGGER AS $$ BEGIN IF (OLD.status = 'PENDING' AND NEW.status = 'PAID') THEN INSERT INTO notification (employee_id, message, created_at) VALUES ( NEW.employee_id, 'Dear ' || (SELECT first_name FROM employees WHERE id = NEW.employee_id) || ', your ' || NEW.month || '/' || NEW.year || ' salary from Institution ' || NEW.net_salary || ' has been credited to your ' || (SELECT code FROM employees WHERE id = NEW.employee_id) || ' account successfully', NOW() ); END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS payslip_status_update ON payslips; CREATE TRIGGER payslip_status_update AFTER UPDATE ON payslips FOR EACH ROW EXECUTE FUNCTION notify_payslip_paid();
-
Build and Run:
mvn clean install mvn spring-boot:run
-
Diagrams:
- The system flow diagram and entity-relationship diagram (ERD) are created using Mermaid and render automatically in GitHub and other Markdown viewers that support Mermaid.
- Authenticate: POST to
/api/auth/login
with{"email": "employee@example.com", "password": "password"}
to get a JWT token. UseBearer <token>
in theAuthorization
header. - Manage Employees: Use
/api/employee
endpoints (ROLE_MANAGER for creation/updates). - Manage Deductions: Use
/api/deduction
endpoints (ROLE_MANAGER). - Generate Payroll: Use
/api/payroll/generate/{month}/{year}
(ROLE_MANAGER) and approve with/api/payroll/approve/{id}
(ROLE_ADMIN). - Test Email Notifications:
- Approve a payslip to trigger an email notification.
- View the email in MailHog’s web UI (
http://localhost:8025
). - Verify the trigger by checking the
email_notifications
table:SELECT * FROM email_notifications;
- View Payslips: Employees use
/api/payroll/employee/{employeeId}/{month}/{year}
; managers use/api/payroll/{month}/{year}
.
A PostgreSQL trigger generates email notifications when a payslip's status changes to PAID:
CREATE OR REPLACE FUNCTION notify_payslip_paid()
RETURNS TRIGGER AS $$
BEGIN
IF (OLD.status = 'PENDING' AND NEW.status = 'PAID') THEN
INSERT INTO notification (employee_id, message, created_at)
VALUES (
NEW.employee_id,
'Dear ' ||
(SELECT first_name FROM employees WHERE id = NEW.employee_id) ||
', your ' ||
NEW.month || '/' || NEW.year ||
' salary from Institution ' ||
NEW.net_salary ||
' has been credited to your ' ||
(SELECT code FROM employees WHERE id = NEW.employee_id) ||
' account successfully',
NOW()
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER payslip_status_update
AFTER UPDATE ON payslips
FOR EACH ROW
EXECUTE FUNCTION notify_payslip_paid();
The EmailService
processes these notifications via the configured SMTP server (MailHog for testing).
payroll-management-system/
├── src/
│ ├── main/
│ │ ├── java/com/sylvain/payroll_management_system/
│ │ │ ├── PayrollManagementSystemApplication.java
│ │ │ ├── config/
│ │ │ │ ├── SwaggerConfig.java
│ │ │ │ ├── ThymeleafConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── AuthController.java
│ │ │ │ ├── DeductionController.java
│ │ │ │ ├── EmployeeController.java
│ │ │ │ ├── EmploymentController.java
│ │ │ │ ├── PaySlipController.java
│ │ │ ├── dto/
│ │ │ │ ├── AdminCreateDto.java
│ │ │ │ ├── AuthResponseDto.java
│ │ │ │ ├── DeductionRegisterDto.java
│ │ │ │ ├── DeductionUpdateDto.java
│ │ │ │ ├── EmailDto.java
│ │ │ │ ├── EmployeeRegisterDto.java
│ │ │ │ ├── EmployeeResponseDto.java
│ │ │ │ ├── EmploymentCreateDto.java
│ │ │ │ ├── LoginDto.java
│ │ │ │ ├── ResetPasswordDto.java
│ │ │ │ ├── SuccessResponse.java
│ │ │ │ ├── VerifyDto.java
│ │ │ ├── email/
│ │ │ │ ├── EmailService.java
│ │ │ ├── exceptions/
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ ├── InvalidJwtException.java
│ │ │ ├── model/
│ │ │ │ ├── DeductionModel.java
│ │ │ │ ├── EmployeeModel.java
│ │ │ │ ├── EmploymentModel.java
│ │ │ │ ├── PaySlipModel.java
│ │ │ ├── repository/
│ │ │ │ ├── DeductionRepository.java
│ │ │ │ ├── EmployeeRepository.java
│ │ │ │ ├── EmploymentRepository.java
│ │ │ │ ├── PaySlipRepository.java
│ │ │ ├── security/
│ │ │ │ ├── ApplicationConfig.java
│ │ │ │ ├── CustomUserDetail.java
│ │ │ │ ├── EmployeeServiceImplementation.java
│ │ │ │ ├── EmployeeUserDetail.java
│ │ │ │ ├── JwtConstant.java
│ │ │ │ ├── JwtProvider.java
│ │ │ │ ├── JwtTokenValidator.java
│ │ │ │ ├── UserServiceImplementation.java
│ │ │ ├── service/
│ │ │ │ ├── DeductionService.java
│ │ │ │ ├── EmployeeService.java
│ │ │ │ ├── EmploymentService.java
│ │ │ │ ├── PaySlipService.java
│ │ │ ├── utils/
│ │ │ │ ├── CodeGenerator.java
│ │ │ │ ├── EmailVerifiedStatus.java
│ │ │ │ ├── EmployeeStatus.java
│ │ │ │ ├── EmploymentStatus.java
│ │ │ │ ├── JwtUtils.java
│ │ │ │ ├── NotificationStatus.java
│ │ │ │ ├── OtpType.java
│ │ │ │ ├── PasswordVerifiedStatus.java
│ │ │ │ ├── PaySlipStatus.java
│ │ │ │ ├── Role.java
│ │ │ ├── validator/
│ │ │ │ ├── PageableValidator.java
│ │ │ │ ├── PasswordConstraintValidator.java
│ │ │ │ ├── RwandaNationalIdValidator.java
│ │ │ │ ├── RwandanPhoneNumberValidator.java
│ │ │ │ ├── ValidPassword.java
│ │ │ │ ├── ValidRwandaId.java
│ │ │ │ ├── ValidRwandanPhoneNumber.java
│ │ ├── resources/
│ │ │ ├── application.properties
│ │ │ ├── db/migration/
│ │ │ │ ├── V1__create_payroll_trigger.sql
│ │ │ ├── templates/
│ │ │ │ ├── AccountVerificationEmailTemplate.html
│ │ │ │ ├── PayrollNotificationTemplate.html
│ │ │ │ ├── PasswordResetEmailTemplate.html
│ ├── test/
│ │ ├── java/com/sylvain/payroll_management_system/
│ │ │ ├── PayrollManagementSystemApplicationTests.java
│ │ │ ├── service/
│ │ │ │ ├── PaySlipServiceTest.java
├── pom.xml
├── README.md