Spaces:
Configuration error
Configuration error
Add api face-id, fix bug and refactor code
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- pom.xml +9 -0
- src/main/java/com/attendenceSystem/module/attendance/api/AttendanceManagerApiController.java +0 -1
- src/main/java/com/attendenceSystem/module/attendance/controller/AttendanceController.java +2 -1
- src/main/java/com/attendenceSystem/module/attendance/dto/response/AttendanceResponse.java +3 -3
- src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveDetailResponse.java +2 -1
- src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveRequestResponse.java +2 -2
- src/main/java/com/attendenceSystem/module/attendance/entity/AttendanceRecord.java +3 -3
- src/main/java/com/attendenceSystem/module/attendance/entity/LeaveRequest.java +2 -2
- src/main/java/com/attendenceSystem/module/attendance/mapper/response/AttendanceResponseMapper.java +9 -3
- src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveDetailResponseMapper.java +0 -1
- src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveRequestResponseMapper.java +0 -1
- src/main/java/com/attendenceSystem/module/attendance/model/DateRange.java +9 -0
- src/main/java/com/attendenceSystem/module/attendance/service/AttendanceService.java +12 -2
- src/main/java/com/attendenceSystem/module/attendance/service/impl/AttendanceServiceImpl.java +59 -39
- src/main/java/com/attendenceSystem/module/attendance/util/AttendanceCalculator.java +5 -5
- src/main/java/com/attendenceSystem/module/attendance/util/impl/AttendanceCalculatorImpl.java +5 -5
- src/main/java/com/attendenceSystem/module/faceid/controller/FaceIdAttendanceController.java +30 -0
- src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAction.java +8 -0
- src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAttendanceRequest.java +29 -0
- src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAttendanceResponse.java +22 -0
- src/main/java/com/attendenceSystem/module/faceid/dto/response/FaceProfileResponse.java +3 -3
- src/main/java/com/attendenceSystem/module/faceid/entity/FaceIdRecognition.java +61 -0
- src/main/java/com/attendenceSystem/module/faceid/entity/FaceProfile.java +3 -2
- src/main/java/com/attendenceSystem/module/faceid/entity/FaceSample.java +2 -1
- src/main/java/com/attendenceSystem/module/faceid/filter/FaceIdApiKeyFilter.java +54 -0
- src/main/java/com/attendenceSystem/module/faceid/repository/FaceIdRecognitionRepository.java +10 -0
- src/main/java/com/attendenceSystem/module/faceid/service/FaceIdAttendanceService.java +8 -0
- src/main/java/com/attendenceSystem/module/faceid/service/impl/FaceIdAttendanceServiceImpl.java +200 -0
- src/main/java/com/attendenceSystem/module/log/dto/response/AuditLogResponse.java +3 -1
- src/main/java/com/attendenceSystem/module/log/entity/AuditLog.java +2 -1
- src/main/java/com/attendenceSystem/module/log/mapper/response/AuditLogResponseMapper.java +1 -2
- src/main/java/com/attendenceSystem/module/otp/dto/response/OtpResponse.java +3 -3
- src/main/java/com/attendenceSystem/module/otp/entity/Otp.java +4 -3
- src/main/java/com/attendenceSystem/module/otp/mapper/request/SendOtpRequestMapper.java +3 -3
- src/main/java/com/attendenceSystem/module/report/controller/ReportController.java +1 -0
- src/main/java/com/attendenceSystem/module/report/dto/request/CreateReportRequest.java +3 -0
- src/main/java/com/attendenceSystem/module/report/dto/response/ReportDetailResponse.java +5 -4
- src/main/java/com/attendenceSystem/module/report/dto/response/ReportResponse.java +5 -3
- src/main/java/com/attendenceSystem/module/report/entity/Report.java +8 -2
- src/main/java/com/attendenceSystem/module/report/mapper/response/ReportDetailResponseMapper.java +1 -0
- src/main/java/com/attendenceSystem/module/report/mapper/response/ReportResponseMapper.java +3 -1
- src/main/java/com/attendenceSystem/module/report/service/impl/ReportServiceImpl.java +55 -2
- src/main/java/com/attendenceSystem/module/user/controller/AccountController.java +0 -2
- src/main/java/com/attendenceSystem/module/user/dto/request/UpdateUserInformationRequest.java +0 -1
- src/main/java/com/attendenceSystem/module/user/entity/User.java +3 -2
- src/main/java/com/attendenceSystem/module/user/service/impl/UserServiceImpl.java +0 -4
- src/main/resources/application.properties +5 -1
- src/main/resources/templates/cms/dashboard/dashboard-employee.html +3 -28
- src/main/resources/templates/cms/document/document-detail.html +11 -1
- src/main/resources/templates/cms/document/document-list.html +17 -15
pom.xml
CHANGED
|
@@ -107,6 +107,15 @@
|
|
| 107 |
<groupId>org.springframework.boot</groupId>
|
| 108 |
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
| 109 |
</dependency>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
</dependencies>
|
| 111 |
|
| 112 |
<build>
|
|
|
|
| 107 |
<groupId>org.springframework.boot</groupId>
|
| 108 |
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
| 109 |
</dependency>
|
| 110 |
+
<dependency>
|
| 111 |
+
<groupId>com.fasterxml.jackson.core</groupId>
|
| 112 |
+
<artifactId>jackson-databind</artifactId>
|
| 113 |
+
</dependency>
|
| 114 |
+
<dependency>
|
| 115 |
+
<groupId>com.github.ben-manes.caffeine</groupId>
|
| 116 |
+
<artifactId>caffeine</artifactId>
|
| 117 |
+
<version>3.1.8</version>
|
| 118 |
+
</dependency>
|
| 119 |
</dependencies>
|
| 120 |
|
| 121 |
<build>
|
src/main/java/com/attendenceSystem/module/attendance/api/AttendanceManagerApiController.java
CHANGED
|
@@ -2,7 +2,6 @@ package com.attendenceSystem.module.attendance.api;
|
|
| 2 |
|
| 3 |
import java.time.LocalDate;
|
| 4 |
import java.util.List;
|
| 5 |
-
import java.util.Map;
|
| 6 |
|
| 7 |
import org.springframework.http.ResponseEntity;
|
| 8 |
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
|
| 2 |
|
| 3 |
import java.time.LocalDate;
|
| 4 |
import java.util.List;
|
|
|
|
| 5 |
|
| 6 |
import org.springframework.http.ResponseEntity;
|
| 7 |
import org.springframework.web.bind.annotation.GetMapping;
|
src/main/java/com/attendenceSystem/module/attendance/controller/AttendanceController.java
CHANGED
|
@@ -55,6 +55,7 @@ public class AttendanceController {
|
|
| 55 |
|
| 56 |
@GetMapping(Routes.Attendance.HISTORY)
|
| 57 |
public String attendanceHistory(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
|
|
|
| 58 |
model.addAttribute("attendanceHistory", attendanceService.getAttendanceHistory(pageable));
|
| 59 |
return Views.Attendance.HISTORY;
|
| 60 |
}
|
|
@@ -83,7 +84,7 @@ public class AttendanceController {
|
|
| 83 |
}
|
| 84 |
attendanceService.createLeaveRequest(createLeaveRequest);
|
| 85 |
redirectAttributes.addFlashAttribute("successMessage", "Yêu cầu nghỉ phép đã được gửi.");
|
| 86 |
-
return
|
| 87 |
}
|
| 88 |
|
| 89 |
@ExceptionHandler({
|
|
|
|
| 55 |
|
| 56 |
@GetMapping(Routes.Attendance.HISTORY)
|
| 57 |
public String attendanceHistory(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
| 58 |
+
|
| 59 |
model.addAttribute("attendanceHistory", attendanceService.getAttendanceHistory(pageable));
|
| 60 |
return Views.Attendance.HISTORY;
|
| 61 |
}
|
|
|
|
| 84 |
}
|
| 85 |
attendanceService.createLeaveRequest(createLeaveRequest);
|
| 86 |
redirectAttributes.addFlashAttribute("successMessage", "Yêu cầu nghỉ phép đã được gửi.");
|
| 87 |
+
return Routes.REDIRECT + Routes.Attendance.ROOT + Routes.Attendance.LEAVE + Routes.Action.CREATE;
|
| 88 |
}
|
| 89 |
|
| 90 |
@ExceptionHandler({
|
src/main/java/com/attendenceSystem/module/attendance/dto/response/AttendanceResponse.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 7 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
|
@@ -15,8 +15,8 @@ public record AttendanceResponse(
|
|
| 15 |
String fullName,
|
| 16 |
Department department,
|
| 17 |
LocalDate attendanceDate,
|
| 18 |
-
|
| 19 |
-
|
| 20 |
AttendanceStatus status,
|
| 21 |
boolean late,
|
| 22 |
boolean earlyLeave,
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.dto.response;
|
| 2 |
|
|
|
|
| 3 |
import java.time.LocalDate;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 7 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
|
|
|
| 15 |
String fullName,
|
| 16 |
Department department,
|
| 17 |
LocalDate attendanceDate,
|
| 18 |
+
LocalDateTime checkInTime,
|
| 19 |
+
LocalDateTime checkOutTime,
|
| 20 |
AttendanceStatus status,
|
| 21 |
boolean late,
|
| 22 |
boolean earlyLeave,
|
src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveDetailResponse.java
CHANGED
|
@@ -2,6 +2,7 @@ package com.attendenceSystem.module.attendance.dto.response;
|
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 7 |
|
|
@@ -13,5 +14,5 @@ public record LeaveDetailResponse(
|
|
| 13 |
LocalDate endDate,
|
| 14 |
String reason,
|
| 15 |
LeaveStatus status,
|
| 16 |
-
|
| 17 |
}
|
|
|
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
| 5 |
+
import java.time.LocalDateTime;
|
| 6 |
|
| 7 |
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 8 |
|
|
|
|
| 14 |
LocalDate endDate,
|
| 15 |
String reason,
|
| 16 |
LeaveStatus status,
|
| 17 |
+
LocalDateTime createdAt) {
|
| 18 |
}
|
src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveRequestResponse.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 7 |
|
|
@@ -13,5 +13,5 @@ public record LeaveRequestResponse(
|
|
| 13 |
LocalDate endDate,
|
| 14 |
String reason,
|
| 15 |
LeaveStatus status,
|
| 16 |
-
|
| 17 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.dto.response;
|
| 2 |
|
|
|
|
| 3 |
import java.time.LocalDate;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 7 |
|
|
|
|
| 13 |
LocalDate endDate,
|
| 14 |
String reason,
|
| 15 |
LeaveStatus status,
|
| 16 |
+
LocalDateTime createdAt) {
|
| 17 |
}
|
src/main/java/com/attendenceSystem/module/attendance/entity/AttendanceRecord.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.entity;
|
| 2 |
|
| 3 |
-
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 7 |
import com.attendenceSystem.module.user.entity.User;
|
|
@@ -44,13 +44,13 @@ public class AttendanceRecord {
|
|
| 44 |
private User user;
|
| 45 |
|
| 46 |
@Column(name = "check_in_time", nullable = false)
|
| 47 |
-
private
|
| 48 |
|
| 49 |
@Column(name = "attendance_date", nullable = false)
|
| 50 |
private LocalDate attendanceDate;
|
| 51 |
|
| 52 |
@Column(name = "check_out_time")
|
| 53 |
-
private
|
| 54 |
|
| 55 |
@Enumerated(EnumType.STRING)
|
| 56 |
@Column(name = "status", nullable = false)
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.entity;
|
| 2 |
|
|
|
|
| 3 |
import java.time.LocalDate;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 7 |
import com.attendenceSystem.module.user.entity.User;
|
|
|
|
| 44 |
private User user;
|
| 45 |
|
| 46 |
@Column(name = "check_in_time", nullable = false)
|
| 47 |
+
private LocalDateTime checkInTime;
|
| 48 |
|
| 49 |
@Column(name = "attendance_date", nullable = false)
|
| 50 |
private LocalDate attendanceDate;
|
| 51 |
|
| 52 |
@Column(name = "check_out_time")
|
| 53 |
+
private LocalDateTime checkOutTime;
|
| 54 |
|
| 55 |
@Enumerated(EnumType.STRING)
|
| 56 |
@Column(name = "status", nullable = false)
|
src/main/java/com/attendenceSystem/module/attendance/entity/LeaveRequest.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.entity;
|
| 2 |
|
| 3 |
-
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
|
|
@@ -53,5 +53,5 @@ public class LeaveRequest {
|
|
| 53 |
|
| 54 |
@CreationTimestamp
|
| 55 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 56 |
-
private
|
| 57 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.entity;
|
| 2 |
|
|
|
|
| 3 |
import java.time.LocalDate;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
|
|
|
|
| 53 |
|
| 54 |
@CreationTimestamp
|
| 55 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 56 |
+
private LocalDateTime createdAt;
|
| 57 |
}
|
src/main/java/com/attendenceSystem/module/attendance/mapper/response/AttendanceResponseMapper.java
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.mapper.response;
|
| 2 |
|
|
|
|
|
|
|
|
|
|
| 3 |
import org.springframework.stereotype.Component;
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
|
@@ -14,9 +17,12 @@ public class AttendanceResponseMapper {
|
|
| 14 |
private final AttendanceCalculator attendanceCalculator;
|
| 15 |
|
| 16 |
public AttendanceResponse fromEntity(AttendanceRecord attendance) {
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
return AttendanceResponse.builder()
|
| 22 |
.id(attendance.getId())
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.mapper.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
+
import java.time.ZoneId;
|
| 5 |
+
|
| 6 |
import org.springframework.stereotype.Component;
|
| 7 |
|
| 8 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
|
|
|
| 17 |
private final AttendanceCalculator attendanceCalculator;
|
| 18 |
|
| 19 |
public AttendanceResponse fromEntity(AttendanceRecord attendance) {
|
| 20 |
+
boolean late = attendanceCalculator.isLate(attendance.getCheckInTime());
|
| 21 |
+
boolean earlyLeave = attendanceCalculator.isEarlyLeave(attendance.getCheckOutTime());
|
| 22 |
+
long workingMinutes = attendanceCalculator.workingMinutes(
|
| 23 |
+
attendance.getCheckInTime(),
|
| 24 |
+
attendance.getCheckOutTime()
|
| 25 |
+
);
|
| 26 |
|
| 27 |
return AttendanceResponse.builder()
|
| 28 |
.id(attendance.getId())
|
src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveDetailResponseMapper.java
CHANGED
|
@@ -4,7 +4,6 @@ import org.springframework.stereotype.Component;
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.attendance.dto.response.LeaveDetailResponse;
|
| 6 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
| 7 |
-
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 8 |
|
| 9 |
import lombok.RequiredArgsConstructor;
|
| 10 |
|
|
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.attendance.dto.response.LeaveDetailResponse;
|
| 6 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
|
|
|
| 7 |
|
| 8 |
import lombok.RequiredArgsConstructor;
|
| 9 |
|
src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveRequestResponseMapper.java
CHANGED
|
@@ -4,7 +4,6 @@ import org.springframework.stereotype.Component;
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.attendance.dto.response.LeaveRequestResponse;
|
| 6 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
| 7 |
-
import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
| 8 |
|
| 9 |
import lombok.RequiredArgsConstructor;
|
| 10 |
|
|
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.attendance.dto.response.LeaveRequestResponse;
|
| 6 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
|
|
|
| 7 |
|
| 8 |
import lombok.RequiredArgsConstructor;
|
| 9 |
|
src/main/java/com/attendenceSystem/module/attendance/model/DateRange.java
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.attendance.model;
|
| 2 |
+
|
| 3 |
+
import java.time.LocalDate;
|
| 4 |
+
|
| 5 |
+
public record DateRange(
|
| 6 |
+
LocalDate startDate,
|
| 7 |
+
LocalDate endDate) {
|
| 8 |
+
|
| 9 |
+
}
|
src/main/java/com/attendenceSystem/module/attendance/service/AttendanceService.java
CHANGED
|
@@ -5,12 +5,16 @@ import org.springframework.data.domain.Pageable;
|
|
| 5 |
|
| 6 |
import java.time.LocalDate;
|
| 7 |
import java.util.List;
|
|
|
|
| 8 |
|
| 9 |
import com.attendenceSystem.module.attendance.dto.request.CreateLeaveRequest;
|
| 10 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 11 |
import com.attendenceSystem.module.attendance.dto.response.LeaveDetailResponse;
|
| 12 |
import com.attendenceSystem.module.attendance.dto.response.LeaveRequestResponse;
|
| 13 |
import com.attendenceSystem.module.attendance.dto.response.ManagerStatsResponse;
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
public interface AttendanceService {
|
| 16 |
|
|
@@ -18,15 +22,19 @@ public interface AttendanceService {
|
|
| 18 |
|
| 19 |
AttendanceResponse checkOut();
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
Page<AttendanceResponse> getAttendanceHistory(Pageable pageable);
|
| 22 |
|
| 23 |
ManagerStatsResponse getManagerStats(String departmentId, LocalDate startDate, LocalDate endDate);
|
| 24 |
|
| 25 |
-
ManagerStatsResponse getManagerStats(String departmentId, LocalDate startDate, LocalDate endDate,
|
| 26 |
|
| 27 |
List<AttendanceResponse> getManagerAttendanceList(String departmentId, LocalDate startDate, LocalDate endDate);
|
| 28 |
|
| 29 |
-
List<AttendanceResponse> getManagerAttendanceList(String departmentId, LocalDate startDate, LocalDate endDate,
|
| 30 |
|
| 31 |
LeaveRequestResponse createLeaveRequest(CreateLeaveRequest request);
|
| 32 |
|
|
@@ -35,4 +43,6 @@ public interface AttendanceService {
|
|
| 35 |
Page<LeaveRequestResponse> getAllLeaveRequests(Pageable pageable);
|
| 36 |
|
| 37 |
LeaveDetailResponse getLeaveDetail(Long id);
|
|
|
|
|
|
|
| 38 |
}
|
|
|
|
| 5 |
|
| 6 |
import java.time.LocalDate;
|
| 7 |
import java.util.List;
|
| 8 |
+
import java.util.Optional;
|
| 9 |
|
| 10 |
import com.attendenceSystem.module.attendance.dto.request.CreateLeaveRequest;
|
| 11 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 12 |
import com.attendenceSystem.module.attendance.dto.response.LeaveDetailResponse;
|
| 13 |
import com.attendenceSystem.module.attendance.dto.response.LeaveRequestResponse;
|
| 14 |
import com.attendenceSystem.module.attendance.dto.response.ManagerStatsResponse;
|
| 15 |
+
import com.attendenceSystem.module.attendance.entity.AttendanceRecord;
|
| 16 |
+
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 17 |
+
import com.attendenceSystem.module.user.entity.User;
|
| 18 |
|
| 19 |
public interface AttendanceService {
|
| 20 |
|
|
|
|
| 22 |
|
| 23 |
AttendanceResponse checkOut();
|
| 24 |
|
| 25 |
+
AttendanceResponse checkIn(User user);
|
| 26 |
+
|
| 27 |
+
AttendanceResponse checkOut(User user);
|
| 28 |
+
|
| 29 |
Page<AttendanceResponse> getAttendanceHistory(Pageable pageable);
|
| 30 |
|
| 31 |
ManagerStatsResponse getManagerStats(String departmentId, LocalDate startDate, LocalDate endDate);
|
| 32 |
|
| 33 |
+
ManagerStatsResponse getManagerStats(String departmentId, LocalDate startDate, LocalDate endDate, AttendanceStatus status);
|
| 34 |
|
| 35 |
List<AttendanceResponse> getManagerAttendanceList(String departmentId, LocalDate startDate, LocalDate endDate);
|
| 36 |
|
| 37 |
+
List<AttendanceResponse> getManagerAttendanceList(String departmentId, LocalDate startDate, LocalDate endDate, AttendanceStatus status);
|
| 38 |
|
| 39 |
LeaveRequestResponse createLeaveRequest(CreateLeaveRequest request);
|
| 40 |
|
|
|
|
| 43 |
Page<LeaveRequestResponse> getAllLeaveRequests(Pageable pageable);
|
| 44 |
|
| 45 |
LeaveDetailResponse getLeaveDetail(Long id);
|
| 46 |
+
|
| 47 |
+
Optional<AttendanceRecord> getTodayAttendanceRecord(User user);
|
| 48 |
}
|
src/main/java/com/attendenceSystem/module/attendance/service/impl/AttendanceServiceImpl.java
CHANGED
|
@@ -1,12 +1,14 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.service.impl;
|
| 2 |
|
| 3 |
-
import java.time.Instant;
|
| 4 |
import java.time.LocalDate;
|
|
|
|
| 5 |
import java.util.List;
|
| 6 |
import java.util.Optional;
|
| 7 |
|
| 8 |
import org.springframework.beans.factory.annotation.Value;
|
| 9 |
import org.springframework.dao.DataIntegrityViolationException;
|
|
|
|
|
|
|
| 10 |
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
| 11 |
import org.springframework.stereotype.Service;
|
| 12 |
import org.springframework.transaction.annotation.Transactional;
|
|
@@ -27,6 +29,7 @@ import com.attendenceSystem.module.attendance.mapper.response.AttendanceResponse
|
|
| 27 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
| 28 |
import com.attendenceSystem.module.attendance.mapper.response.LeaveDetailResponseMapper;
|
| 29 |
import com.attendenceSystem.module.attendance.mapper.response.LeaveRequestResponseMapper;
|
|
|
|
| 30 |
import com.attendenceSystem.module.attendance.repository.LeaveRequestRepository;
|
| 31 |
import com.attendenceSystem.module.attendance.repository.AttendanceRecordRepository;
|
| 32 |
import com.attendenceSystem.module.attendance.service.AttendanceService;
|
|
@@ -61,16 +64,26 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 61 |
@Transactional
|
| 62 |
@Override
|
| 63 |
public AttendanceResponse checkIn() {
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
try {
|
| 68 |
Optional<AttendanceRecord> existing = attendanceRecordRepository
|
| 69 |
.findByUserAndAttendanceDateWithLock(user, today);
|
| 70 |
if (existing.isPresent()) {
|
| 71 |
throw new AlreadyCheckedInException("Bạn đã điểm danh hôm nay");
|
| 72 |
}
|
| 73 |
-
|
| 74 |
boolean late = attendanceCalculator.isLate(checkInTime);
|
| 75 |
AttendanceStatus status = late ? AttendanceStatus.LATE : AttendanceStatus.PRESENT;
|
| 76 |
AttendanceRecord attendanceRecord = AttendanceRecord.builder()
|
|
@@ -82,7 +95,7 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 82 |
.build();
|
| 83 |
attendanceRecordRepository.save(attendanceRecord);
|
| 84 |
return attendanceResponseMapper.fromEntity(attendanceRecord);
|
| 85 |
-
} catch (
|
| 86 |
throw new AlreadyCheckedInException("Bạn đã điểm danh hôm nay");
|
| 87 |
} catch (ObjectOptimisticLockingFailureException e) {
|
| 88 |
throw new AlreadyCheckedInException("Dữ liệu đã bị thay đổi, vui lòng thử lại");
|
|
@@ -91,10 +104,8 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 91 |
|
| 92 |
@Transactional
|
| 93 |
@Override
|
| 94 |
-
public AttendanceResponse checkOut() {
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
LocalDate today = LocalDate.now(timeZoneProvider.getZoneId());
|
| 98 |
AttendanceRecord attendance = attendanceRecordRepository
|
| 99 |
.findByUserAndAttendanceDateWithLock(user, today)
|
| 100 |
.orElseThrow(() -> new NotCheckedInException(
|
|
@@ -105,7 +116,7 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 105 |
if (attendance.getStatus() != AttendanceStatus.PRESENT && attendance.getStatus() != AttendanceStatus.LATE) {
|
| 106 |
throw new InvalidAttendanceStateException("Trạng thái điểm danh không hợp lệ");
|
| 107 |
}
|
| 108 |
-
|
| 109 |
if (checkOutTime.isBefore(attendance.getCheckInTime())) {
|
| 110 |
throw new InvalidAttendanceStateException("Thời gian checkout phải sau thời gian check-in");
|
| 111 |
}
|
|
@@ -117,9 +128,8 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 117 |
attendanceRecordRepository.save(attendance);
|
| 118 |
return attendanceResponseMapper.fromEntity(attendance);
|
| 119 |
}
|
| 120 |
-
|
| 121 |
@Override
|
| 122 |
-
public
|
| 123 |
User user = getCurrentUser();
|
| 124 |
|
| 125 |
return attendanceRecordRepository
|
|
@@ -167,19 +177,9 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 167 |
.absent(0)
|
| 168 |
.build();
|
| 169 |
}
|
|
|
|
| 170 |
|
| 171 |
-
|
| 172 |
-
LocalDate effectiveEndDate = endDate;
|
| 173 |
-
if (effectiveStartDate == null && effectiveEndDate == null) {
|
| 174 |
-
effectiveStartDate = LocalDate.now(timeZoneProvider.getZoneId()).minusMonths(6);
|
| 175 |
-
effectiveEndDate = LocalDate.now(timeZoneProvider.getZoneId());
|
| 176 |
-
} else if (effectiveStartDate != null && effectiveEndDate == null) {
|
| 177 |
-
effectiveEndDate = effectiveStartDate;
|
| 178 |
-
} else if (effectiveStartDate == null && effectiveEndDate != null) {
|
| 179 |
-
effectiveStartDate = effectiveEndDate;
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
List<AttendanceRecord> records = getFilteredRecords(effectiveStartDate, effectiveEndDate, status);
|
| 183 |
|
| 184 |
long checkedIn = 0;
|
| 185 |
long checkedOut = 0;
|
|
@@ -227,19 +227,9 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 227 |
} else {
|
| 228 |
employees = userRepository.findByRoleNot(Role.ADMIN);
|
| 229 |
}
|
|
|
|
| 230 |
|
| 231 |
-
|
| 232 |
-
LocalDate effectiveEndDate = endDate;
|
| 233 |
-
if (effectiveStartDate == null && effectiveEndDate == null) {
|
| 234 |
-
effectiveStartDate = LocalDate.now(timeZoneProvider.getZoneId()).minusMonths(6);
|
| 235 |
-
effectiveEndDate = LocalDate.now(timeZoneProvider.getZoneId());
|
| 236 |
-
} else if (effectiveStartDate != null && effectiveEndDate == null) {
|
| 237 |
-
effectiveEndDate = effectiveStartDate;
|
| 238 |
-
} else if (effectiveStartDate == null && effectiveEndDate != null) {
|
| 239 |
-
effectiveStartDate = effectiveEndDate;
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
List<AttendanceRecord> records = getFilteredRecords(effectiveStartDate, effectiveEndDate, status);
|
| 243 |
|
| 244 |
return records.stream()
|
| 245 |
.map(attendanceResponseMapper::fromEntity)
|
|
@@ -271,14 +261,14 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 271 |
}
|
| 272 |
|
| 273 |
@Override
|
| 274 |
-
public
|
| 275 |
User user = getCurrentUser();
|
| 276 |
return leaveRequestRepository.findByUser(user, pageable)
|
| 277 |
.map(leaveRequestResponseMapper::fromEntity);
|
| 278 |
}
|
| 279 |
|
| 280 |
@Override
|
| 281 |
-
public
|
| 282 |
return leaveRequestRepository.findAll(pageable).map(leaveRequestResponseMapper::fromEntity);
|
| 283 |
}
|
| 284 |
|
|
@@ -292,13 +282,43 @@ public class AttendanceServiceImpl implements AttendanceService {
|
|
| 292 |
return leaveDetailResponseMapper.fromEntity(leave);
|
| 293 |
}
|
| 294 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
private User getCurrentUser() {
|
| 296 |
if (!SecurityUtil.isAuthenticated()) {
|
| 297 |
throw new IllegalStateException("Người dùng chưa đăng nhập");
|
| 298 |
}
|
| 299 |
String currentUsername = SecurityUtil.getCurrentUserName();
|
| 300 |
return userRepository.findByUsername(currentUsername)
|
| 301 |
-
.orElseThrow(() -> new
|
| 302 |
"Không tìm thấy người dùng với tên đăng nhập: " + currentUsername));
|
| 303 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.service.impl;
|
| 2 |
|
|
|
|
| 3 |
import java.time.LocalDate;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
import java.util.List;
|
| 6 |
import java.util.Optional;
|
| 7 |
|
| 8 |
import org.springframework.beans.factory.annotation.Value;
|
| 9 |
import org.springframework.dao.DataIntegrityViolationException;
|
| 10 |
+
import org.springframework.data.domain.Page;
|
| 11 |
+
import org.springframework.data.domain.Pageable;
|
| 12 |
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
| 13 |
import org.springframework.stereotype.Service;
|
| 14 |
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
| 29 |
import com.attendenceSystem.module.attendance.entity.LeaveRequest;
|
| 30 |
import com.attendenceSystem.module.attendance.mapper.response.LeaveDetailResponseMapper;
|
| 31 |
import com.attendenceSystem.module.attendance.mapper.response.LeaveRequestResponseMapper;
|
| 32 |
+
import com.attendenceSystem.module.attendance.model.DateRange;
|
| 33 |
import com.attendenceSystem.module.attendance.repository.LeaveRequestRepository;
|
| 34 |
import com.attendenceSystem.module.attendance.repository.AttendanceRecordRepository;
|
| 35 |
import com.attendenceSystem.module.attendance.service.AttendanceService;
|
|
|
|
| 64 |
@Transactional
|
| 65 |
@Override
|
| 66 |
public AttendanceResponse checkIn() {
|
| 67 |
+
return checkIn(getCurrentUser());
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
@Transactional
|
| 71 |
+
@Override
|
| 72 |
+
public AttendanceResponse checkOut() {
|
| 73 |
+
return checkOut(getCurrentUser());
|
| 74 |
+
}
|
| 75 |
|
| 76 |
+
@Transactional
|
| 77 |
+
@Override
|
| 78 |
+
public AttendanceResponse checkIn(User user) {
|
| 79 |
+
LocalDate today = todayDate();
|
| 80 |
try {
|
| 81 |
Optional<AttendanceRecord> existing = attendanceRecordRepository
|
| 82 |
.findByUserAndAttendanceDateWithLock(user, today);
|
| 83 |
if (existing.isPresent()) {
|
| 84 |
throw new AlreadyCheckedInException("Bạn đã điểm danh hôm nay");
|
| 85 |
}
|
| 86 |
+
LocalDateTime checkInTime = LocalDateTime.now();
|
| 87 |
boolean late = attendanceCalculator.isLate(checkInTime);
|
| 88 |
AttendanceStatus status = late ? AttendanceStatus.LATE : AttendanceStatus.PRESENT;
|
| 89 |
AttendanceRecord attendanceRecord = AttendanceRecord.builder()
|
|
|
|
| 95 |
.build();
|
| 96 |
attendanceRecordRepository.save(attendanceRecord);
|
| 97 |
return attendanceResponseMapper.fromEntity(attendanceRecord);
|
| 98 |
+
} catch (DataIntegrityViolationException e) {
|
| 99 |
throw new AlreadyCheckedInException("Bạn đã điểm danh hôm nay");
|
| 100 |
} catch (ObjectOptimisticLockingFailureException e) {
|
| 101 |
throw new AlreadyCheckedInException("Dữ liệu đã bị thay đổi, vui lòng thử lại");
|
|
|
|
| 104 |
|
| 105 |
@Transactional
|
| 106 |
@Override
|
| 107 |
+
public AttendanceResponse checkOut(User user) {
|
| 108 |
+
LocalDate today = todayDate();
|
|
|
|
|
|
|
| 109 |
AttendanceRecord attendance = attendanceRecordRepository
|
| 110 |
.findByUserAndAttendanceDateWithLock(user, today)
|
| 111 |
.orElseThrow(() -> new NotCheckedInException(
|
|
|
|
| 116 |
if (attendance.getStatus() != AttendanceStatus.PRESENT && attendance.getStatus() != AttendanceStatus.LATE) {
|
| 117 |
throw new InvalidAttendanceStateException("Trạng thái điểm danh không hợp lệ");
|
| 118 |
}
|
| 119 |
+
LocalDateTime checkOutTime = LocalDateTime.now();
|
| 120 |
if (checkOutTime.isBefore(attendance.getCheckInTime())) {
|
| 121 |
throw new InvalidAttendanceStateException("Thời gian checkout phải sau thời gian check-in");
|
| 122 |
}
|
|
|
|
| 128 |
attendanceRecordRepository.save(attendance);
|
| 129 |
return attendanceResponseMapper.fromEntity(attendance);
|
| 130 |
}
|
|
|
|
| 131 |
@Override
|
| 132 |
+
public Page<AttendanceResponse> getAttendanceHistory(final Pageable pageable) {
|
| 133 |
User user = getCurrentUser();
|
| 134 |
|
| 135 |
return attendanceRecordRepository
|
|
|
|
| 177 |
.absent(0)
|
| 178 |
.build();
|
| 179 |
}
|
| 180 |
+
DateRange dateRange = getDateRange(startDate, endDate);
|
| 181 |
|
| 182 |
+
List<AttendanceRecord> records = getFilteredRecords(dateRange.startDate(), dateRange.endDate(), status);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
long checkedIn = 0;
|
| 185 |
long checkedOut = 0;
|
|
|
|
| 227 |
} else {
|
| 228 |
employees = userRepository.findByRoleNot(Role.ADMIN);
|
| 229 |
}
|
| 230 |
+
DateRange dateRange = getDateRange(startDate, endDate);
|
| 231 |
|
| 232 |
+
List<AttendanceRecord> records = getFilteredRecords(dateRange.startDate(), dateRange.endDate(), status);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
return records.stream()
|
| 235 |
.map(attendanceResponseMapper::fromEntity)
|
|
|
|
| 261 |
}
|
| 262 |
|
| 263 |
@Override
|
| 264 |
+
public Page<LeaveRequestResponse> getLeaveRequests(final Pageable pageable) {
|
| 265 |
User user = getCurrentUser();
|
| 266 |
return leaveRequestRepository.findByUser(user, pageable)
|
| 267 |
.map(leaveRequestResponseMapper::fromEntity);
|
| 268 |
}
|
| 269 |
|
| 270 |
@Override
|
| 271 |
+
public Page<LeaveRequestResponse> getAllLeaveRequests(final Pageable pageable) {
|
| 272 |
return leaveRequestRepository.findAll(pageable).map(leaveRequestResponseMapper::fromEntity);
|
| 273 |
}
|
| 274 |
|
|
|
|
| 282 |
return leaveDetailResponseMapper.fromEntity(leave);
|
| 283 |
}
|
| 284 |
|
| 285 |
+
@Override
|
| 286 |
+
public Optional<AttendanceRecord> getTodayAttendanceRecord(User user) {
|
| 287 |
+
if (user == null) {
|
| 288 |
+
return Optional.empty();
|
| 289 |
+
}
|
| 290 |
+
LocalDate today = todayDate();
|
| 291 |
+
return attendanceRecordRepository.findByUserAndAttendanceDateWithLock(user, today);
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
private User getCurrentUser() {
|
| 295 |
if (!SecurityUtil.isAuthenticated()) {
|
| 296 |
throw new IllegalStateException("Người dùng chưa đăng nhập");
|
| 297 |
}
|
| 298 |
String currentUsername = SecurityUtil.getCurrentUserName();
|
| 299 |
return userRepository.findByUsername(currentUsername)
|
| 300 |
+
.orElseThrow(() -> new IllegalStateException(
|
| 301 |
"Không tìm thấy người dùng với tên đăng nhập: " + currentUsername));
|
| 302 |
}
|
| 303 |
+
|
| 304 |
+
private LocalDate todayDate() {
|
| 305 |
+
return LocalDate.now(timeZoneProvider.getZoneId());
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
private DateRange getDateRange(LocalDate startDate, LocalDate endDate) {
|
| 309 |
+
LocalDate effectiveStartDate = startDate;
|
| 310 |
+
LocalDate effectiveEndDate = endDate;
|
| 311 |
+
if (effectiveStartDate == null && effectiveEndDate == null) {
|
| 312 |
+
LocalDate today = todayDate();
|
| 313 |
+
effectiveStartDate = today.minusMonths(6);
|
| 314 |
+
effectiveEndDate = today;
|
| 315 |
+
} else if (effectiveStartDate != null && effectiveEndDate == null) {
|
| 316 |
+
effectiveEndDate = effectiveStartDate;
|
| 317 |
+
} else if (effectiveStartDate == null && effectiveEndDate != null) {
|
| 318 |
+
effectiveStartDate = effectiveEndDate;
|
| 319 |
+
}
|
| 320 |
+
return new DateRange(effectiveStartDate, effectiveEndDate);
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
|
| 324 |
}
|
src/main/java/com/attendenceSystem/module/attendance/util/AttendanceCalculator.java
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.util;
|
| 2 |
|
| 3 |
-
import java.time.
|
| 4 |
|
| 5 |
public interface AttendanceCalculator {
|
| 6 |
|
| 7 |
-
boolean isLate(
|
| 8 |
|
| 9 |
-
boolean isEarlyLeave(
|
| 10 |
|
| 11 |
-
long workingMinutes(
|
| 12 |
|
| 13 |
-
double totalWorkingHours(
|
| 14 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.util;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
|
| 5 |
public interface AttendanceCalculator {
|
| 6 |
|
| 7 |
+
boolean isLate(LocalDateTime checkInTime);
|
| 8 |
|
| 9 |
+
boolean isEarlyLeave(LocalDateTime checkOutTime);
|
| 10 |
|
| 11 |
+
long workingMinutes(LocalDateTime checkInTime, LocalDateTime checkOutTime);
|
| 12 |
|
| 13 |
+
double totalWorkingHours(LocalDateTime checkInTime, LocalDateTime checkOutTime);
|
| 14 |
}
|
src/main/java/com/attendenceSystem/module/attendance/util/impl/AttendanceCalculatorImpl.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.attendance.util.impl;
|
| 2 |
|
| 3 |
import java.time.Duration;
|
| 4 |
-
import java.time.
|
| 5 |
import java.time.LocalTime;
|
| 6 |
import java.time.ZoneId;
|
| 7 |
|
|
@@ -73,7 +73,7 @@ public class AttendanceCalculatorImpl implements AttendanceCalculator {
|
|
| 73 |
}
|
| 74 |
|
| 75 |
@Override
|
| 76 |
-
public boolean isLate(final
|
| 77 |
if (checkInTime == null) {
|
| 78 |
return false;
|
| 79 |
}
|
|
@@ -82,7 +82,7 @@ public class AttendanceCalculatorImpl implements AttendanceCalculator {
|
|
| 82 |
}
|
| 83 |
|
| 84 |
@Override
|
| 85 |
-
public boolean isEarlyLeave(final
|
| 86 |
if (checkOutTime == null) {
|
| 87 |
return false;
|
| 88 |
}
|
|
@@ -91,7 +91,7 @@ public class AttendanceCalculatorImpl implements AttendanceCalculator {
|
|
| 91 |
}
|
| 92 |
|
| 93 |
@Override
|
| 94 |
-
public long workingMinutes(final
|
| 95 |
if (checkInTime == null || checkOutTime == null) {
|
| 96 |
return 0;
|
| 97 |
}
|
|
@@ -99,7 +99,7 @@ public class AttendanceCalculatorImpl implements AttendanceCalculator {
|
|
| 99 |
}
|
| 100 |
|
| 101 |
@Override
|
| 102 |
-
public double totalWorkingHours(final
|
| 103 |
long minutes = workingMinutes(checkInTime, checkOutTime);
|
| 104 |
return minutes / 60.0;
|
| 105 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.attendance.util.impl;
|
| 2 |
|
| 3 |
import java.time.Duration;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
import java.time.LocalTime;
|
| 6 |
import java.time.ZoneId;
|
| 7 |
|
|
|
|
| 73 |
}
|
| 74 |
|
| 75 |
@Override
|
| 76 |
+
public boolean isLate(final LocalDateTime checkInTime) {
|
| 77 |
if (checkInTime == null) {
|
| 78 |
return false;
|
| 79 |
}
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
@Override
|
| 85 |
+
public boolean isEarlyLeave(final LocalDateTime checkOutTime) {
|
| 86 |
if (checkOutTime == null) {
|
| 87 |
return false;
|
| 88 |
}
|
|
|
|
| 91 |
}
|
| 92 |
|
| 93 |
@Override
|
| 94 |
+
public long workingMinutes(final LocalDateTime checkInTime, final LocalDateTime checkOutTime) {
|
| 95 |
if (checkInTime == null || checkOutTime == null) {
|
| 96 |
return 0;
|
| 97 |
}
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
@Override
|
| 102 |
+
public double totalWorkingHours(final LocalDateTime checkInTime, final LocalDateTime checkOutTime) {
|
| 103 |
long minutes = workingMinutes(checkInTime, checkOutTime);
|
| 104 |
return minutes / 60.0;
|
| 105 |
}
|
src/main/java/com/attendenceSystem/module/faceid/controller/FaceIdAttendanceController.java
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.controller;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceRequest;
|
| 4 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceResponse;
|
| 5 |
+
import com.attendenceSystem.module.faceid.service.FaceIdAttendanceService;
|
| 6 |
+
import com.attendenceSystem.module.faceid.service.impl.FaceIdAttendanceServiceImpl;
|
| 7 |
+
|
| 8 |
+
import jakarta.validation.Valid;
|
| 9 |
+
import lombok.RequiredArgsConstructor;
|
| 10 |
+
import org.springframework.http.ResponseEntity;
|
| 11 |
+
import org.springframework.web.bind.annotation.PostMapping;
|
| 12 |
+
import org.springframework.web.bind.annotation.RequestBody;
|
| 13 |
+
import org.springframework.web.bind.annotation.RequestMapping;
|
| 14 |
+
import org.springframework.web.bind.annotation.RestController;
|
| 15 |
+
|
| 16 |
+
@RestController
|
| 17 |
+
@RequestMapping("/api/face-id")
|
| 18 |
+
@RequiredArgsConstructor
|
| 19 |
+
public class FaceIdAttendanceController {
|
| 20 |
+
|
| 21 |
+
private final FaceIdAttendanceServiceImpl faceIdAttendanceService;
|
| 22 |
+
|
| 23 |
+
@PostMapping("/attendance")
|
| 24 |
+
public ResponseEntity<FaceIdAttendanceResponse> processAttendance(
|
| 25 |
+
@Valid @RequestBody FaceIdAttendanceRequest request) {
|
| 26 |
+
|
| 27 |
+
FaceIdAttendanceResponse response = faceIdAttendanceService.processAttendance(request);
|
| 28 |
+
return ResponseEntity.ok(response);
|
| 29 |
+
}
|
| 30 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAction.java
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.dto;
|
| 2 |
+
|
| 3 |
+
public enum FaceIdAction {
|
| 4 |
+
CHECKIN,
|
| 5 |
+
CHECKOUT,
|
| 6 |
+
IGNORED,
|
| 7 |
+
FAILED
|
| 8 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAttendanceRequest.java
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.dto;
|
| 2 |
+
|
| 3 |
+
import jakarta.validation.constraints.NotBlank;
|
| 4 |
+
import jakarta.validation.constraints.NotNull;
|
| 5 |
+
import jakarta.validation.constraints.Size;
|
| 6 |
+
import lombok.Data;
|
| 7 |
+
|
| 8 |
+
import java.time.LocalDateTime;
|
| 9 |
+
|
| 10 |
+
@Data
|
| 11 |
+
public class FaceIdAttendanceRequest {
|
| 12 |
+
@NotBlank(message = "studentCode không được để trống")
|
| 13 |
+
private String studentCode;
|
| 14 |
+
|
| 15 |
+
@NotNull(message = "confidence không được để trống")
|
| 16 |
+
private Double confidence;
|
| 17 |
+
|
| 18 |
+
@NotNull(message = "capturedAt không được để trống")
|
| 19 |
+
private LocalDateTime capturedAt;
|
| 20 |
+
|
| 21 |
+
private String cameraId;
|
| 22 |
+
|
| 23 |
+
private String imageId;
|
| 24 |
+
|
| 25 |
+
@Size(max = 255, message = "trackingId không được vượt quá 255 ký tự")
|
| 26 |
+
private String trackingId;
|
| 27 |
+
|
| 28 |
+
private Boolean liveness;
|
| 29 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/dto/FaceIdAttendanceResponse.java
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.dto;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 4 |
+
import lombok.AllArgsConstructor;
|
| 5 |
+
import lombok.Builder;
|
| 6 |
+
import lombok.Data;
|
| 7 |
+
import lombok.NoArgsConstructor;
|
| 8 |
+
|
| 9 |
+
import java.time.Instant;
|
| 10 |
+
|
| 11 |
+
@Data
|
| 12 |
+
@Builder
|
| 13 |
+
@NoArgsConstructor
|
| 14 |
+
@AllArgsConstructor
|
| 15 |
+
public class FaceIdAttendanceResponse {
|
| 16 |
+
private boolean success;
|
| 17 |
+
private FaceIdAction action;
|
| 18 |
+
private String message;
|
| 19 |
+
private String trackingId;
|
| 20 |
+
private Instant timestamp;
|
| 21 |
+
private AttendanceResponse attendance;
|
| 22 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/dto/response/FaceProfileResponse.java
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.faceid.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.
|
| 4 |
|
| 5 |
import lombok.Builder;
|
| 6 |
|
|
@@ -15,6 +15,6 @@ public record FaceProfileResponse(
|
|
| 15 |
String userName,
|
| 16 |
String userEmail,
|
| 17 |
String userFullName,
|
| 18 |
-
|
| 19 |
-
|
| 20 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.faceid.dto.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
|
| 5 |
import lombok.Builder;
|
| 6 |
|
|
|
|
| 15 |
String userName,
|
| 16 |
String userEmail,
|
| 17 |
String userFullName,
|
| 18 |
+
LocalDateTime createdAt,
|
| 19 |
+
LocalDateTime updatedAt) {
|
| 20 |
}
|
src/main/java/com/attendenceSystem/module/faceid/entity/FaceIdRecognition.java
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.entity;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAction;
|
| 4 |
+
import jakarta.persistence.*;
|
| 5 |
+
import lombok.AllArgsConstructor;
|
| 6 |
+
import lombok.Builder;
|
| 7 |
+
import lombok.Data;
|
| 8 |
+
import lombok.NoArgsConstructor;
|
| 9 |
+
import org.hibernate.annotations.CreationTimestamp;
|
| 10 |
+
|
| 11 |
+
import java.time.LocalDateTime;
|
| 12 |
+
|
| 13 |
+
@Entity
|
| 14 |
+
@Table(name = "face_id_recognition_logs")
|
| 15 |
+
@Data
|
| 16 |
+
@Builder
|
| 17 |
+
@NoArgsConstructor
|
| 18 |
+
@AllArgsConstructor
|
| 19 |
+
public class FaceIdRecognition {
|
| 20 |
+
@Id
|
| 21 |
+
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
| 22 |
+
private Long id;
|
| 23 |
+
|
| 24 |
+
@Column(name = "student_code", nullable = false, length = 50)
|
| 25 |
+
private String studentCode;
|
| 26 |
+
|
| 27 |
+
@Column(name = "confidence", nullable = false)
|
| 28 |
+
private Double confidence;
|
| 29 |
+
|
| 30 |
+
@Column(name = "camera_id", length = 50)
|
| 31 |
+
private String cameraId;
|
| 32 |
+
|
| 33 |
+
@Column(name = "image_id", length = 255)
|
| 34 |
+
private String imageId;
|
| 35 |
+
|
| 36 |
+
@Column(name = "tracking_id", length = 255, unique = true)
|
| 37 |
+
private String trackingId;
|
| 38 |
+
|
| 39 |
+
@Column(name = "liveness")
|
| 40 |
+
private Boolean liveness;
|
| 41 |
+
|
| 42 |
+
@Enumerated(EnumType.STRING)
|
| 43 |
+
@Column(name = "action", nullable = false, length = 20)
|
| 44 |
+
private FaceIdAction action;
|
| 45 |
+
|
| 46 |
+
@Column(name = "message", length = 500)
|
| 47 |
+
private String message;
|
| 48 |
+
|
| 49 |
+
@Column(name = "request_payload", columnDefinition = "JSON")
|
| 50 |
+
private String requestPayload;
|
| 51 |
+
|
| 52 |
+
@Column(name = "response_payload", columnDefinition = "JSON")
|
| 53 |
+
private String responsePayload;
|
| 54 |
+
|
| 55 |
+
@Column(name = "captured_at")
|
| 56 |
+
private LocalDateTime capturedAt;
|
| 57 |
+
|
| 58 |
+
@CreationTimestamp
|
| 59 |
+
@Column(name = "created_at", updatable = false)
|
| 60 |
+
private LocalDateTime createdAt;
|
| 61 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/entity/FaceProfile.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.faceid.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import org.hibernate.annotations.CreationTimestamp;
|
| 6 |
import org.hibernate.annotations.UpdateTimestamp;
|
|
@@ -56,9 +57,9 @@ public class FaceProfile {
|
|
| 56 |
|
| 57 |
@CreationTimestamp
|
| 58 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 59 |
-
private
|
| 60 |
|
| 61 |
@UpdateTimestamp
|
| 62 |
@Column(name = "updated_at", nullable = false)
|
| 63 |
-
private
|
| 64 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.faceid.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
import org.hibernate.annotations.UpdateTimestamp;
|
|
|
|
| 57 |
|
| 58 |
@CreationTimestamp
|
| 59 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 60 |
+
private LocalDateTime createdAt;
|
| 61 |
|
| 62 |
@UpdateTimestamp
|
| 63 |
@Column(name = "updated_at", nullable = false)
|
| 64 |
+
private LocalDateTime updatedAt;
|
| 65 |
}
|
src/main/java/com/attendenceSystem/module/faceid/entity/FaceSample.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.faceid.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import org.hibernate.annotations.CreationTimestamp;
|
| 6 |
|
|
@@ -45,5 +46,5 @@ public class FaceSample {
|
|
| 45 |
|
| 46 |
@CreationTimestamp
|
| 47 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 48 |
-
private
|
| 49 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.faceid.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
|
|
|
|
| 46 |
|
| 47 |
@CreationTimestamp
|
| 48 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 49 |
+
private LocalDateTime createdAt;
|
| 50 |
}
|
src/main/java/com/attendenceSystem/module/faceid/filter/FaceIdApiKeyFilter.java
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.filter;
|
| 2 |
+
|
| 3 |
+
import jakarta.servlet.FilterChain;
|
| 4 |
+
import jakarta.servlet.ServletException;
|
| 5 |
+
import jakarta.servlet.http.HttpServletRequest;
|
| 6 |
+
import jakarta.servlet.http.HttpServletResponse;
|
| 7 |
+
import org.springframework.beans.factory.annotation.Value;
|
| 8 |
+
import org.springframework.core.Ordered;
|
| 9 |
+
import org.springframework.core.annotation.Order;
|
| 10 |
+
import org.springframework.stereotype.Component;
|
| 11 |
+
import org.springframework.web.filter.OncePerRequestFilter;
|
| 12 |
+
|
| 13 |
+
import java.io.IOException;
|
| 14 |
+
|
| 15 |
+
@Component
|
| 16 |
+
@Order(Ordered.HIGHEST_PRECEDENCE)
|
| 17 |
+
public class FaceIdApiKeyFilter extends OncePerRequestFilter {
|
| 18 |
+
|
| 19 |
+
@Value("${face-id.api-key}")
|
| 20 |
+
private String apiKey;
|
| 21 |
+
|
| 22 |
+
private static final String API_KEY_HEADER = "X-API-KEY";
|
| 23 |
+
private static final String FACE_ID_PATH_PREFIX = "/api/face-id";
|
| 24 |
+
|
| 25 |
+
@Override
|
| 26 |
+
protected boolean shouldNotFilter(HttpServletRequest request) {
|
| 27 |
+
String path = request.getRequestURI();
|
| 28 |
+
return !path.startsWith(FACE_ID_PATH_PREFIX);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
@Override
|
| 32 |
+
protected void doFilterInternal(
|
| 33 |
+
HttpServletRequest request,
|
| 34 |
+
HttpServletResponse response,
|
| 35 |
+
FilterChain filterChain
|
| 36 |
+
) throws ServletException, IOException {
|
| 37 |
+
|
| 38 |
+
// String requestApiKey = extractApiKey(request);
|
| 39 |
+
|
| 40 |
+
// if (apiKey == null || apiKey.isBlank() || !apiKey.equals(requestApiKey)) {
|
| 41 |
+
// throw new RuntimeException("Invalid or missing API key");
|
| 42 |
+
// }
|
| 43 |
+
|
| 44 |
+
filterChain.doFilter(request, response);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
private String extractApiKey(HttpServletRequest request) {
|
| 48 |
+
String apiKeyHeader = request.getHeader(API_KEY_HEADER);
|
| 49 |
+
if (apiKeyHeader != null && apiKeyHeader.startsWith("Bearer ")) {
|
| 50 |
+
return apiKeyHeader.substring(7);
|
| 51 |
+
}
|
| 52 |
+
return apiKeyHeader;
|
| 53 |
+
}
|
| 54 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/repository/FaceIdRecognitionRepository.java
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.repository;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.faceid.entity.FaceIdRecognition;
|
| 4 |
+
import org.springframework.data.jpa.repository.JpaRepository;
|
| 5 |
+
import org.springframework.stereotype.Repository;
|
| 6 |
+
|
| 7 |
+
@Repository
|
| 8 |
+
public interface FaceIdRecognitionRepository extends JpaRepository<FaceIdRecognition, Long> {
|
| 9 |
+
boolean existsByTrackingId(String trackingId);
|
| 10 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/service/FaceIdAttendanceService.java
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.service;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceRequest;
|
| 4 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceResponse;
|
| 5 |
+
|
| 6 |
+
public interface FaceIdAttendanceService {
|
| 7 |
+
FaceIdAttendanceResponse processAttendance(FaceIdAttendanceRequest request);
|
| 8 |
+
}
|
src/main/java/com/attendenceSystem/module/faceid/service/impl/FaceIdAttendanceServiceImpl.java
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.faceid.service.impl;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 4 |
+
import com.attendenceSystem.module.attendance.entity.AttendanceRecord;
|
| 5 |
+
import com.attendenceSystem.module.attendance.service.AttendanceService;
|
| 6 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAction;
|
| 7 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceRequest;
|
| 8 |
+
import com.attendenceSystem.module.faceid.dto.FaceIdAttendanceResponse;
|
| 9 |
+
import com.attendenceSystem.module.faceid.entity.FaceIdRecognition;
|
| 10 |
+
import com.attendenceSystem.module.faceid.repository.FaceIdRecognitionRepository;
|
| 11 |
+
import com.attendenceSystem.module.faceid.service.FaceIdAttendanceService;
|
| 12 |
+
import com.attendenceSystem.module.user.entity.User;
|
| 13 |
+
import com.attendenceSystem.module.user.repository.UserRepository;
|
| 14 |
+
import com.fasterxml.jackson.core.JsonProcessingException;
|
| 15 |
+
import com.fasterxml.jackson.databind.ObjectMapper;
|
| 16 |
+
import com.github.benmanes.caffeine.cache.Cache;
|
| 17 |
+
import com.github.benmanes.caffeine.cache.Caffeine;
|
| 18 |
+
import lombok.RequiredArgsConstructor;
|
| 19 |
+
import lombok.extern.slf4j.Slf4j;
|
| 20 |
+
import org.springframework.beans.factory.annotation.Value;
|
| 21 |
+
import org.springframework.stereotype.Service;
|
| 22 |
+
import org.springframework.transaction.annotation.Transactional;
|
| 23 |
+
import org.springframework.util.StringUtils;
|
| 24 |
+
|
| 25 |
+
import java.time.Duration;
|
| 26 |
+
import java.time.Instant;
|
| 27 |
+
import java.util.Optional;
|
| 28 |
+
|
| 29 |
+
@Service
|
| 30 |
+
@Slf4j
|
| 31 |
+
@RequiredArgsConstructor
|
| 32 |
+
public class FaceIdAttendanceServiceImpl implements FaceIdAttendanceService {
|
| 33 |
+
|
| 34 |
+
private final AttendanceService attendanceService;
|
| 35 |
+
private final UserRepository userRepository;
|
| 36 |
+
private final FaceIdRecognitionRepository faceIdRecognitionRepository;
|
| 37 |
+
|
| 38 |
+
@Value("${face-id.confidence-threshold:0.90}")
|
| 39 |
+
private double confidenceThreshold;
|
| 40 |
+
|
| 41 |
+
private final ObjectMapper objectMapper = new ObjectMapper();
|
| 42 |
+
|
| 43 |
+
private final Cache<String, FaceIdAction> processedTrackingIds = Caffeine.newBuilder()
|
| 44 |
+
.expireAfterWrite(Duration.ofMinutes(5))
|
| 45 |
+
.maximumSize(100_000)
|
| 46 |
+
.build();
|
| 47 |
+
|
| 48 |
+
@Override
|
| 49 |
+
@Transactional
|
| 50 |
+
public FaceIdAttendanceResponse processAttendance(FaceIdAttendanceRequest request) {
|
| 51 |
+
String trackingId = request.getTrackingId();
|
| 52 |
+
Instant timestamp = Instant.now();
|
| 53 |
+
|
| 54 |
+
// 1. Check idempotency (only if trackingId is present)
|
| 55 |
+
if (StringUtils.hasText(trackingId)) {
|
| 56 |
+
FaceIdAction cachedAction = processedTrackingIds.getIfPresent(trackingId);
|
| 57 |
+
if (cachedAction != null) {
|
| 58 |
+
log.info("Ignoring duplicate trackingId: {}", trackingId);
|
| 59 |
+
return buildResponse(FaceIdAction.IGNORED, "DUPLICATE", trackingId, timestamp, null);
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// 2. Check confidence threshold
|
| 64 |
+
if (request.getConfidence() < confidenceThreshold) {
|
| 65 |
+
String message = "Confidence below threshold: " + request.getConfidence();
|
| 66 |
+
log.warn(message);
|
| 67 |
+
saveRecognitionLog(request, FaceIdAction.FAILED, message, null, timestamp);
|
| 68 |
+
return buildResponse(FaceIdAction.FAILED, message, trackingId, timestamp, null);
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// 3. Find user by username (studentCode)
|
| 72 |
+
User user = userRepository.findByUsername(request.getStudentCode())
|
| 73 |
+
.orElse(null);
|
| 74 |
+
|
| 75 |
+
if (user == null) {
|
| 76 |
+
String message = "User not found: " + request.getStudentCode();
|
| 77 |
+
log.warn(message);
|
| 78 |
+
saveRecognitionLog(request, FaceIdAction.FAILED, message, null, timestamp);
|
| 79 |
+
return buildResponse(FaceIdAction.FAILED, message, trackingId, timestamp, null);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// 4. Determine action using AttendanceService state query
|
| 83 |
+
FaceIdAction action;
|
| 84 |
+
AttendanceResponse attendanceResponse = null;
|
| 85 |
+
String message = "Success";
|
| 86 |
+
|
| 87 |
+
try {
|
| 88 |
+
Optional<AttendanceRecord> todayRecord = attendanceService.getTodayAttendanceRecord(user);
|
| 89 |
+
|
| 90 |
+
if (todayRecord.isEmpty()) {
|
| 91 |
+
// Not checked in yet -> CHECKIN
|
| 92 |
+
attendanceResponse = attendanceService.checkIn(user);
|
| 93 |
+
action = FaceIdAction.CHECKIN;
|
| 94 |
+
message = "Checked in successfully";
|
| 95 |
+
} else if (todayRecord.get().getCheckOutTime() == null) {
|
| 96 |
+
// Checked in, not checked out -> CHECKOUT
|
| 97 |
+
attendanceResponse = attendanceService.checkOut(user);
|
| 98 |
+
action = FaceIdAction.CHECKOUT;
|
| 99 |
+
message = "Checked out successfully";
|
| 100 |
+
} else {
|
| 101 |
+
// Already checked out
|
| 102 |
+
action = FaceIdAction.IGNORED;
|
| 103 |
+
message = "Already checked out today";
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
log.info("Face ID attendance processed: student={}, action={}", request.getStudentCode(), action);
|
| 107 |
+
|
| 108 |
+
} catch (Exception e) {
|
| 109 |
+
// Catch specific attendance exceptions
|
| 110 |
+
String errorMessage = e.getMessage();
|
| 111 |
+
log.error("Attendance processing error for student {}: {}", request.getStudentCode(), errorMessage, e);
|
| 112 |
+
saveRecognitionLog(request, FaceIdAction.FAILED, errorMessage, null, timestamp);
|
| 113 |
+
return buildResponse(FaceIdAction.FAILED, errorMessage, trackingId, timestamp, null);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// 5. Save recognition log
|
| 117 |
+
saveRecognitionLog(request, action, message, attendanceResponse, timestamp);
|
| 118 |
+
|
| 119 |
+
// 6. Cache trackingId for idempotency
|
| 120 |
+
if (org.springframework.util.StringUtils.hasText(trackingId)) {
|
| 121 |
+
processedTrackingIds.put(trackingId, action);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// 7. Build and return response
|
| 125 |
+
return buildResponse(true, action, message, trackingId, timestamp, attendanceResponse);
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
private void saveRecognitionLog(
|
| 129 |
+
FaceIdAttendanceRequest request,
|
| 130 |
+
FaceIdAction action,
|
| 131 |
+
String message,
|
| 132 |
+
AttendanceResponse attendance,
|
| 133 |
+
Instant timestamp) {
|
| 134 |
+
|
| 135 |
+
try {
|
| 136 |
+
String requestPayload = objectMapper.writeValueAsString(request);
|
| 137 |
+
String responsePayload = objectMapper.writeValueAsString(
|
| 138 |
+
FaceIdAttendanceResponse.builder()
|
| 139 |
+
.action(action)
|
| 140 |
+
.message(message)
|
| 141 |
+
.timestamp(timestamp)
|
| 142 |
+
.build()
|
| 143 |
+
);
|
| 144 |
+
|
| 145 |
+
FaceIdRecognition log = FaceIdRecognition.builder()
|
| 146 |
+
.studentCode(request.getStudentCode())
|
| 147 |
+
.confidence(request.getConfidence())
|
| 148 |
+
.cameraId(request.getCameraId())
|
| 149 |
+
.imageId(request.getImageId())
|
| 150 |
+
.trackingId(request.getTrackingId())
|
| 151 |
+
.liveness(request.getLiveness())
|
| 152 |
+
.action(action)
|
| 153 |
+
.message(message)
|
| 154 |
+
.requestPayload(requestPayload)
|
| 155 |
+
.responsePayload(responsePayload)
|
| 156 |
+
.capturedAt(request.getCapturedAt())
|
| 157 |
+
.build();
|
| 158 |
+
|
| 159 |
+
faceIdRecognitionRepository.save(log);
|
| 160 |
+
|
| 161 |
+
} catch (JsonProcessingException e) {
|
| 162 |
+
log.error("Failed to serialize request/response for log", e);
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
private FaceIdAttendanceResponse buildResponse(
|
| 167 |
+
FaceIdAction action,
|
| 168 |
+
String message,
|
| 169 |
+
String trackingId,
|
| 170 |
+
Instant timestamp,
|
| 171 |
+
AttendanceResponse attendance) {
|
| 172 |
+
|
| 173 |
+
return FaceIdAttendanceResponse.builder()
|
| 174 |
+
.success(action != FaceIdAction.FAILED)
|
| 175 |
+
.action(action)
|
| 176 |
+
.message(message)
|
| 177 |
+
.trackingId(trackingId)
|
| 178 |
+
.timestamp(timestamp)
|
| 179 |
+
.attendance(attendance)
|
| 180 |
+
.build();
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
private FaceIdAttendanceResponse buildResponse(
|
| 184 |
+
boolean success,
|
| 185 |
+
FaceIdAction action,
|
| 186 |
+
String message,
|
| 187 |
+
String trackingId,
|
| 188 |
+
Instant timestamp,
|
| 189 |
+
AttendanceResponse attendance) {
|
| 190 |
+
|
| 191 |
+
return FaceIdAttendanceResponse.builder()
|
| 192 |
+
.success(success)
|
| 193 |
+
.action(action)
|
| 194 |
+
.message(message)
|
| 195 |
+
.trackingId(trackingId)
|
| 196 |
+
.timestamp(timestamp)
|
| 197 |
+
.attendance(attendance)
|
| 198 |
+
.build();
|
| 199 |
+
}
|
| 200 |
+
}
|
src/main/java/com/attendenceSystem/module/log/dto/response/AuditLogResponse.java
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.log.dto.response;
|
| 2 |
|
|
|
|
|
|
|
| 3 |
import com.attendenceSystem.module.log.entity.enums.LogAction;
|
| 4 |
import com.attendenceSystem.module.log.entity.enums.LogEntityType;
|
| 5 |
|
|
@@ -14,5 +16,5 @@ public record AuditLogResponse(
|
|
| 14 |
Long entityId,
|
| 15 |
LogAction action,
|
| 16 |
String description,
|
| 17 |
-
|
| 18 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.log.dto.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
+
|
| 5 |
import com.attendenceSystem.module.log.entity.enums.LogAction;
|
| 6 |
import com.attendenceSystem.module.log.entity.enums.LogEntityType;
|
| 7 |
|
|
|
|
| 16 |
Long entityId,
|
| 17 |
LogAction action,
|
| 18 |
String description,
|
| 19 |
+
LocalDateTime createdAt) {
|
| 20 |
}
|
src/main/java/com/attendenceSystem/module/log/entity/AuditLog.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.log.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import org.hibernate.annotations.CreationTimestamp;
|
| 6 |
|
|
@@ -50,7 +51,7 @@ public class AuditLog {
|
|
| 50 |
|
| 51 |
@CreationTimestamp
|
| 52 |
@Column(name = "created_at", updatable = false)
|
| 53 |
-
private
|
| 54 |
|
| 55 |
@ManyToOne(fetch = FetchType.LAZY)
|
| 56 |
@JoinColumn(name = "user_id", updatable = false)
|
|
|
|
| 1 |
package com.attendenceSystem.module.log.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
|
|
|
|
| 51 |
|
| 52 |
@CreationTimestamp
|
| 53 |
@Column(name = "created_at", updatable = false)
|
| 54 |
+
private LocalDateTime createdAt;
|
| 55 |
|
| 56 |
@ManyToOne(fetch = FetchType.LAZY)
|
| 57 |
@JoinColumn(name = "user_id", updatable = false)
|
src/main/java/com/attendenceSystem/module/log/mapper/response/AuditLogResponseMapper.java
CHANGED
|
@@ -4,7 +4,6 @@ import org.springframework.stereotype.Component;
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.log.dto.response.AuditLogResponse;
|
| 6 |
import com.attendenceSystem.module.log.entity.AuditLog;
|
| 7 |
-
import com.attendenceSystem.util.TimeUtil;
|
| 8 |
|
| 9 |
@Component
|
| 10 |
public class AuditLogResponseMapper {
|
|
@@ -17,7 +16,7 @@ public class AuditLogResponseMapper {
|
|
| 17 |
.entityId(auditLog.getEntityId())
|
| 18 |
.action(auditLog.getAction())
|
| 19 |
.description(auditLog.getDescription())
|
| 20 |
-
.createdAt(
|
| 21 |
.build();
|
| 22 |
}
|
| 23 |
}
|
|
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.log.dto.response.AuditLogResponse;
|
| 6 |
import com.attendenceSystem.module.log.entity.AuditLog;
|
|
|
|
| 7 |
|
| 8 |
@Component
|
| 9 |
public class AuditLogResponseMapper {
|
|
|
|
| 16 |
.entityId(auditLog.getEntityId())
|
| 17 |
.action(auditLog.getAction())
|
| 18 |
.description(auditLog.getDescription())
|
| 19 |
+
.createdAt(auditLog.getCreatedAt())
|
| 20 |
.build();
|
| 21 |
}
|
| 22 |
}
|
src/main/java/com/attendenceSystem/module/otp/dto/response/OtpResponse.java
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.otp.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.otp.entity.enums.OtpPurpose;
|
| 6 |
|
|
@@ -8,8 +8,8 @@ public record OtpResponse(
|
|
| 8 |
Long id,
|
| 9 |
String destination,
|
| 10 |
OtpPurpose purpose,
|
| 11 |
-
|
| 12 |
-
|
| 13 |
boolean used
|
| 14 |
) {
|
| 15 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.otp.dto.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.otp.entity.enums.OtpPurpose;
|
| 6 |
|
|
|
|
| 8 |
Long id,
|
| 9 |
String destination,
|
| 10 |
OtpPurpose purpose,
|
| 11 |
+
LocalDateTime expiredAt,
|
| 12 |
+
LocalDateTime createdAt,
|
| 13 |
boolean used
|
| 14 |
) {
|
| 15 |
}
|
src/main/java/com/attendenceSystem/module/otp/entity/Otp.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.otp.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.otp.entity.enums.OtpPurpose;
|
| 6 |
|
|
@@ -35,13 +36,13 @@ public class Otp {
|
|
| 35 |
@Column(name = "purpose", nullable = false)
|
| 36 |
private OtpPurpose purpose;
|
| 37 |
@Column(name = "expired_at", nullable = false)
|
| 38 |
-
private
|
| 39 |
@Column(name = "created_at", nullable = false)
|
| 40 |
-
private
|
| 41 |
@Column(name = "used", nullable = false)
|
| 42 |
private boolean used;
|
| 43 |
|
| 44 |
public boolean isExpired() {
|
| 45 |
-
return
|
| 46 |
}
|
| 47 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.otp.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.otp.entity.enums.OtpPurpose;
|
| 7 |
|
|
|
|
| 36 |
@Column(name = "purpose", nullable = false)
|
| 37 |
private OtpPurpose purpose;
|
| 38 |
@Column(name = "expired_at", nullable = false)
|
| 39 |
+
private LocalDateTime expiredAt;
|
| 40 |
@Column(name = "created_at", nullable = false)
|
| 41 |
+
private LocalDateTime createdAt;
|
| 42 |
@Column(name = "used", nullable = false)
|
| 43 |
private boolean used;
|
| 44 |
|
| 45 |
public boolean isExpired() {
|
| 46 |
+
return LocalDateTime.now().isAfter(expiredAt);
|
| 47 |
}
|
| 48 |
}
|
src/main/java/com/attendenceSystem/module/otp/mapper/request/SendOtpRequestMapper.java
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.otp.mapper.request;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
-
import java.time.
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.otp.dto.request.SendOtpRequest;
|
| 7 |
import com.attendenceSystem.module.otp.entity.Otp;
|
|
@@ -15,12 +15,12 @@ public class SendOtpRequestMapper {
|
|
| 15 |
private static final int OTP_EXPIRY_MINUTES = 5;
|
| 16 |
|
| 17 |
public static Otp toEntity(SendOtpRequest request, String code) {
|
| 18 |
-
|
| 19 |
return Otp.builder()
|
| 20 |
.destination(request.getDestination())
|
| 21 |
.code(code)
|
| 22 |
.purpose(request.getPurpose())
|
| 23 |
-
.expiredAt(now.
|
| 24 |
.createdAt(now)
|
| 25 |
.used(false)
|
| 26 |
.build();
|
|
|
|
| 1 |
package com.attendenceSystem.module.otp.mapper.request;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.otp.dto.request.SendOtpRequest;
|
| 7 |
import com.attendenceSystem.module.otp.entity.Otp;
|
|
|
|
| 15 |
private static final int OTP_EXPIRY_MINUTES = 5;
|
| 16 |
|
| 17 |
public static Otp toEntity(SendOtpRequest request, String code) {
|
| 18 |
+
LocalDateTime now = LocalDateTime.now();
|
| 19 |
return Otp.builder()
|
| 20 |
.destination(request.getDestination())
|
| 21 |
.code(code)
|
| 22 |
.purpose(request.getPurpose())
|
| 23 |
+
.expiredAt(now.plusMinutes(OTP_EXPIRY_MINUTES))
|
| 24 |
.createdAt(now)
|
| 25 |
.used(false)
|
| 26 |
.build();
|
src/main/java/com/attendenceSystem/module/report/controller/ReportController.java
CHANGED
|
@@ -52,6 +52,7 @@ public class ReportController {
|
|
| 52 |
request.setContent(content);
|
| 53 |
request.setAttachmentUrl(link);
|
| 54 |
request.setSharedUserIds(sharedUserIds);
|
|
|
|
| 55 |
|
| 56 |
reportService.createReport(request);
|
| 57 |
|
|
|
|
| 52 |
request.setContent(content);
|
| 53 |
request.setAttachmentUrl(link);
|
| 54 |
request.setSharedUserIds(sharedUserIds);
|
| 55 |
+
request.setFiles(files);
|
| 56 |
|
| 57 |
reportService.createReport(request);
|
| 58 |
|
src/main/java/com/attendenceSystem/module/report/dto/request/CreateReportRequest.java
CHANGED
|
@@ -6,6 +6,7 @@ import lombok.AllArgsConstructor;
|
|
| 6 |
import lombok.Getter;
|
| 7 |
import lombok.NoArgsConstructor;
|
| 8 |
import lombok.Setter;
|
|
|
|
| 9 |
|
| 10 |
@Getter
|
| 11 |
@Setter
|
|
@@ -24,4 +25,6 @@ public class CreateReportRequest {
|
|
| 24 |
private String attachmentUrl;
|
| 25 |
|
| 26 |
private Long[] sharedUserIds;
|
|
|
|
|
|
|
| 27 |
}
|
|
|
|
| 6 |
import lombok.Getter;
|
| 7 |
import lombok.NoArgsConstructor;
|
| 8 |
import lombok.Setter;
|
| 9 |
+
import org.springframework.web.multipart.MultipartFile;
|
| 10 |
|
| 11 |
@Getter
|
| 12 |
@Setter
|
|
|
|
| 25 |
private String attachmentUrl;
|
| 26 |
|
| 27 |
private Long[] sharedUserIds;
|
| 28 |
+
|
| 29 |
+
private MultipartFile[] files;
|
| 30 |
}
|
src/main/java/com/attendenceSystem/module/report/dto/response/ReportDetailResponse.java
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.report.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 6 |
|
|
@@ -9,9 +9,10 @@ public record ReportDetailResponse(
|
|
| 9 |
String title,
|
| 10 |
String content,
|
| 11 |
String attachmentUrl,
|
|
|
|
| 12 |
String employeeName,
|
| 13 |
ReportStatus status,
|
| 14 |
String rejectReason,
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.report.dto.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 6 |
|
|
|
|
| 9 |
String title,
|
| 10 |
String content,
|
| 11 |
String attachmentUrl,
|
| 12 |
+
String attachmentFiles,
|
| 13 |
String employeeName,
|
| 14 |
ReportStatus status,
|
| 15 |
String rejectReason,
|
| 16 |
+
LocalDateTime createdAt,
|
| 17 |
+
LocalDateTime reviewedAt) {
|
| 18 |
+
}
|
src/main/java/com/attendenceSystem/module/report/dto/response/ReportResponse.java
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.report.dto.response;
|
| 2 |
|
| 3 |
-
import java.time.
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 6 |
|
|
@@ -10,5 +10,7 @@ public record ReportResponse(
|
|
| 10 |
String content,
|
| 11 |
String employeeName,
|
| 12 |
ReportStatus status,
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
| 1 |
package com.attendenceSystem.module.report.dto.response;
|
| 2 |
|
| 3 |
+
import java.time.LocalDateTime;
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 6 |
|
|
|
|
| 10 |
String content,
|
| 11 |
String employeeName,
|
| 12 |
ReportStatus status,
|
| 13 |
+
LocalDateTime createdAt,
|
| 14 |
+
String attachmentUrl,
|
| 15 |
+
String attachmentFiles) {
|
| 16 |
+
}
|
src/main/java/com/attendenceSystem/module/report/entity/Report.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.report.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 6 |
import com.attendenceSystem.module.user.entity.User;
|
|
@@ -12,6 +13,7 @@ import jakarta.persistence.GeneratedValue;
|
|
| 12 |
import jakarta.persistence.GenerationType;
|
| 13 |
import jakarta.persistence.Id;
|
| 14 |
import jakarta.persistence.JoinColumn;
|
|
|
|
| 15 |
import jakarta.persistence.ManyToOne;
|
| 16 |
import jakarta.persistence.Table;
|
| 17 |
|
|
@@ -47,6 +49,10 @@ public class Report {
|
|
| 47 |
@Column(name = "attachment_url")
|
| 48 |
private String attachmentUrl;
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
@Column(name = "status", nullable = false)
|
| 51 |
private ReportStatus status;
|
| 52 |
|
|
@@ -54,8 +60,8 @@ public class Report {
|
|
| 54 |
private String rejectReason;
|
| 55 |
|
| 56 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 57 |
-
private
|
| 58 |
|
| 59 |
@Column(name = "reviewed_at")
|
| 60 |
-
private
|
| 61 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.report.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import com.attendenceSystem.module.report.entity.enums.ReportStatus;
|
| 7 |
import com.attendenceSystem.module.user.entity.User;
|
|
|
|
| 13 |
import jakarta.persistence.GenerationType;
|
| 14 |
import jakarta.persistence.Id;
|
| 15 |
import jakarta.persistence.JoinColumn;
|
| 16 |
+
import jakarta.persistence.Lob;
|
| 17 |
import jakarta.persistence.ManyToOne;
|
| 18 |
import jakarta.persistence.Table;
|
| 19 |
|
|
|
|
| 49 |
@Column(name = "attachment_url")
|
| 50 |
private String attachmentUrl;
|
| 51 |
|
| 52 |
+
@Lob
|
| 53 |
+
@Column(name = "attachment_files")
|
| 54 |
+
private String attachmentFiles;
|
| 55 |
+
|
| 56 |
@Column(name = "status", nullable = false)
|
| 57 |
private ReportStatus status;
|
| 58 |
|
|
|
|
| 60 |
private String rejectReason;
|
| 61 |
|
| 62 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 63 |
+
private LocalDateTime createdAt;
|
| 64 |
|
| 65 |
@Column(name = "reviewed_at")
|
| 66 |
+
private LocalDateTime reviewedAt;
|
| 67 |
}
|
src/main/java/com/attendenceSystem/module/report/mapper/response/ReportDetailResponseMapper.java
CHANGED
|
@@ -15,6 +15,7 @@ public class ReportDetailResponseMapper {
|
|
| 15 |
report.getTitle(),
|
| 16 |
report.getContent(),
|
| 17 |
report.getAttachmentUrl(),
|
|
|
|
| 18 |
report.getEmployee().getFullName(),
|
| 19 |
report.getStatus(),
|
| 20 |
report.getRejectReason(),
|
|
|
|
| 15 |
report.getTitle(),
|
| 16 |
report.getContent(),
|
| 17 |
report.getAttachmentUrl(),
|
| 18 |
+
report.getAttachmentFiles(),
|
| 19 |
report.getEmployee().getFullName(),
|
| 20 |
report.getStatus(),
|
| 21 |
report.getRejectReason(),
|
src/main/java/com/attendenceSystem/module/report/mapper/response/ReportResponseMapper.java
CHANGED
|
@@ -16,7 +16,9 @@ public class ReportResponseMapper {
|
|
| 16 |
report.getContent(),
|
| 17 |
report.getEmployee().getFullName(),
|
| 18 |
report.getStatus(),
|
| 19 |
-
report.getCreatedAt()
|
|
|
|
|
|
|
| 20 |
);
|
| 21 |
}
|
| 22 |
}
|
|
|
|
| 16 |
report.getContent(),
|
| 17 |
report.getEmployee().getFullName(),
|
| 18 |
report.getStatus(),
|
| 19 |
+
report.getCreatedAt(),
|
| 20 |
+
report.getAttachmentUrl(),
|
| 21 |
+
report.getAttachmentFiles()
|
| 22 |
);
|
| 23 |
}
|
| 24 |
}
|
src/main/java/com/attendenceSystem/module/report/service/impl/ReportServiceImpl.java
CHANGED
|
@@ -1,8 +1,15 @@
|
|
| 1 |
package com.attendenceSystem.module.report.service.impl;
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
import java.util.ArrayList;
|
| 5 |
import java.util.List;
|
|
|
|
| 6 |
|
| 7 |
import org.springframework.data.domain.Page;
|
| 8 |
import org.springframework.data.domain.Pageable;
|
|
@@ -24,24 +31,44 @@ import com.attendenceSystem.module.report.service.ReportService;
|
|
| 24 |
import com.attendenceSystem.module.user.entity.User;
|
| 25 |
import com.attendenceSystem.module.user.repository.UserRepository;
|
| 26 |
import com.attendenceSystem.util.SecurityUtil;
|
| 27 |
-
|
| 28 |
import lombok.RequiredArgsConstructor;
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
@Service
|
| 31 |
@RequiredArgsConstructor
|
|
|
|
| 32 |
public class ReportServiceImpl implements ReportService {
|
| 33 |
|
| 34 |
private final ReportRepository reportRepository;
|
| 35 |
private final ReportShareRepository reportShareRepository;
|
| 36 |
private final UserRepository userRepository;
|
| 37 |
|
|
|
|
|
|
|
|
|
|
| 38 |
@Transactional
|
| 39 |
@Override
|
| 40 |
public ReportResponse createReport(final CreateReportRequest request) {
|
| 41 |
User employee = findCurrentUser();
|
| 42 |
|
| 43 |
Report report = CreateReportRequestMapper.toEntity(request, employee);
|
| 44 |
-
report.setCreatedAt(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
Report savedReport = reportRepository.save(report);
|
| 47 |
|
|
@@ -63,6 +90,32 @@ public class ReportServiceImpl implements ReportService {
|
|
| 63 |
return ReportResponseMapper.fromEntity(savedReport);
|
| 64 |
}
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
@Override
|
| 67 |
public Page<ReportResponse> getAllReports(final Pageable pageable) {
|
| 68 |
return reportRepository.findAllByOrderByCreatedAtDesc(pageable)
|
|
|
|
| 1 |
package com.attendenceSystem.module.report.service.impl;
|
| 2 |
|
| 3 |
+
import java.io.File;
|
| 4 |
+
import java.io.IOException;
|
| 5 |
+
import java.nio.file.Files;
|
| 6 |
+
import java.nio.file.Path;
|
| 7 |
+
import java.nio.file.Paths;
|
| 8 |
import java.time.Instant;
|
| 9 |
+
import java.time.LocalDateTime;
|
| 10 |
import java.util.ArrayList;
|
| 11 |
import java.util.List;
|
| 12 |
+
import java.util.UUID;
|
| 13 |
|
| 14 |
import org.springframework.data.domain.Page;
|
| 15 |
import org.springframework.data.domain.Pageable;
|
|
|
|
| 31 |
import com.attendenceSystem.module.user.entity.User;
|
| 32 |
import com.attendenceSystem.module.user.repository.UserRepository;
|
| 33 |
import com.attendenceSystem.util.SecurityUtil;
|
|
|
|
| 34 |
import lombok.RequiredArgsConstructor;
|
| 35 |
+
import lombok.extern.slf4j.Slf4j;
|
| 36 |
+
import org.springframework.beans.factory.annotation.Value;
|
| 37 |
+
import org.springframework.web.multipart.MultipartFile;
|
| 38 |
|
| 39 |
@Service
|
| 40 |
@RequiredArgsConstructor
|
| 41 |
+
@Slf4j
|
| 42 |
public class ReportServiceImpl implements ReportService {
|
| 43 |
|
| 44 |
private final ReportRepository reportRepository;
|
| 45 |
private final ReportShareRepository reportShareRepository;
|
| 46 |
private final UserRepository userRepository;
|
| 47 |
|
| 48 |
+
@Value("${app.storage.upload-dir:uploads}")
|
| 49 |
+
private String storageBaseDir;
|
| 50 |
+
|
| 51 |
@Transactional
|
| 52 |
@Override
|
| 53 |
public ReportResponse createReport(final CreateReportRequest request) {
|
| 54 |
User employee = findCurrentUser();
|
| 55 |
|
| 56 |
Report report = CreateReportRequestMapper.toEntity(request, employee);
|
| 57 |
+
report.setCreatedAt(LocalDateTime.now());
|
| 58 |
+
|
| 59 |
+
// Xử lý upload files
|
| 60 |
+
if (request.getFiles() != null && request.getFiles().length > 0) {
|
| 61 |
+
List<String> filePaths = new ArrayList<>();
|
| 62 |
+
for (MultipartFile file : request.getFiles()) {
|
| 63 |
+
if (!file.isEmpty()) {
|
| 64 |
+
String filePath = saveFile(file);
|
| 65 |
+
filePaths.add(filePath);
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
if (!filePaths.isEmpty()) {
|
| 69 |
+
report.setAttachmentFiles(String.join(",", filePaths));
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
|
| 73 |
Report savedReport = reportRepository.save(report);
|
| 74 |
|
|
|
|
| 90 |
return ReportResponseMapper.fromEntity(savedReport);
|
| 91 |
}
|
| 92 |
|
| 93 |
+
private String saveFile(MultipartFile file) {
|
| 94 |
+
try {
|
| 95 |
+
// Tạo thư mục nếu chưa tồn tại
|
| 96 |
+
String reportUploadDir = storageBaseDir + "/reports";
|
| 97 |
+
Path uploadPath = Paths.get(reportUploadDir);
|
| 98 |
+
if (!Files.exists(uploadPath)) {
|
| 99 |
+
Files.createDirectories(uploadPath);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// Tạo tên file unique
|
| 103 |
+
String originalFilename = file.getOriginalFilename();
|
| 104 |
+
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
| 105 |
+
String uniqueFilename = UUID.randomUUID().toString() + extension;
|
| 106 |
+
|
| 107 |
+
// Lưu file
|
| 108 |
+
Path filePath = uploadPath.resolve(uniqueFilename);
|
| 109 |
+
file.transferTo(filePath.toFile());
|
| 110 |
+
|
| 111 |
+
// Trả về relative path
|
| 112 |
+
return reportUploadDir + "/" + uniqueFilename;
|
| 113 |
+
} catch (IOException e) {
|
| 114 |
+
log.error("Failed to save file", e);
|
| 115 |
+
throw new RuntimeException("Không thể lưu file: " + e.getMessage());
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
@Override
|
| 120 |
public Page<ReportResponse> getAllReports(final Pageable pageable) {
|
| 121 |
return reportRepository.findAllByOrderByCreatedAtDesc(pageable)
|
src/main/java/com/attendenceSystem/module/user/controller/AccountController.java
CHANGED
|
@@ -5,8 +5,6 @@ import org.springframework.data.web.PageableDefault;
|
|
| 5 |
import org.springframework.stereotype.Controller;
|
| 6 |
import org.springframework.ui.Model;
|
| 7 |
import org.springframework.web.bind.annotation.PathVariable;
|
| 8 |
-
import org.springframework.web.bind.annotation.RequestMapping;
|
| 9 |
-
|
| 10 |
import com.attendenceSystem.constant.Routes;
|
| 11 |
import com.attendenceSystem.constant.Views;
|
| 12 |
import com.attendenceSystem.module.user.dto.response.UserResponse;
|
|
|
|
| 5 |
import org.springframework.stereotype.Controller;
|
| 6 |
import org.springframework.ui.Model;
|
| 7 |
import org.springframework.web.bind.annotation.PathVariable;
|
|
|
|
|
|
|
| 8 |
import com.attendenceSystem.constant.Routes;
|
| 9 |
import com.attendenceSystem.constant.Views;
|
| 10 |
import com.attendenceSystem.module.user.dto.response.UserResponse;
|
src/main/java/com/attendenceSystem/module/user/dto/request/UpdateUserInformationRequest.java
CHANGED
|
@@ -3,7 +3,6 @@ package com.attendenceSystem.module.user.dto.request;
|
|
| 3 |
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 4 |
|
| 5 |
import jakarta.validation.constraints.Email;
|
| 6 |
-
import jakarta.validation.constraints.NotBlank;
|
| 7 |
import jakarta.validation.constraints.Size;
|
| 8 |
import lombok.AllArgsConstructor;
|
| 9 |
import lombok.Data;
|
|
|
|
| 3 |
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 4 |
|
| 5 |
import jakarta.validation.constraints.Email;
|
|
|
|
| 6 |
import jakarta.validation.constraints.Size;
|
| 7 |
import lombok.AllArgsConstructor;
|
| 8 |
import lombok.Data;
|
src/main/java/com/attendenceSystem/module/user/entity/User.java
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
package com.attendenceSystem.module.user.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
| 4 |
|
| 5 |
import org.hibernate.annotations.CreationTimestamp;
|
| 6 |
import org.hibernate.annotations.UpdateTimestamp;
|
|
@@ -56,10 +57,10 @@ public class User {
|
|
| 56 |
private Department department;
|
| 57 |
@CreationTimestamp
|
| 58 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 59 |
-
private
|
| 60 |
@UpdateTimestamp
|
| 61 |
@Column(name = "updated_at", nullable = false)
|
| 62 |
-
private
|
| 63 |
@Column(name = "must_change_password", nullable = false)
|
| 64 |
@Builder.Default
|
| 65 |
private boolean mustChangePassword = false;
|
|
|
|
| 1 |
package com.attendenceSystem.module.user.entity;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.time.LocalDateTime;
|
| 5 |
|
| 6 |
import org.hibernate.annotations.CreationTimestamp;
|
| 7 |
import org.hibernate.annotations.UpdateTimestamp;
|
|
|
|
| 57 |
private Department department;
|
| 58 |
@CreationTimestamp
|
| 59 |
@Column(name = "created_at", nullable = false, updatable = false)
|
| 60 |
+
private LocalDateTime createdAt;
|
| 61 |
@UpdateTimestamp
|
| 62 |
@Column(name = "updated_at", nullable = false)
|
| 63 |
+
private LocalDateTime updatedAt;
|
| 64 |
@Column(name = "must_change_password", nullable = false)
|
| 65 |
@Builder.Default
|
| 66 |
private boolean mustChangePassword = false;
|
src/main/java/com/attendenceSystem/module/user/service/impl/UserServiceImpl.java
CHANGED
|
@@ -1,9 +1,5 @@
|
|
| 1 |
package com.attendenceSystem.module.user.service.impl;
|
| 2 |
|
| 3 |
-
import org.springframework.data.domain.Page;
|
| 4 |
-
import org.springframework.data.domain.Pageable;
|
| 5 |
-
import org.springframework.security.core.Authentication;
|
| 6 |
-
import org.springframework.security.core.context.SecurityContextHolder;
|
| 7 |
import org.springframework.security.crypto.password.PasswordEncoder;
|
| 8 |
import org.springframework.stereotype.Service;
|
| 9 |
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
| 1 |
package com.attendenceSystem.module.user.service.impl;
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import org.springframework.security.crypto.password.PasswordEncoder;
|
| 4 |
import org.springframework.stereotype.Service;
|
| 5 |
import org.springframework.transaction.annotation.Transactional;
|
src/main/resources/application.properties
CHANGED
|
@@ -59,4 +59,8 @@ app.storage.upload-dir=uploads
|
|
| 59 |
# server.error.include-stacktrace=never
|
| 60 |
# server.error.include-exception=false
|
| 61 |
|
| 62 |
-
app.timezone=Asia/Ho_Chi_Minh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
# server.error.include-stacktrace=never
|
| 60 |
# server.error.include-exception=false
|
| 61 |
|
| 62 |
+
app.timezone=Asia/Ho_Chi_Minh
|
| 63 |
+
|
| 64 |
+
# Face ID Configuration
|
| 65 |
+
face-id.api-key=${FACE_ID_API_KEY:dev-secret-key-change-in-production}
|
| 66 |
+
face-id.confidence-threshold=0.90
|
src/main/resources/templates/cms/dashboard/dashboard-employee.html
CHANGED
|
@@ -42,31 +42,6 @@
|
|
| 42 |
|
| 43 |
<span class="employee-stat-card__trend">↗</span>
|
| 44 |
</div>
|
| 45 |
-
|
| 46 |
-
<div class="employee-stat-card report-approved">
|
| 47 |
-
<div class="employee-stat-card__icon">
|
| 48 |
-
<i class="bi bi-file-earmark-check"></i>
|
| 49 |
-
</div>
|
| 50 |
-
|
| 51 |
-
<div class="employee-stat-card__content">
|
| 52 |
-
<h3 class="employee-stat-card__number">10</h3>
|
| 53 |
-
<p class="employee-stat-card__label">Báo cáo được duyệt</p>
|
| 54 |
-
</div>
|
| 55 |
-
<span class="employee-stat-card__trend">V</span>
|
| 56 |
-
</div>
|
| 57 |
-
|
| 58 |
-
<div class="employee-stat-card report-rejected">
|
| 59 |
-
<div class="employee-stat-card__icon">
|
| 60 |
-
<i class="bi bi-file-earmark-excel-fill"></i>
|
| 61 |
-
</div>
|
| 62 |
-
|
| 63 |
-
<div class="employee-stat-card__content">
|
| 64 |
-
<h3 class="employee-stat-card__number">1</h3>
|
| 65 |
-
<p class="employee-stat-card__label">Báo cáo bị từ chối</p>
|
| 66 |
-
</div>
|
| 67 |
-
<span class="employee-stat-card__trend">X</span>
|
| 68 |
-
</div>
|
| 69 |
-
|
| 70 |
<div class="employee-stat-card attendance-rate">
|
| 71 |
<div class="employee-stat-card__icon">
|
| 72 |
<i class="bi bi-file-earmark-bar-graph-fill"></i>
|
|
@@ -241,17 +216,17 @@
|
|
| 241 |
|
| 242 |
<div class="profile-item">
|
| 243 |
<label>Email</label>
|
| 244 |
-
<span
|
| 245 |
</div>
|
| 246 |
|
| 247 |
<div class="profile-item">
|
| 248 |
<label>Số điện thoại</label>
|
| 249 |
-
<span >
|
| 250 |
</div>
|
| 251 |
|
| 252 |
<div class="profile-item">
|
| 253 |
<label>Phòng ban</label>
|
| 254 |
-
<span >
|
| 255 |
</div>
|
| 256 |
|
| 257 |
<div class="profile-item">
|
|
|
|
| 42 |
|
| 43 |
<span class="employee-stat-card__trend">↗</span>
|
| 44 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
<div class="employee-stat-card attendance-rate">
|
| 46 |
<div class="employee-stat-card__icon">
|
| 47 |
<i class="bi bi-file-earmark-bar-graph-fill"></i>
|
|
|
|
| 216 |
|
| 217 |
<div class="profile-item">
|
| 218 |
<label>Email</label>
|
| 219 |
+
<span th:text="${#authentication.principal.email}"></span>
|
| 220 |
</div>
|
| 221 |
|
| 222 |
<div class="profile-item">
|
| 223 |
<label>Số điện thoại</label>
|
| 224 |
+
<span th:text="${#authentication.principal.phone}"></span>
|
| 225 |
</div>
|
| 226 |
|
| 227 |
<div class="profile-item">
|
| 228 |
<label>Phòng ban</label>
|
| 229 |
+
<span th:text="${#authentication.principal.department}"></span>
|
| 230 |
</div>
|
| 231 |
|
| 232 |
<div class="profile-item">
|
src/main/resources/templates/cms/document/document-detail.html
CHANGED
|
@@ -21,6 +21,16 @@
|
|
| 21 |
<p id="modalContent"></p>
|
| 22 |
</div>
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
</div>
|
| 25 |
|
| 26 |
-
</div>
|
|
|
|
| 21 |
<p id="modalContent"></p>
|
| 22 |
</div>
|
| 23 |
|
| 24 |
+
<div class="report-attachments">
|
| 25 |
+
<strong>File đính kèm:</strong>
|
| 26 |
+
<div id="modalFiles"></div>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<div class="report-link">
|
| 30 |
+
<strong>Link tài liệu:</strong>
|
| 31 |
+
<div id="modalLink"></div>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
</div>
|
| 35 |
|
| 36 |
+
</div>
|
src/main/resources/templates/cms/document/document-list.html
CHANGED
|
@@ -35,8 +35,6 @@
|
|
| 35 |
<th>Ngày nộp</th>
|
| 36 |
<th>File đính kèm</th>
|
| 37 |
<th>Link tài liệu</th>
|
| 38 |
-
<th>Trạng thái</th>
|
| 39 |
-
<th>Thao tác</th>
|
| 40 |
</tr>
|
| 41 |
</thead>
|
| 42 |
|
|
@@ -108,18 +106,29 @@
|
|
| 108 |
|
| 109 |
const employeeName = report.employeeName || 'N/A';
|
| 110 |
const attachmentUrl = report.attachmentUrl || '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
const
|
|
|
|
|
|
|
| 114 |
|
| 115 |
row.innerHTML =
|
| 116 |
'<td>' + employeeName + '</td>' +
|
| 117 |
'<td>' + report.title + '</td>' +
|
| 118 |
'<td>' + formattedDate + '</td>' +
|
| 119 |
-
'<td>' +
|
| 120 |
-
'<td>' +
|
| 121 |
-
'<td><span class="' + statusClass + '">' + statusText + '</span></td>' +
|
| 122 |
-
'<td><div class="action-group"><form method="post"><button class="btn btn-success" type="submit">Duyệt</button></form><form method="post"><button class="btn btn-danger" type="submit">Từ chối</button></form></div></td>';
|
| 123 |
|
| 124 |
tableBody.appendChild(row);
|
| 125 |
});
|
|
@@ -132,13 +141,6 @@
|
|
| 132 |
tableBody.innerHTML = '<tr><td colspan="7" style="text-align:center">Lỗi khi tải danh sách báo cáo</td></tr>';
|
| 133 |
});
|
| 134 |
});
|
| 135 |
-
|
| 136 |
-
function getStatusText(status) {
|
| 137 |
-
if (status === 'PENDING') return 'Chờ duyệt';
|
| 138 |
-
if (status === 'APPROVED') return 'Đã duyệt';
|
| 139 |
-
if (status === 'REJECTED') return 'Từ chối';
|
| 140 |
-
return status;
|
| 141 |
-
}
|
| 142 |
</script>
|
| 143 |
</body>
|
| 144 |
</html>
|
|
|
|
| 35 |
<th>Ngày nộp</th>
|
| 36 |
<th>File đính kèm</th>
|
| 37 |
<th>Link tài liệu</th>
|
|
|
|
|
|
|
| 38 |
</tr>
|
| 39 |
</thead>
|
| 40 |
|
|
|
|
| 106 |
|
| 107 |
const employeeName = report.employeeName || 'N/A';
|
| 108 |
const attachmentUrl = report.attachmentUrl || '';
|
| 109 |
+
const attachmentFiles = report.attachmentFiles || '';
|
| 110 |
+
|
| 111 |
+
// Hiển thị files đính kèm
|
| 112 |
+
let filesHtml = '<span>Không có</span>';
|
| 113 |
+
if (attachmentFiles) {
|
| 114 |
+
const filePaths = attachmentFiles.split(',');
|
| 115 |
+
filesHtml = filePaths.map(path => {
|
| 116 |
+
const filename = path.split('/').pop();
|
| 117 |
+
return '<a href="' + path + '" class="file-link" download>📎 ' + filename + '</a>';
|
| 118 |
+
}).join('<br>');
|
| 119 |
+
}
|
| 120 |
|
| 121 |
+
// Hiển thị link
|
| 122 |
+
const linkHtml = attachmentUrl ?
|
| 123 |
+
'<a href="' + attachmentUrl + '" target="_blank" class="file-link">🔗 Mở link</a>' :
|
| 124 |
+
'<span>Không có</span>';
|
| 125 |
|
| 126 |
row.innerHTML =
|
| 127 |
'<td>' + employeeName + '</td>' +
|
| 128 |
'<td>' + report.title + '</td>' +
|
| 129 |
'<td>' + formattedDate + '</td>' +
|
| 130 |
+
'<td>' + filesHtml + '</td>' +
|
| 131 |
+
'<td>' + linkHtml + '</td>' +
|
|
|
|
|
|
|
| 132 |
|
| 133 |
tableBody.appendChild(row);
|
| 134 |
});
|
|
|
|
| 141 |
tableBody.innerHTML = '<tr><td colspan="7" style="text-align:center">Lỗi khi tải danh sách báo cáo</td></tr>';
|
| 142 |
});
|
| 143 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
</script>
|
| 145 |
</body>
|
| 146 |
</html>
|