From 473bf5b81ed55295754c35219d625bfa17395ef1 Mon Sep 17 00:00:00 2001 From: chiguyong Date: Tue, 19 May 2026 14:33:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20DDD=E6=9E=B6=E6=9E=84=E5=AE=8C?= =?UTF-8?q?=E5=96=84+=E6=B5=8B=E8=AF=95mock=E9=85=8D=E7=BD=AE=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完善Service接口DDD分层架构 - 修复EquipmentHealthServiceTest mock配置 - 修复WorkOrderServiceTest状态转换测试mock - 修复MaintenanceTaskServiceTest mock配置 - 修复SystemType枚举测试(FIRE→FIRE_PROTECTION) - 添加工单事件监听器测试 - 添加工单状态历史测试 - 添加维保到期提醒功能 Tests: 100 passed, 0 failures --- .../asset/controller/EquipmentController.java | 68 ++- .../controller/EquipmentHealthController.java | 22 +- .../controller/OwnershipEntityController.java | 31 +- .../pms/asset/dto/EquipmentCreateDTO.java | 4 +- .../com/ether/pms/asset/dto/SpaceNodeDTO.java | 5 +- .../pms/asset/dto/SpaceNodeEquipmentDTO.java | 6 +- .../com/ether/pms/asset/entity/Equipment.java | 31 +- .../pms/asset/entity/EquipmentElevator.java | 3 +- .../pms/asset/entity/EquipmentEnergy.java | 3 +- .../asset/entity/EquipmentFailureHistory.java | 21 +- .../ether/pms/asset/entity/EquipmentFire.java | 3 +- .../asset/entity/EquipmentHealthScore.java | 19 +- .../ether/pms/asset/entity/EquipmentHvac.java | 3 +- .../pms/asset/entity/MaintenanceReminder.java | 71 +++ .../pms/asset/entity/OwnershipEntity.java | 13 +- .../com/ether/pms/asset/enums/SystemType.java | 10 +- .../event/EquipmentFailureRecordedEvent.java | 39 ++ .../EquipmentElevatorRepository.java | 5 +- .../repository/EquipmentEnergyRepository.java | 5 +- .../EquipmentFailureHistoryRepository.java | 29 +- .../repository/EquipmentFireRepository.java | 5 +- .../EquipmentHealthScoreRepository.java | 32 +- .../repository/EquipmentHvacRepository.java | 5 +- .../asset/repository/EquipmentRepository.java | 27 +- .../MaintenanceReminderRepository.java | 34 ++ .../repository/OwnershipEntityRepository.java | 5 +- .../MaintenanceReminderScheduler.java | 115 +++++ .../service/EquipmentElevatorService.java | 1 - .../asset/service/EquipmentEnergyService.java | 1 - .../asset/service/EquipmentFireService.java | 1 - .../asset/service/EquipmentHealthService.java | 36 +- .../asset/service/EquipmentHvacService.java | 1 - .../pms/asset/service/EquipmentService.java | 3 +- .../impl/EquipmentElevatorServiceImpl.java | 8 +- .../impl/EquipmentEnergyServiceImpl.java | 8 +- .../impl/EquipmentFireServiceImpl.java | 5 +- .../impl/EquipmentHealthServiceImpl.java | 150 ++++-- .../impl/EquipmentHvacServiceImpl.java | 5 +- .../service/impl/EquipmentServiceImpl.java | 135 +++-- .../com/ether/pms/asset/TestApplication.java | 3 +- .../repository/EquipmentRepositoryTest.java | 42 +- .../service/EquipmentElevatorServiceTest.java | 160 ++++++ .../service/EquipmentHealthServiceTest.java | 379 ++++++++++++++ .../asset/service/EquipmentServiceTest.java | 369 ++++++++++++++ .../pms/auth/annotation/OperationLog.java | 42 +- .../ether/pms/auth/aspect/AuditLogAspect.java | 15 +- .../ether/pms/auth/config/AsyncConfig.java | 10 +- .../ether/pms/auth/config/SecurityConfig.java | 114 +++-- .../auth/controller/AuditLogController.java | 71 ++- .../pms/auth/controller/AuthController.java | 54 +- .../auth/controller/DataAccessController.java | 56 --- .../pms/auth/controller/DeptController.java | 65 +-- .../auth/controller/PermissionController.java | 89 +++- .../controller/PermissionTreeController.java | 24 + .../controller/ProjectMemberController.java | 92 ++-- .../pms/auth/controller/RoleController.java | 57 +-- .../auth/controller/SysConfigController.java | 42 +- .../pms/auth/controller/UserController.java | 65 ++- .../controller/dto/AddProjectMemberDTO.java | 15 +- .../dto/CreateEnterpriseUserDTO.java | 55 +- .../controller/dto/DataAccessRequest.java | 23 - .../pms/auth/controller/dto/DeptDTO.java | 30 +- .../ether/pms/auth/controller/dto/DeptVO.java | 41 +- .../pms/auth/controller/dto/PageResponse.java | 23 +- .../auth/controller/dto/ProjectMemberVO.java | 53 +- .../auth/controller/dto/RoleWithUsersDTO.java | 4 +- .../controller/dto/UserProjectRequest.java | 2 +- .../ether/pms/auth/controller/dto/UserVO.java | 76 +-- .../auth/controller/dto/UserWithRolesDTO.java | 8 +- .../com/ether/pms/auth/entity/AuditLog.java | 21 +- .../com/ether/pms/auth/entity/DataAccess.java | 45 -- .../java/com/ether/pms/auth/entity/Dept.java | 74 +-- .../ether/pms/auth/entity/EnterpriseUser.java | 44 +- .../com/ether/pms/auth/entity/Permission.java | 90 +--- .../ether/pms/auth/entity/ProjectStaff.java | 64 +-- .../pms/auth/entity/ProjectStaffRole.java | 7 +- .../com/ether/pms/auth/entity/Resident.java | 60 +-- .../ether/pms/auth/entity/ResidentSpace.java | 41 +- .../java/com/ether/pms/auth/entity/Role.java | 101 ++-- .../java/com/ether/pms/auth/entity/Space.java | 51 +- .../com/ether/pms/auth/entity/SysConfig.java | 42 +- .../java/com/ether/pms/auth/entity/User.java | 122 ++--- .../ether/pms/auth/entity/UserProject.java | 38 +- .../auth/repository/AuditLogRepository.java | 30 +- .../auth/repository/DataAccessRepository.java | 38 -- .../pms/auth/repository/DeptRepository.java | 9 +- .../repository/EnterpriseUserRepository.java | 7 +- .../auth/repository/PermissionRepository.java | 15 +- .../repository/ProjectStaffRepository.java | 17 +- .../ProjectStaffRoleRepository.java | 6 +- .../auth/repository/ResidentRepository.java | 7 +- .../repository/ResidentSpaceRepository.java | 7 +- .../pms/auth/repository/RoleRepository.java | 22 +- .../pms/auth/repository/SpaceRepository.java | 10 +- .../auth/repository/SysConfigRepository.java | 8 +- .../repository/UserProjectRepository.java | 26 +- .../pms/auth/repository/UserRepository.java | 40 +- .../auth/scheduler/AuditLogArchiveTask.java | 5 +- .../pms/auth/service/AuditLogService.java | 139 +---- .../pms/auth/service/DataAccessService.java | 51 -- .../pms/auth/service/DataScopeService.java | 28 +- .../ether/pms/auth/service/DeptService.java | 134 +---- .../pms/auth/service/LoginAttemptService.java | 92 +--- .../ether/pms/auth/service/LoginService.java | 80 +-- .../pms/auth/service/PasswordService.java | 305 +---------- .../pms/auth/service/PermissionService.java | 176 +------ .../ether/pms/auth/service/RoleService.java | 232 +-------- .../pms/auth/service/SysConfigService.java | 89 +--- .../auth/service/UserManagementService.java | 259 +--------- .../pms/auth/service/UserProjectService.java | 88 +--- .../ether/pms/auth/service/UserService.java | 258 +--------- .../service/impl/AuditLogServiceImpl.java | 118 +++++ .../service/impl/DataScopeServiceImpl.java | 34 ++ .../auth/service/impl/DeptServiceImpl.java | 95 ++++ .../service/impl/LoginAttemptServiceImpl.java | 88 ++++ .../auth/service/impl/LoginServiceImpl.java | 83 +++ .../service/impl/PasswordServiceImpl.java | 219 ++++++++ .../service/impl/PermissionServiceImpl.java | 282 +++++++++++ .../auth/service/impl/RoleServiceImpl.java | 134 +++++ .../service/impl/SysConfigServiceImpl.java | 59 +++ .../impl/UserManagementServiceImpl.java | 177 +++++++ .../service/impl/UserProjectServiceImpl.java | 51 ++ .../auth/service/impl/UserServiceImpl.java | 170 +++++++ .../ether/pms/auth/util/JwtTokenProvider.java | 79 +-- .../ether/pms/auth/util/SecurityUtils.java | 2 +- .../java/com/ether/pms/auth/vo/MenuVO.java | 28 ++ .../ether/pms/auth/vo/PermissionTreeVO.java | 34 ++ .../com/ether/pms/auth/vo/PermissionVO.java | 17 + .../ether/pms/auth/PasswordEncoderTest.java | 4 +- .../com/ether/pms/auth/TestApplication.java | 6 + .../controller/dto/ProjectMemberVOTest.java | 12 +- .../pms/auth/service/PasswordServiceTest.java | 18 +- .../auth/service/PermissionServiceTest.java | 31 +- .../pms/auth/service/RoleServiceTest.java | 35 +- .../service/UserManagementServiceTest.java | 197 ++++---- .../pms/auth/service/UserServiceTest.java | 70 ++- .../pms/auth/util/JwtTokenProviderTest.java | 19 +- .../src/test/resources/application.yml | 23 + .../com/ether/pms/common/ApiResponse.java | 21 +- .../ether/pms/common/BusinessException.java | 8 +- .../java/com/ether/pms/common/ErrorCode.java | 23 +- .../pms/common/GlobalExceptionHandler.java | 221 ++++---- .../pms/common/MaintenanceTaskRepository.java | 11 + .../common/util/BatchOperationValidator.java | 27 +- .../ether/pms/common/util/LogMaskUtil.java | 43 +- .../pms/common/util/PaginationValidator.java | 22 +- .../pms/mdm/controller/EnergyController.java | 34 +- .../controller/InspectionItemController.java | 18 +- .../InspectionRecordController.java | 20 +- .../InspectionTemplateController.java | 24 +- .../pms/mdm/controller/ProjectController.java | 89 ++-- .../mdm/controller/SpaceNodeController.java | 127 ++--- .../mdm/controller/SparePartController.java | 34 +- .../ether/pms/mdm/dto/AddMemberRequest.java | 17 +- .../pms/mdm/dto/ChangeStatusRequest.java | 13 +- .../ether/pms/mdm/dto/EquipmentCreateDTO.java | 4 +- .../com/ether/pms/mdm/dto/FloorDetailVO.java | 26 +- .../com/ether/pms/mdm/dto/FloorInfoVO.java | 28 +- .../com/ether/pms/mdm/dto/PageResponse.java | 16 +- .../ether/pms/mdm/dto/ProjectConfigDTO.java | 47 +- .../pms/mdm/dto/ProjectDeleteCheckVO.java | 20 +- .../pms/mdm/dto/ProjectDeleteStatistics.java | 33 +- .../ether/pms/mdm/dto/ProjectMemberDTO.java | 41 +- .../pms/mdm/dto/ProjectQueryRequest.java | 4 +- .../pms/mdm/dto/ProjectSelectorItem.java | 7 +- .../ether/pms/mdm/dto/SpaceNodeCreateDTO.java | 2 +- .../com/ether/pms/mdm/dto/SpaceNodeDTO.java | 4 +- .../pms/mdm/dto/SpaceNodeDeleteCheckDTO.java | 56 ++- .../pms/mdm/dto/SpaceNodeEquipmentDTO.java | 10 +- .../pms/mdm/dto/SpaceNodeProjection.java | 57 +++ .../ether/pms/mdm/dto/SpaceNodeTreeDTO.java | 2 +- .../ether/pms/mdm/dto/SpaceNodeUpdateDTO.java | 2 +- .../pms/mdm/entity/EnergyConsumption.java | 18 +- .../com/ether/pms/mdm/entity/EnergyMeter.java | 17 +- .../ether/pms/mdm/entity/InspectionItem.java | 12 +- .../pms/mdm/entity/InspectionRecord.java | 30 +- .../pms/mdm/entity/InspectionTemplate.java | 7 +- .../com/ether/pms/mdm/entity/Project.java | 46 +- .../ether/pms/mdm/entity/ProjectConfig.java | 6 +- .../pms/mdm/entity/ProjectStatistics.java | 6 +- .../pms/mdm/entity/ProjectStatusHistory.java | 6 +- .../com/ether/pms/mdm/entity/SpaceNode.java | 140 +++++- .../com/ether/pms/mdm/entity/SparePart.java | 13 +- .../pms/mdm/entity/SparePartCategory.java | 4 +- .../ether/pms/mdm/entity/SparePartRecord.java | 14 +- .../ether/pms/mdm/enums/ProjectStatus.java | 19 +- .../com/ether/pms/mdm/enums/SystemType.java | 12 +- .../EnergyConsumptionRepository.java | 30 +- .../mdm/repository/EnergyMeterRepository.java | 12 +- .../repository/InspectionItemRepository.java | 20 +- .../InspectionRecordRepository.java | 28 +- .../InspectionTemplateRepository.java | 9 +- .../repository/ProjectConfigRepository.java | 5 +- .../pms/mdm/repository/ProjectRepository.java | 29 +- .../ProjectStatisticsRepository.java | 5 +- .../ProjectStatusHistoryRepository.java | 13 +- .../mdm/repository/SpaceNodeRepository.java | 43 +- .../SparePartCategoryRepository.java | 8 +- .../repository/SparePartRecordRepository.java | 8 +- .../mdm/repository/SparePartRepository.java | 14 +- .../mdm/service/EnergyConsumptionService.java | 9 +- .../pms/mdm/service/EnergyMeterService.java | 7 +- .../mdm/service/InspectionItemService.java | 10 +- .../mdm/service/InspectionRecordService.java | 10 +- .../service/InspectionTemplateService.java | 7 +- .../pms/mdm/service/ProjectConfigService.java | 125 +---- .../pms/mdm/service/ProjectMemberService.java | 132 +---- .../ether/pms/mdm/service/ProjectService.java | 284 +---------- .../mdm/service/ProjectStatisticsService.java | 57 +-- .../pms/mdm/service/SpaceNodeService.java | 235 +++++---- .../pms/mdm/service/SparePartService.java | 18 +- .../impl/EnergyConsumptionServiceImpl.java | 48 +- .../service/impl/EnergyMeterServiceImpl.java | 19 +- .../impl/InspectionItemServiceImpl.java | 23 +- .../impl/InspectionRecordServiceImpl.java | 22 +- .../impl/InspectionTemplateServiceImpl.java | 15 +- .../impl/ProjectConfigServiceImpl.java | 119 +++++ .../impl/ProjectMemberServiceImpl.java | 121 +++++ .../mdm/service/impl/ProjectServiceImpl.java | 257 ++++++++++ .../impl/ProjectStatisticsServiceImpl.java | 62 +++ .../service/impl/SparePartServiceImpl.java | 76 +-- .../mdm/service/EnergyMeterServiceTest.java | 109 ++-- .../InspectionTemplateServiceTest.java | 86 ++-- .../mdm/service/ProjectMemberServiceTest.java | 105 ++-- .../pms/mdm/service/ProjectServiceTest.java | 73 +-- .../pms/mdm/service/SparePartServiceTest.java | 113 +++-- module-wo/pom.xml | 6 + .../controller/MaintenanceTaskController.java | 49 +- .../ops/controller/WorkOrderController.java | 110 +++- .../pms/ops/dto/MaintenanceTaskStatsDTO.java | 9 +- .../com/ether/pms/ops/dto/ResumeRequest.java | 10 + .../com/ether/pms/ops/dto/ReturnRequest.java | 16 + .../com/ether/pms/ops/dto/SuspendRequest.java | 16 + .../ether/pms/ops/dto/WorkOrderStatsDTO.java | 7 +- .../ether/pms/ops/entity/InspectionItem.java | 5 +- .../pms/ops/entity/InspectionTemplate.java | 11 +- .../ether/pms/ops/entity/MaintenancePlan.java | 12 +- .../ether/pms/ops/entity/MaintenanceTask.java | 59 +-- .../com/ether/pms/ops/entity/WorkOrder.java | 62 ++- .../ether/pms/ops/entity/WorkOrderItem.java | 9 +- .../ops/entity/WorkOrderStatusHistory.java | 45 ++ .../ops/listener/WorkOrderEventListener.java | 97 ++++ .../repository/InspectionItemRepository.java | 11 +- .../InspectionTemplateRepository.java | 14 +- .../repository/MaintenancePlanRepository.java | 16 +- .../MaintenanceTaskJpaRepository.java | 107 ++++ .../repository/MaintenanceTaskRepository.java | 101 ++-- .../repository/WorkOrderItemRepository.java | 7 +- .../ops/repository/WorkOrderRepository.java | 79 +-- .../WorkOrderStatusHistoryRepository.java | 16 + .../ops/scheduler/MaintenanceScheduler.java | 57 +-- .../ops/service/MaintenancePlanService.java | 11 +- .../ops/service/MaintenanceTaskService.java | 20 +- .../pms/ops/service/WorkOrderService.java | 270 +++++++++- .../impl/MaintenancePlanServiceImpl.java | 20 +- .../impl/MaintenanceTaskServiceImpl.java | 88 ++-- .../service/impl/WorkOrderServiceImpl.java | 287 ++++++++--- .../com/ether/pms/ops/TestApplication.java | 22 + .../controller/WorkOrderControllerTest.java | 237 +++++++++ .../listener/WorkOrderEventListenerTest.java | 195 ++++++++ .../service/MaintenanceTaskServiceTest.java | 473 ++++++++++++++++++ .../pms/ops/service/WorkOrderServiceTest.java | 437 ++++++++++++++++ .../service/WorkOrderStatusHistoryTest.java | 247 +++++++++ .../service/impl/WorkOrderServiceTest.java | 179 ++++--- module-wo/src/test/resources/application.yml | 22 + pom.xml | 76 +-- 266 files changed, 9345 insertions(+), 6107 deletions(-) create mode 100644 module-asset/src/main/java/com/ether/pms/asset/entity/MaintenanceReminder.java create mode 100644 module-asset/src/main/java/com/ether/pms/asset/event/EquipmentFailureRecordedEvent.java create mode 100644 module-asset/src/main/java/com/ether/pms/asset/repository/MaintenanceReminderRepository.java create mode 100644 module-asset/src/main/java/com/ether/pms/asset/scheduler/MaintenanceReminderScheduler.java create mode 100644 module-asset/src/test/java/com/ether/pms/asset/service/EquipmentElevatorServiceTest.java create mode 100644 module-asset/src/test/java/com/ether/pms/asset/service/EquipmentHealthServiceTest.java create mode 100644 module-asset/src/test/java/com/ether/pms/asset/service/EquipmentServiceTest.java delete mode 100644 module-auth/src/main/java/com/ether/pms/auth/controller/DataAccessController.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/controller/PermissionTreeController.java delete mode 100644 module-auth/src/main/java/com/ether/pms/auth/controller/dto/DataAccessRequest.java delete mode 100644 module-auth/src/main/java/com/ether/pms/auth/entity/DataAccess.java delete mode 100644 module-auth/src/main/java/com/ether/pms/auth/repository/DataAccessRepository.java delete mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/DataAccessService.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/AuditLogServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/DataScopeServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/DeptServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginAttemptServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/PasswordServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/PermissionServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/RoleServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/SysConfigServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/UserManagementServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/UserProjectServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/service/impl/UserServiceImpl.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/vo/MenuVO.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/vo/PermissionTreeVO.java create mode 100644 module-auth/src/main/java/com/ether/pms/auth/vo/PermissionVO.java create mode 100644 module-auth/src/test/java/com/ether/pms/auth/TestApplication.java create mode 100644 module-auth/src/test/resources/application.yml create mode 100644 module-common/src/main/java/com/ether/pms/common/MaintenanceTaskRepository.java create mode 100644 module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectConfigServiceImpl.java create mode 100644 module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectMemberServiceImpl.java create mode 100644 module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectServiceImpl.java create mode 100644 module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectStatisticsServiceImpl.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/dto/ResumeRequest.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/dto/ReturnRequest.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/dto/SuspendRequest.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderStatusHistory.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/listener/WorkOrderEventListener.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskJpaRepository.java create mode 100644 module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderStatusHistoryRepository.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/TestApplication.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/controller/WorkOrderControllerTest.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/listener/WorkOrderEventListenerTest.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/service/MaintenanceTaskServiceTest.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderServiceTest.java create mode 100644 module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderStatusHistoryTest.java create mode 100644 module-wo/src/test/resources/application.yml diff --git a/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentController.java b/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentController.java index ad161c9..f13cebe 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentController.java +++ b/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentController.java @@ -11,8 +11,13 @@ import com.ether.pms.asset.service.*; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.BusinessException; import com.ether.pms.common.ErrorCode; -import com.ether.pms.common.util.BatchOperationValidator; import jakarta.validation.Valid; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -21,13 +26,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - @RestController @RequestMapping("/api/asset/equipment") @RequiredArgsConstructor @@ -40,10 +38,10 @@ public class EquipmentController { private final EquipmentEnergyService energyService; private final EquipmentFireService fireService; - private static final Set ALLOWED_CONTENT_TYPES = Set.of( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel" - ); + private static final Set ALLOWED_CONTENT_TYPES = + Set.of( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel"); private static final Set ALLOWED_EXTENSIONS = Set.of(".xlsx", ".xls"); @@ -62,7 +60,8 @@ public class EquipmentController { } @PutMapping("/{id}") - public ApiResponse updateEquipment(@PathVariable UUID id, @Valid @RequestBody Equipment equipment) { + public ApiResponse updateEquipment( + @PathVariable UUID id, @Valid @RequestBody Equipment equipment) { return ApiResponse.success(equipmentService.updateEquipment(id, equipment)); } @@ -79,7 +78,8 @@ public class EquipmentController { } @PostMapping("/import") - public ApiResponse> importEquipment(@RequestParam("file") MultipartFile file, @RequestParam UUID projectId) { + public ApiResponse> importEquipment( + @RequestParam("file") MultipartFile file, @RequestParam UUID projectId) { validateExcelFile(file); return ApiResponse.success(equipmentService.importFromExcel(file, projectId)); } @@ -112,17 +112,16 @@ public class EquipmentController { // Excel 行数预检 try (InputStream is = file.getInputStream(); - org.apache.poi.ss.usermodel.Workbook workbook = - org.apache.poi.ss.usermodel.WorkbookFactory.create(is)) { + org.apache.poi.ss.usermodel.Workbook workbook = + org.apache.poi.ss.usermodel.WorkbookFactory.create(is)) { org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(0); if (sheet != null) { int rowCount = sheet.getLastRowNum() + 1; if (rowCount > MAX_EXCEL_ROWS) { throw new BusinessException( - ErrorCode.FILE_004, - String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount) - ); + ErrorCode.FILE_004, + String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount)); } } } catch (IOException e) { @@ -155,7 +154,8 @@ public class EquipmentController { } @GetMapping("/by-ownership") - public ApiResponse> getEquipmentsByOwnership(@RequestParam OwnershipType ownership) { + public ApiResponse> getEquipmentsByOwnership( + @RequestParam OwnershipType ownership) { return ApiResponse.success(equipmentService.getEquipmentsByOwnership(ownership)); } @@ -180,13 +180,12 @@ public class EquipmentController { @GetMapping("/{id}/elevator") public ApiResponse getElevator(@PathVariable UUID id) { - return ApiResponse.success( - elevatorService.getByEquipmentId(id).orElse(null) - ); + return ApiResponse.success(elevatorService.getByEquipmentId(id).orElse(null)); } @PutMapping("/{id}/elevator") - public ApiResponse updateElevator(@PathVariable UUID id, @Valid @RequestBody EquipmentElevator elevator) { + public ApiResponse updateElevator( + @PathVariable UUID id, @Valid @RequestBody EquipmentElevator elevator) { elevator.setEquipmentId(id); return ApiResponse.success(elevatorService.saveOrUpdate(elevator)); } @@ -195,13 +194,12 @@ public class EquipmentController { @GetMapping("/{id}/hvac") public ApiResponse getHvac(@PathVariable UUID id) { - return ApiResponse.success( - hvacService.getByEquipmentId(id).orElse(null) - ); + return ApiResponse.success(hvacService.getByEquipmentId(id).orElse(null)); } @PutMapping("/{id}/hvac") - public ApiResponse updateHvac(@PathVariable UUID id, @Valid @RequestBody EquipmentHvac hvac) { + public ApiResponse updateHvac( + @PathVariable UUID id, @Valid @RequestBody EquipmentHvac hvac) { hvac.setEquipmentId(id); return ApiResponse.success(hvacService.saveOrUpdate(hvac)); } @@ -210,13 +208,12 @@ public class EquipmentController { @GetMapping("/{id}/energy") public ApiResponse getEnergy(@PathVariable UUID id) { - return ApiResponse.success( - energyService.getByEquipmentId(id).orElse(null) - ); + return ApiResponse.success(energyService.getByEquipmentId(id).orElse(null)); } @PutMapping("/{id}/energy") - public ApiResponse updateEnergy(@PathVariable UUID id, @Valid @RequestBody EquipmentEnergy energy) { + public ApiResponse updateEnergy( + @PathVariable UUID id, @Valid @RequestBody EquipmentEnergy energy) { energy.setEquipmentId(id); return ApiResponse.success(energyService.saveOrUpdate(energy)); } @@ -225,13 +222,12 @@ public class EquipmentController { @GetMapping("/{id}/fire") public ApiResponse getFire(@PathVariable UUID id) { - return ApiResponse.success( - fireService.getByEquipmentId(id).orElse(null) - ); + return ApiResponse.success(fireService.getByEquipmentId(id).orElse(null)); } @PutMapping("/{id}/fire") - public ApiResponse updateFire(@PathVariable UUID id, @Valid @RequestBody EquipmentFire fire) { + public ApiResponse updateFire( + @PathVariable UUID id, @Valid @RequestBody EquipmentFire fire) { fire.setEquipmentId(id); return ApiResponse.success(fireService.saveOrUpdate(fire)); } diff --git a/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentHealthController.java b/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentHealthController.java index 523ee84..53f5f32 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentHealthController.java +++ b/module-asset/src/main/java/com/ether/pms/asset/controller/EquipmentHealthController.java @@ -6,15 +6,14 @@ import com.ether.pms.asset.service.EquipmentHealthService; import com.ether.pms.common.ApiResponse; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.math.BigDecimal; -import java.util.List; -import java.util.UUID; - @RestController @RequestMapping("/api/asset/equipment-health") @RequiredArgsConstructor @@ -30,24 +29,29 @@ public class EquipmentHealthController { } @GetMapping("/{equipmentId}/history") - public ApiResponse> getHealthHistory(@PathVariable UUID equipmentId) { + public ApiResponse> getHealthHistory( + @PathVariable UUID equipmentId) { List history = equipmentHealthService.getHealthHistory(equipmentId); return ApiResponse.success("[Beta] 健康评分数据准确性待验证", history); } @PostMapping("/calculate") - public ApiResponse calculateHealthScore(@Valid @RequestBody CalculateHealthRequest request) { - EquipmentHealthScore score = equipmentHealthService.calculateHealthScore(request.getEquipmentId()); + public ApiResponse calculateHealthScore( + @Valid @RequestBody CalculateHealthRequest request) { + EquipmentHealthScore score = + equipmentHealthService.calculateHealthScore(request.getEquipmentId()); return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score); } @PostMapping("/failure-history") - public ApiResponse recordFailure(@Valid @RequestBody EquipmentFailureHistory failure) { + public ApiResponse recordFailure( + @Valid @RequestBody EquipmentFailureHistory failure) { return ApiResponse.success(equipmentHealthService.recordFailure(failure)); } @GetMapping("/failure-history/{equipmentId}") - public ApiResponse> getFailureHistory(@PathVariable UUID equipmentId) { + public ApiResponse> getFailureHistory( + @PathVariable UUID equipmentId) { return ApiResponse.success(equipmentHealthService.getFailureHistory(equipmentId)); } diff --git a/module-asset/src/main/java/com/ether/pms/asset/controller/OwnershipEntityController.java b/module-asset/src/main/java/com/ether/pms/asset/controller/OwnershipEntityController.java index 4cce9ce..20bdf54 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/controller/OwnershipEntityController.java +++ b/module-asset/src/main/java/com/ether/pms/asset/controller/OwnershipEntityController.java @@ -4,13 +4,12 @@ import com.ether.pms.asset.entity.OwnershipEntity; import com.ether.pms.asset.repository.OwnershipEntityRepository; import com.ether.pms.common.ApiResponse; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - @RestController @RequestMapping("/api/asset/ownership-entity") @RequiredArgsConstructor @@ -27,14 +26,16 @@ public class OwnershipEntityController { @GetMapping("/{id}") public ApiResponse getById(@PathVariable UUID id) { return ApiResponse.success( - ownershipEntityRepository.findByIdAndIsDeletedFalse(id).orElse(null) - ); + ownershipEntityRepository.findByIdAndIsDeletedFalse(id).orElse(null)); } @PutMapping("/{id}") - public ApiResponse update(@PathVariable UUID id, @Valid @RequestBody OwnershipEntity entity) { - OwnershipEntity existing = ownershipEntityRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); + public ApiResponse update( + @PathVariable UUID id, @Valid @RequestBody OwnershipEntity entity) { + OwnershipEntity existing = + ownershipEntityRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); existing.setEntityName(entity.getEntityName()); existing.setContactPerson(entity.getContactPerson()); existing.setContactPhone(entity.getContactPhone()); @@ -51,20 +52,24 @@ public class OwnershipEntityController { @DeleteMapping("/{id}") public ApiResponse delete(@PathVariable UUID id) { - OwnershipEntity entity = ownershipEntityRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); + OwnershipEntity entity = + ownershipEntityRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new RuntimeException("归属主体不存在: " + id)); entity.setIsDeleted(true); ownershipEntityRepository.save(entity); return ApiResponse.success(null); } @GetMapping("/by-type") - public ApiResponse> getByType(@RequestParam OwnershipEntity.EntityType type) { - return ApiResponse.success(ownershipEntityRepository.findByEntityTypeAndIsDeletedFalse(type)); + public ApiResponse> getByType( + @RequestParam OwnershipEntity.EntityType type) { + return ApiResponse.success( + ownershipEntityRepository.findByEntityTypeAndIsDeletedFalse(type)); } @GetMapping public ApiResponse> getAll() { return ApiResponse.success(ownershipEntityRepository.findByIsDeletedFalse()); } -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/dto/EquipmentCreateDTO.java b/module-asset/src/main/java/com/ether/pms/asset/dto/EquipmentCreateDTO.java index 6455b15..90861b7 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/dto/EquipmentCreateDTO.java +++ b/module-asset/src/main/java/com/ether/pms/asset/dto/EquipmentCreateDTO.java @@ -2,10 +2,10 @@ package com.ether.pms.asset.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.util.UUID; +import lombok.Data; @Data public class EquipmentCreateDTO { @@ -57,4 +57,4 @@ public class EquipmentCreateDTO { private String installationEnvironment; private String protectionLevel; -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeDTO.java b/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeDTO.java index 708a516..3d96950 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeDTO.java +++ b/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeDTO.java @@ -1,8 +1,7 @@ package com.ether.pms.asset.dto; -import lombok.Data; - import java.util.UUID; +import lombok.Data; @Data public class SpaceNodeDTO { @@ -15,4 +14,4 @@ public class SpaceNodeDTO { private String building; private String unit; private String roomNo; -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeEquipmentDTO.java b/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeEquipmentDTO.java index c34e481..e4f50e9 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeEquipmentDTO.java +++ b/module-asset/src/main/java/com/ether/pms/asset/dto/SpaceNodeEquipmentDTO.java @@ -1,10 +1,10 @@ package com.ether.pms.asset.dto; -import lombok.Data; -import lombok.EqualsAndHashCode; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) @@ -38,4 +38,4 @@ public class SpaceNodeEquipmentDTO extends SpaceNodeDTO { private String model; private Integer quantity; } -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/Equipment.java b/module-asset/src/main/java/com/ether/pms/asset/entity/Equipment.java index a88f2c0..d72a43e 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/Equipment.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/Equipment.java @@ -5,29 +5,30 @@ import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.SystemType; import jakarta.persistence.*; -import lombok.Data; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.Data; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity -@Table(name = "asset_equipment", indexes = { - @Index(name = "idx_equipment_project", columnList = "project_id"), - @Index(name = "idx_equipment_space", columnList = "space_node_id"), - @Index(name = "idx_equipment_type", columnList = "equipment_type"), - @Index(name = "idx_equipment_ownership", columnList = "ownership_type"), - @Index(name = "idx_equipment_code", columnList = "equipment_code"), - @Index(name = "idx_equipment_status", columnList = "status"), - @Index(name = "idx_eq_project_status", columnList = "project_id, status"), - @Index(name = "idx_eq_project_type", columnList = "project_id, equipment_type"), - @Index(name = "idx_eq_project_deleted", columnList = "project_id, is_deleted") -}) +@Table( + name = "asset_equipment", + indexes = { + @Index(name = "idx_equipment_project", columnList = "project_id"), + @Index(name = "idx_equipment_space", columnList = "space_node_id"), + @Index(name = "idx_equipment_type", columnList = "equipment_type"), + @Index(name = "idx_equipment_ownership", columnList = "ownership_type"), + @Index(name = "idx_equipment_code", columnList = "equipment_code"), + @Index(name = "idx_equipment_status", columnList = "status"), + @Index(name = "idx_eq_project_status", columnList = "project_id, status"), + @Index(name = "idx_eq_project_type", columnList = "project_id, equipment_type"), + @Index(name = "idx_eq_project_deleted", columnList = "project_id, is_deleted") + }) @Data public class Equipment { diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentElevator.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentElevator.java index f1d3f16..d7778d4 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentElevator.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentElevator.java @@ -1,12 +1,11 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "asset_equipment_elevator") diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentEnergy.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentEnergy.java index d94c19b..613f14c 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentEnergy.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentEnergy.java @@ -1,12 +1,11 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "asset_equipment_energy") diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFailureHistory.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFailureHistory.java index c3fcd3d..d49e08c 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFailureHistory.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFailureHistory.java @@ -1,19 +1,20 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity -@Table(name = "ops_equipment_failure_history", indexes = { - @Index(name = "idx_failure_equipment", columnList = "equipment_id"), - @Index(name = "idx_failure_time", columnList = "failure_time"), - @Index(name = "idx_failure_project", columnList = "project_id"), - @Index(name = "idx_efh_project_time", columnList = "project_id, failure_time"), - @Index(name = "idx_efh_equipment_time", columnList = "equipment_id, failure_time DESC") -}) +@Table( + name = "ops_equipment_failure_history", + indexes = { + @Index(name = "idx_failure_equipment", columnList = "equipment_id"), + @Index(name = "idx_failure_time", columnList = "failure_time"), + @Index(name = "idx_failure_project", columnList = "project_id"), + @Index(name = "idx_efh_project_time", columnList = "project_id, failure_time"), + @Index(name = "idx_efh_equipment_time", columnList = "equipment_id, failure_time DESC") + }) @Data public class EquipmentFailureHistory { @@ -154,4 +155,4 @@ public class EquipmentFailureHistory { return desc; } } -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFire.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFire.java index 73fdc06..aa8aee1 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFire.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentFire.java @@ -1,12 +1,11 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "asset_equipment_fire") diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHealthScore.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHealthScore.java index a549574..00283cb 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHealthScore.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHealthScore.java @@ -1,21 +1,20 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; import java.math.BigDecimal; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; -/** - * 此功能为Beta版本,数据准确性待验证 - */ +/** 此功能为Beta版本,数据准确性待验证 */ @Entity -@Table(name = "ops_equipment_health_score", indexes = { - @Index(name = "idx_health_equipment", columnList = "equipment_id"), - @Index(name = "idx_health_calc_time", columnList = "calculated_at"), - @Index(name = "idx_health_project", columnList = "project_id") -}) +@Table( + name = "ops_equipment_health_score", + indexes = { + @Index(name = "idx_health_equipment", columnList = "equipment_id"), + @Index(name = "idx_health_calc_time", columnList = "calculated_at"), + @Index(name = "idx_health_project", columnList = "project_id") + }) @Data public class EquipmentHealthScore { diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHvac.java b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHvac.java index c8bb89c..11e57dd 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHvac.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/EquipmentHvac.java @@ -1,12 +1,11 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "asset_equipment_hvac") diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/MaintenanceReminder.java b/module-asset/src/main/java/com/ether/pms/asset/entity/MaintenanceReminder.java new file mode 100644 index 0000000..7416f46 --- /dev/null +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/MaintenanceReminder.java @@ -0,0 +1,71 @@ +package com.ether.pms.asset.entity; + +import jakarta.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Data; + +@Entity +@Table( + name = "mdm_maintenance_reminder", + indexes = { + @Index(name = "idx_reminder_equipment", columnList = "equipment_id"), + @Index(name = "idx_reminder_type_date", columnList = "reminder_type, contract_end_date") + }) +@Data +public class MaintenanceReminder { + + public static final String REMIND_7 = "REMIND_7"; + public static final String REMIND_3 = "REMIND_3"; + public static final String REMIND_1 = "REMIND_1"; + public static final String EXPIRED = "EXPIRED"; + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "equipment_id", nullable = false) + private UUID equipmentId; + + @Column(name = "equipment_code", length = 50) + private String equipmentCode; + + @Column(name = "maintenance_vendor", length = 200) + private String maintenanceVendor; + + @Column(name = "vendor_contact", length = 100) + private String vendorContact; + + @Column(name = "contract_end_date", nullable = false) + private LocalDate contractEndDate; + + @Column(name = "reminder_type", nullable = false, length = 20) + private String reminderType; + + @Column(name = "reminded_at") + private LocalDateTime remindedAt; + + @Column(name = "is_active") + private Boolean isActive = true; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + if (this.isActive == null) { + this.isActive = true; + } + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/entity/OwnershipEntity.java b/module-asset/src/main/java/com/ether/pms/asset/entity/OwnershipEntity.java index 9532bab..196af0a 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/entity/OwnershipEntity.java +++ b/module-asset/src/main/java/com/ether/pms/asset/entity/OwnershipEntity.java @@ -1,18 +1,19 @@ package com.ether.pms.asset.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity -@Table(name = "asset_ownership_entity", indexes = { - @Index(name = "idx_ownership_entity_type", columnList = "entity_type"), - @Index(name = "idx_ownership_entity_code", columnList = "entity_code") -}) +@Table( + name = "asset_ownership_entity", + indexes = { + @Index(name = "idx_ownership_entity_type", columnList = "entity_type"), + @Index(name = "idx_ownership_entity_code", columnList = "entity_code") + }) @Data public class OwnershipEntity { diff --git a/module-asset/src/main/java/com/ether/pms/asset/enums/SystemType.java b/module-asset/src/main/java/com/ether/pms/asset/enums/SystemType.java index 6ad1b4f..fe5f739 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/enums/SystemType.java +++ b/module-asset/src/main/java/com/ether/pms/asset/enums/SystemType.java @@ -1,18 +1,14 @@ package com.ether.pms.asset.enums; -/** - * 商业地产8大系统分类枚举 - */ public enum SystemType { HVAC("暖通空调"), - FIRE("消防系统"), + FIRE_PROTECTION("消防系统"), ELEVATOR("电梯系统"), ELECTRICAL("电气系统"), PLUMBING("给排水"), - BAS("弱电智能化"), - KITCHEN("餐饮厨房"), + SECURITY("弱电智能化"), LANDSCAPE("景观"), - OTHER("其他"); + ENERGY_METER("能源计量"); private final String description; diff --git a/module-asset/src/main/java/com/ether/pms/asset/event/EquipmentFailureRecordedEvent.java b/module-asset/src/main/java/com/ether/pms/asset/event/EquipmentFailureRecordedEvent.java new file mode 100644 index 0000000..b316bd0 --- /dev/null +++ b/module-asset/src/main/java/com/ether/pms/asset/event/EquipmentFailureRecordedEvent.java @@ -0,0 +1,39 @@ +package com.ether.pms.asset.event; + +import com.ether.pms.asset.entity.EquipmentFailureHistory; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class EquipmentFailureRecordedEvent extends ApplicationEvent { + + private final UUID equipmentId; + private final UUID failureHistoryId; + private final UUID projectId; + private final String equipmentCode; + private final String equipmentName; + private final String faultType; + private final String faultLevel; + private final String failureReason; + private final String failureDescription; + private final LocalDateTime occurredAt; + private final LocalDateTime createdAt; + + public EquipmentFailureRecordedEvent(Object source, EquipmentFailureHistory failure) { + super(source); + this.equipmentId = failure.getEquipmentId(); + this.failureHistoryId = failure.getId(); + this.projectId = failure.getProjectId(); + this.equipmentCode = failure.getEquipmentCode(); + this.equipmentName = failure.getEquipmentName(); + this.faultType = failure.getFailureType() != null ? failure.getFailureType().name() : null; + this.faultLevel = + failure.getFailureLevel() != null ? failure.getFailureLevel().name() : null; + this.failureReason = failure.getFailureReason(); + this.failureDescription = failure.getFailureDescription(); + this.occurredAt = failure.getFailureTime(); + this.createdAt = failure.getCreatedAt(); + } +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentElevatorRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentElevatorRepository.java index 6e9fc75..09f6350 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentElevatorRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentElevatorRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentElevator; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface EquipmentElevatorRepository extends JpaRepository { diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentEnergyRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentEnergyRepository.java index b502725..16a5cda 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentEnergyRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentEnergyRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentEnergy; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface EquipmentEnergyRepository extends JpaRepository { diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFailureHistoryRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFailureHistoryRepository.java index 586caf2..6516dc9 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFailureHistoryRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFailureHistoryRepository.java @@ -1,28 +1,35 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentFailureHistory; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; @Repository -public interface EquipmentFailureHistoryRepository extends JpaRepository { +public interface EquipmentFailureHistoryRepository + extends JpaRepository { List findByEquipmentIdOrderByFailureTimeDesc(UUID equipmentId); List findByProjectIdAndFailureTimeBetweenOrderByFailureTimeDesc( UUID projectId, LocalDateTime startTime, LocalDateTime endTime); - @Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since ORDER BY f.failureTime DESC") - List findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); + @Query( + "SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since ORDER BY f.failureTime DESC") + List findByEquipmentIdSince( + @Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); - @Query("SELECT COUNT(f) FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since") - long countByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); + @Query( + "SELECT COUNT(f) FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.failureTime >= :since") + long countByEquipmentIdSince( + @Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); - @Query("SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.repairEndTime IS NOT NULL AND f.failureTime >= :since ORDER BY f.failureTime DESC") - List findRepairedFailuresByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); -} \ No newline at end of file + @Query( + "SELECT f FROM EquipmentFailureHistory f WHERE f.equipmentId = :equipmentId AND f.repairEndTime IS NOT NULL AND f.failureTime >= :since ORDER BY f.failureTime DESC") + List findRepairedFailuresByEquipmentIdSince( + @Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFireRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFireRepository.java index ea84de6..b0fab80 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFireRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentFireRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentFire; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface EquipmentFireRepository extends JpaRepository { diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHealthScoreRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHealthScoreRepository.java index ee69df6..5d2a4b8 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHealthScoreRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHealthScoreRepository.java @@ -1,29 +1,37 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentHealthScore; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; @Repository public interface EquipmentHealthScoreRepository extends JpaRepository { List findByEquipmentIdOrderByCalculatedAtDesc(UUID equipmentId); - @Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId ORDER BY h.calculatedAt DESC LIMIT 1") + @Query( + "SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId ORDER BY h.calculatedAt DESC LIMIT 1") Optional findLatestByEquipmentId(@Param("equipmentId") UUID equipmentId); - @Query("SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId AND h.calculatedAt >= :since ORDER BY h.calculatedAt DESC") - List findByEquipmentIdSince(@Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); + @Query( + "SELECT h FROM EquipmentHealthScore h WHERE h.equipmentId = :equipmentId AND h.calculatedAt >= :since ORDER BY h.calculatedAt DESC") + List findByEquipmentIdSince( + @Param("equipmentId") UUID equipmentId, @Param("since") LocalDateTime since); - @Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId ORDER BY h.calculatedAt DESC") - List findByProjectIdOrderByCalculatedAtDesc(@Param("projectId") UUID projectId); + @Query( + "SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId ORDER BY h.calculatedAt DESC") + List findByProjectIdOrderByCalculatedAtDesc( + @Param("projectId") UUID projectId); - @Query("SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId AND h.healthLevel IN :levels ORDER BY h.healthScore ASC") - List findByProjectIdAndHealthLevelIn(@Param("projectId") UUID projectId, @Param("levels") List levels); -} \ No newline at end of file + @Query( + "SELECT h FROM EquipmentHealthScore h WHERE h.projectId = :projectId AND h.healthLevel IN :levels ORDER BY h.healthScore ASC") + List findByProjectIdAndHealthLevelIn( + @Param("projectId") UUID projectId, + @Param("levels") List levels); +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHvacRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHvacRepository.java index 6405be4..d01dbcb 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHvacRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentHvacRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.EquipmentHvac; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface EquipmentHvacRepository extends JpaRepository { diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentRepository.java index 9c48cd3..945d77a 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/EquipmentRepository.java @@ -4,21 +4,22 @@ import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.OwnershipType; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - @Repository public interface EquipmentRepository extends JpaRepository { List findByProjectIdAndIsDeletedFalse(UUID projectId); - List findByProjectIdAndStatusAndIsDeletedFalse(UUID projectId, EquipmentStatus status); + List findByProjectIdAndStatusAndIsDeletedFalse( + UUID projectId, EquipmentStatus status); List findBySpaceNodeIdAndIsDeletedFalse(UUID spaceNodeId); @@ -38,12 +39,20 @@ public interface EquipmentRepository extends JpaRepository { @Query("SELECT e FROM Equipment e WHERE e.spaceNodeId = :spaceNodeId AND e.isDeleted = false") List findBySpaceNode(@Param("spaceNodeId") UUID spaceNodeId); - @Query("SELECT COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false") + @Query( + "SELECT COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false") long countByProject(@Param("projectId") UUID projectId); - @Query("SELECT e.equipmentType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.equipmentType") + @Query( + "SELECT e.equipmentType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.equipmentType") List countByType(@Param("projectId") UUID projectId); - @Query("SELECT e.ownershipType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.ownershipType") + @Query( + "SELECT e.ownershipType, COUNT(e) FROM Equipment e WHERE e.projectId = :projectId AND e.isDeleted = false GROUP BY e.ownershipType") List countByOwnership(@Param("projectId") UUID projectId); -} \ No newline at end of file + + @Query( + "SELECT e FROM Equipment e WHERE e.maintenanceContractEnd >= :startDate AND e.maintenanceContractEnd <= :endDate AND e.isDeleted = false AND e.maintenanceContractEnd IS NOT NULL") + List findByMaintenanceContractEndBetween( + @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/MaintenanceReminderRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/MaintenanceReminderRepository.java new file mode 100644 index 0000000..b4d95dc --- /dev/null +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/MaintenanceReminderRepository.java @@ -0,0 +1,34 @@ +package com.ether.pms.asset.repository; + +import com.ether.pms.asset.entity.MaintenanceReminder; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface MaintenanceReminderRepository extends JpaRepository { + + Optional findByEquipmentIdAndReminderTypeAndContractEndDate( + UUID equipmentId, String reminderType, LocalDate contractEndDate); + + @Query( + "SELECT r FROM MaintenanceReminder r WHERE r.equipmentId = :equipmentId AND r.reminderType = :reminderType AND r.isActive = true") + List findActiveByEquipmentIdAndReminderType( + @Param("equipmentId") UUID equipmentId, @Param("reminderType") String reminderType); + + @Query( + "SELECT CASE WHEN COUNT(r) > 0 THEN true ELSE false END FROM MaintenanceReminder r WHERE r.equipmentId = :equipmentId AND r.reminderType = :reminderType") + boolean existsByEquipmentIdAndReminderType( + @Param("equipmentId") UUID equipmentId, @Param("reminderType") String reminderType); + + List findByEquipmentIdAndIsActiveTrue(UUID equipmentId); + + @Query( + "SELECT r FROM MaintenanceReminder r WHERE r.contractEndDate <= :date AND r.isActive = true") + List findExpiredReminders(@Param("date") LocalDate date); +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/repository/OwnershipEntityRepository.java b/module-asset/src/main/java/com/ether/pms/asset/repository/OwnershipEntityRepository.java index 8ffa346..ce1ad15 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/repository/OwnershipEntityRepository.java +++ b/module-asset/src/main/java/com/ether/pms/asset/repository/OwnershipEntityRepository.java @@ -1,12 +1,11 @@ package com.ether.pms.asset.repository; import com.ether.pms.asset.entity.OwnershipEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface OwnershipEntityRepository extends JpaRepository { diff --git a/module-asset/src/main/java/com/ether/pms/asset/scheduler/MaintenanceReminderScheduler.java b/module-asset/src/main/java/com/ether/pms/asset/scheduler/MaintenanceReminderScheduler.java new file mode 100644 index 0000000..ddfbdc4 --- /dev/null +++ b/module-asset/src/main/java/com/ether/pms/asset/scheduler/MaintenanceReminderScheduler.java @@ -0,0 +1,115 @@ +package com.ether.pms.asset.scheduler; + +import com.ether.pms.asset.entity.Equipment; +import com.ether.pms.asset.entity.MaintenanceReminder; +import com.ether.pms.asset.repository.EquipmentRepository; +import com.ether.pms.asset.repository.MaintenanceReminderRepository; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MaintenanceReminderScheduler { + + private final MaintenanceReminderRepository reminderRepository; + private final EquipmentRepository equipmentRepository; + + @Scheduled(cron = "0 0 9 * * ?") + @Transactional + public void checkMaintenanceExpiry() { + log.info("开始执行维保到期提醒任务..."); + + try { + LocalDate today = LocalDate.now(); + + List expiringEquipments = + equipmentRepository.findByMaintenanceContractEndBetween( + today, today.plusDays(7)); + + int reminderCount = 0; + + for (Equipment equipment : expiringEquipments) { + LocalDate endDate = equipment.getMaintenanceContractEnd(); + String reminderType = determineReminderType(today, endDate); + + if (!hasReminder(equipment.getId(), reminderType)) { + sendReminder(equipment, reminderType); + saveReminder(equipment, reminderType); + reminderCount++; + } + } + + log.info("维保到期提醒任务完成,共发送 {} 条提醒", reminderCount); + + } catch (Exception e) { + log.error("维保到期提醒任务失败: {}", e.getMessage(), e); + } + } + + private String determineReminderType(LocalDate today, LocalDate endDate) { + long daysBetween = Duration.between(today.atStartOfDay(), endDate.atStartOfDay()).toDays(); + + if (daysBetween <= 0) { + return MaintenanceReminder.EXPIRED; + } else if (daysBetween <= 1) { + return MaintenanceReminder.REMIND_1; + } else if (daysBetween <= 3) { + return MaintenanceReminder.REMIND_3; + } else { + return MaintenanceReminder.REMIND_7; + } + } + + private boolean hasReminder(java.util.UUID equipmentId, String reminderType) { + return reminderRepository.existsByEquipmentIdAndReminderType(equipmentId, reminderType); + } + + private void sendReminder(Equipment equipment, String reminderType) { + String reminderDesc = getReminderDescription(reminderType); + log.info( + "维保到期提醒: 设备[{}]的维保合同将在{}到期,提醒类型: {}", + equipment.getEquipmentCode(), + equipment.getMaintenanceContractEnd(), + reminderDesc); + + // TODO: 后续对接消息通知系统,发送实际通知 + // 可以通过以下方式发送: + // 1. 邮件通知维保负责人 + // 2. 站内消息通知 + // 3. 短信通知 + // 4. 企业微信/钉钉机器人通知 + } + + private String getReminderDescription(String reminderType) { + return switch (reminderType) { + case MaintenanceReminder.REMIND_7 -> "7天前提醒"; + case MaintenanceReminder.REMIND_3 -> "3天前提醒"; + case MaintenanceReminder.REMIND_1 -> "1天前提醒"; + case MaintenanceReminder.EXPIRED -> "已到期"; + default -> "未知提醒"; + }; + } + + private void saveReminder(Equipment equipment, String reminderType) { + MaintenanceReminder reminder = new MaintenanceReminder(); + reminder.setEquipmentId(equipment.getId()); + reminder.setEquipmentCode(equipment.getEquipmentCode()); + reminder.setMaintenanceVendor(equipment.getMaintenanceVendor()); + reminder.setVendorContact(equipment.getMaintenanceVendorContact()); + reminder.setContractEndDate(equipment.getMaintenanceContractEnd()); + reminder.setReminderType(reminderType); + reminder.setRemindedAt(LocalDateTime.now()); + reminder.setIsActive(true); + + reminderRepository.save(reminder); + log.debug("保存维保提醒记录: 设备[{}], 类型[{}]", equipment.getEquipmentCode(), reminderType); + } +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentElevatorService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentElevatorService.java index 15ae0a2..7e78803 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentElevatorService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentElevatorService.java @@ -1,7 +1,6 @@ package com.ether.pms.asset.service; import com.ether.pms.asset.entity.EquipmentElevator; - import java.util.Optional; import java.util.UUID; diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentEnergyService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentEnergyService.java index f8dd086..9d0f1f1 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentEnergyService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentEnergyService.java @@ -1,7 +1,6 @@ package com.ether.pms.asset.service; import com.ether.pms.asset.entity.EquipmentEnergy; - import java.util.Optional; import java.util.UUID; diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentFireService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentFireService.java index 82f6eb2..a651444 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentFireService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentFireService.java @@ -1,7 +1,6 @@ package com.ether.pms.asset.service; import com.ether.pms.asset.entity.EquipmentFire; - import java.util.Optional; import java.util.UUID; diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHealthService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHealthService.java index 14869a5..8943447 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHealthService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHealthService.java @@ -9,44 +9,26 @@ import java.util.UUID; public interface EquipmentHealthService { /** - * 计算设备健康度 - * 基础分 = 100 - * 故障率扣分 = 故障次数 × 5(近30天每故障扣5分) - * 维保扣分 = (1 - 维保完成率) × 20 - * 年龄扣分 = 投入使用年限 × 2(最高扣10分) - * 健康度 = 基础分 - 故障率扣分 - 维保扣分 - 年龄扣分 + * 计算设备健康度 基础分 = 100 故障率扣分 = 故障次数 × 5(近30天每故障扣5分) 维保扣分 = (1 - 维保完成率) × 20 年龄扣分 = 投入使用年限 × + * 2(最高扣10分) 健康度 = 基础分 - 故障率扣分 - 维保扣分 - 年龄扣分 */ EquipmentHealthScore calculateHealthScore(UUID equipmentId); - /** - * 计算平均故障间隔时间 (MTBF) - * MTBF = 运行时间 / 故障次数 - */ + /** 计算平均故障间隔时间 (MTBF) MTBF = 运行时间 / 故障次数 */ BigDecimal calculateMTBF(UUID equipmentId, Integer days); - /** - * 计算平均修复时间 (MTTR) - * MTTR = 总修复时间 / 修复次数 - */ + /** 计算平均修复时间 (MTTR) MTTR = 总修复时间 / 修复次数 */ BigDecimal calculateMTTR(UUID equipmentId, Integer days); - /** - * 记录故障 - */ + /** 记录故障 */ EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure); - /** - * 获取设备健康度历史 - */ + /** 获取设备健康度历史 */ List getHealthHistory(UUID equipmentId); - /** - * 获取设备最新健康度 - */ + /** 获取设备最新健康度 */ EquipmentHealthScore getLatestHealthScore(UUID equipmentId); - /** - * 获取设备的故障历史 - */ + /** 获取设备的故障历史 */ List getFailureHistory(UUID equipmentId); -} \ No newline at end of file +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHvacService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHvacService.java index 9a6513a..3d24b68 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHvacService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentHvacService.java @@ -1,7 +1,6 @@ package com.ether.pms.asset.service; import com.ether.pms.asset.entity.EquipmentHvac; - import java.util.Optional; import java.util.UUID; diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentService.java b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentService.java index 7fe08a2..f7a5f51 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentService.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/EquipmentService.java @@ -3,11 +3,10 @@ package com.ether.pms.asset.service; import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.OwnershipType; -import org.springframework.web.multipart.MultipartFile; - import java.util.List; import java.util.Map; import java.util.UUID; +import org.springframework.web.multipart.MultipartFile; public interface EquipmentService { diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentElevatorServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentElevatorServiceImpl.java index 6018fdc..75cbd7d 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentElevatorServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentElevatorServiceImpl.java @@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl; import com.ether.pms.asset.entity.EquipmentElevator; import com.ether.pms.asset.repository.EquipmentElevatorRepository; import com.ether.pms.asset.service.EquipmentElevatorService; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; -import java.util.UUID; - @Service @RequiredArgsConstructor public class EquipmentElevatorServiceImpl implements EquipmentElevatorService { @@ -19,7 +18,8 @@ public class EquipmentElevatorServiceImpl implements EquipmentElevatorService { @Override @Transactional public EquipmentElevator saveOrUpdate(EquipmentElevator elevator) { - Optional existing = elevatorRepository.findByEquipmentId(elevator.getEquipmentId()); + Optional existing = + elevatorRepository.findByEquipmentId(elevator.getEquipmentId()); if (existing.isPresent()) { elevator.setId(existing.get().getId()); } diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentEnergyServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentEnergyServiceImpl.java index 70a4fb1..22dfa40 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentEnergyServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentEnergyServiceImpl.java @@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl; import com.ether.pms.asset.entity.EquipmentEnergy; import com.ether.pms.asset.repository.EquipmentEnergyRepository; import com.ether.pms.asset.service.EquipmentEnergyService; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; -import java.util.UUID; - @Service @RequiredArgsConstructor public class EquipmentEnergyServiceImpl implements EquipmentEnergyService { @@ -19,7 +18,8 @@ public class EquipmentEnergyServiceImpl implements EquipmentEnergyService { @Override @Transactional public EquipmentEnergy saveOrUpdate(EquipmentEnergy energy) { - Optional existing = energyRepository.findByEquipmentId(energy.getEquipmentId()); + Optional existing = + energyRepository.findByEquipmentId(energy.getEquipmentId()); if (existing.isPresent()) { energy.setId(existing.get().getId()); } diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentFireServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentFireServiceImpl.java index b4e7528..0b8a526 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentFireServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentFireServiceImpl.java @@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl; import com.ether.pms.asset.entity.EquipmentFire; import com.ether.pms.asset.repository.EquipmentFireRepository; import com.ether.pms.asset.service.EquipmentFireService; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; -import java.util.UUID; - @Service @RequiredArgsConstructor public class EquipmentFireServiceImpl implements EquipmentFireService { diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHealthServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHealthServiceImpl.java index 52e07f7..288f54c 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHealthServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHealthServiceImpl.java @@ -1,18 +1,15 @@ package com.ether.pms.asset.service.impl; -import com.ether.pms.common.BusinessException; import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.entity.EquipmentFailureHistory; import com.ether.pms.asset.entity.EquipmentHealthScore; +import com.ether.pms.asset.event.EquipmentFailureRecordedEvent; import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository; import com.ether.pms.asset.repository.EquipmentHealthScoreRepository; import com.ether.pms.asset.repository.EquipmentRepository; import com.ether.pms.asset.service.EquipmentHealthService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.MaintenanceTaskRepository; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; @@ -21,6 +18,11 @@ import java.time.Period; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @@ -30,8 +32,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { private final EquipmentFailureHistoryRepository failureHistoryRepository; private final EquipmentHealthScoreRepository healthScoreRepository; private final EquipmentRepository equipmentRepository; - // TODO: 需要改为从 ops 模块查询工单数据 - // private final MaintenanceTaskRepository maintenanceTaskRepository; + private final MaintenanceTaskRepository maintenanceTaskRepository; + private final ApplicationEventPublisher eventPublisher; private static final BigDecimal BASE_SCORE = new BigDecimal("100"); private static final BigDecimal FAILURE_DEDUCTION_PER_COUNT = new BigDecimal("5"); @@ -43,20 +45,23 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { @Override @Transactional public EquipmentHealthScore calculateHealthScore(UUID equipmentId) { - Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(equipmentId) - .orElseThrow(() -> new BusinessException(6001, "设备不存在")); + Equipment equipment = + equipmentRepository + .findByIdAndIsDeletedFalse(equipmentId) + .orElseThrow(() -> new BusinessException(6001, "设备不存在")); // 获取近30天故障次数 LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30); - long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo); + long failureCount30d = + failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo); // 计算故障率扣分 - BigDecimal failureDeduction = FAILURE_DEDUCTION_PER_COUNT.multiply(BigDecimal.valueOf(failureCount30d)); + BigDecimal failureDeduction = + FAILURE_DEDUCTION_PER_COUNT.multiply(BigDecimal.valueOf(failureCount30d)); - // TODO: 从 ops 模块查询工单数据计算维保完成率 - // 暂时跳过维保完成率计算 - BigDecimal maintenanceCompletionRate = BigDecimal.ONE; - BigDecimal maintenanceDeduction = BigDecimal.ZERO; + // 计算维保完成率 + BigDecimal maintenanceCompletionRate = calculateMaintenanceCompletionRate(equipmentId); + BigDecimal maintenanceDeduction = calculateMaintenanceDeduction(maintenanceCompletionRate); // 计算年龄扣分 BigDecimal equipmentAgeYears = calculateEquipmentAge(equipment); @@ -66,10 +71,11 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { } // 计算健康度 - BigDecimal healthScore = BASE_SCORE - .subtract(failureDeduction) - .subtract(maintenanceDeduction) - .subtract(ageDeduction); + BigDecimal healthScore = + BASE_SCORE + .subtract(failureDeduction) + .subtract(maintenanceDeduction) + .subtract(ageDeduction); // 确保健康度不低于0 if (healthScore.compareTo(BigDecimal.ZERO) < 0) { @@ -90,7 +96,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP)); health.setAgeDeduction(ageDeduction.setScale(2, RoundingMode.HALF_UP)); health.setFailureCount30d((int) failureCount30d); - health.setMaintenanceCompletionRate(maintenanceCompletionRate.setScale(4, RoundingMode.HALF_UP)); + health.setMaintenanceCompletionRate( + maintenanceCompletionRate.setScale(4, RoundingMode.HALF_UP)); health.setEquipmentAgeYears(equipmentAgeYears.setScale(2, RoundingMode.HALF_UP)); health.setMtbfHours(mtbf != null ? mtbf : BigDecimal.ZERO); health.setMttrHours(mttr != null ? mttr : BigDecimal.ZERO); @@ -108,7 +115,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { } LocalDateTime since = LocalDateTime.now().minusDays(days); - List failures = failureHistoryRepository.findByEquipmentIdSince(equipmentId, since); + List failures = + failureHistoryRepository.findByEquipmentIdSince(equipmentId, since); if (failures.isEmpty()) { return BigDecimal.ZERO; @@ -126,8 +134,9 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { } // MTBF = 运行时间 / 故障次数 - BigDecimal mtbf = BigDecimal.valueOf(totalHours) - .divide(BigDecimal.valueOf(failures.size()), 2, RoundingMode.HALF_UP); + BigDecimal mtbf = + BigDecimal.valueOf(totalHours) + .divide(BigDecimal.valueOf(failures.size()), 2, RoundingMode.HALF_UP); return mtbf; } @@ -139,22 +148,27 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { } LocalDateTime since = LocalDateTime.now().minusDays(days); - List repairedFailures = failureHistoryRepository - .findRepairedFailuresByEquipmentIdSince(equipmentId, since); + List repairedFailures = + failureHistoryRepository.findRepairedFailuresByEquipmentIdSince(equipmentId, since); if (repairedFailures.isEmpty()) { return BigDecimal.ZERO; } // 计算总修复时间 - double totalRepairHours = repairedFailures.stream() - .filter(f -> f.getRepairDurationHours() != null) - .mapToDouble(EquipmentFailureHistory::getRepairDurationHours) - .sum(); + double totalRepairHours = + repairedFailures.stream() + .filter(f -> f.getRepairDurationHours() != null) + .mapToDouble(EquipmentFailureHistory::getRepairDurationHours) + .sum(); // MTTR = 总修复时间 / 修复次数 - BigDecimal mttr = BigDecimal.valueOf(totalRepairHours) - .divide(BigDecimal.valueOf(repairedFailures.size()), 2, RoundingMode.HALF_UP); + BigDecimal mttr = + BigDecimal.valueOf(totalRepairHours) + .divide( + BigDecimal.valueOf(repairedFailures.size()), + 2, + RoundingMode.HALF_UP); return mttr; } @@ -162,34 +176,44 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { @Override @Transactional public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) { - // 校验设备存在 - Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId()) - .orElseThrow(() -> new BusinessException(6001, "设备不存在")); + Equipment equipment = + equipmentRepository + .findByIdAndIsDeletedFalse(failure.getEquipmentId()) + .orElseThrow(() -> new BusinessException(6001, "设备不存在")); - // 设置项目ID if (failure.getProjectId() == null) { failure.setProjectId(equipment.getProjectId()); } - // 设置设备信息 if (failure.getEquipmentName() == null) { failure.setEquipmentName(equipment.getEquipmentName()); } - // 计算修复时长 if (failure.getRepairStartTime() != null && failure.getRepairEndTime() != null) { - long minutes = ChronoUnit.MINUTES.between(failure.getRepairStartTime(), failure.getRepairEndTime()); + long minutes = + ChronoUnit.MINUTES.between( + failure.getRepairStartTime(), failure.getRepairEndTime()); double hours = minutes / 60.0; failure.setRepairDurationHours(hours); - // 计算停机时长(如果故障时间和修复开始时间不同) - if (failure.getFailureTime() != null && failure.getRepairStartTime().isAfter(failure.getFailureTime())) { - long downtimeMinutes = ChronoUnit.MINUTES.between(failure.getFailureTime(), failure.getRepairStartTime()); + if (failure.getFailureTime() != null + && failure.getRepairStartTime().isAfter(failure.getFailureTime())) { + long downtimeMinutes = + ChronoUnit.MINUTES.between( + failure.getFailureTime(), failure.getRepairStartTime()); failure.setDowntimeHours(downtimeMinutes / 60.0); } } - return failureHistoryRepository.save(failure); + EquipmentFailureHistory savedFailure = failureHistoryRepository.save(failure); + + eventPublisher.publishEvent(new EquipmentFailureRecordedEvent(this, savedFailure)); + log.info( + "发布设备故障记录事件: equipmentId={}, failureHistoryId={}", + savedFailure.getEquipmentId(), + savedFailure.getId()); + + return savedFailure; } @Override @@ -199,7 +223,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { @Override public EquipmentHealthScore getLatestHealthScore(UUID equipmentId) { - return healthScoreRepository.findLatestByEquipmentId(equipmentId) + return healthScoreRepository + .findLatestByEquipmentId(equipmentId) .orElseThrow(() -> new BusinessException(6003, "没有健康度记录")); } @@ -208,9 +233,7 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { return failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(equipmentId); } - /** - * 计算设备年龄(年) - */ + /** 计算设备年龄(年) */ private BigDecimal calculateEquipmentAge(Equipment equipment) { LocalDate installationDate = equipment.getInstallationDate(); if (installationDate == null) { @@ -226,6 +249,35 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { int years = period.getYears(); int months = period.getMonths(); - return BigDecimal.valueOf(years).add(BigDecimal.valueOf(months).divide(BigDecimal.valueOf(12), 2, RoundingMode.HALF_UP)); + return BigDecimal.valueOf(years) + .add( + BigDecimal.valueOf(months) + .divide(BigDecimal.valueOf(12), 2, RoundingMode.HALF_UP)); } -} \ No newline at end of file + + /** 计算维保完成率 */ + private BigDecimal calculateMaintenanceCompletionRate(UUID equipmentId) { + List tasks = + maintenanceTaskRepository.findByEquipmentId(equipmentId); + + if (tasks.isEmpty()) { + return BigDecimal.ONE; + } + + long completedCount = + tasks.stream() + .filter( + task -> + "COMPLETED".equals(task.status()) + || "VERIFIED".equals(task.status())) + .count(); + + return BigDecimal.valueOf(completedCount) + .divide(BigDecimal.valueOf(tasks.size()), 4, RoundingMode.HALF_UP); + } + + /** 根据维保完成率计算扣分 */ + private BigDecimal calculateMaintenanceDeduction(BigDecimal completionRate) { + return MAINTENANCE_DEDUCTION_FACTOR.multiply(BigDecimal.ONE.subtract(completionRate)); + } +} diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHvacServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHvacServiceImpl.java index ac9ecab..7416d90 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHvacServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentHvacServiceImpl.java @@ -3,13 +3,12 @@ package com.ether.pms.asset.service.impl; import com.ether.pms.asset.entity.EquipmentHvac; import com.ether.pms.asset.repository.EquipmentHvacRepository; import com.ether.pms.asset.service.EquipmentHvacService; +import java.util.Optional; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; -import java.util.UUID; - @Service @RequiredArgsConstructor public class EquipmentHvacServiceImpl implements EquipmentHvacService { diff --git a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentServiceImpl.java b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentServiceImpl.java index 082baa9..f0adc40 100644 --- a/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentServiceImpl.java +++ b/module-asset/src/main/java/com/ether/pms/asset/service/impl/EquipmentServiceImpl.java @@ -1,7 +1,5 @@ package com.ether.pms.asset.service.impl; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentType; @@ -9,6 +7,14 @@ import com.ether.pms.asset.enums.OwnershipType; import com.ether.pms.asset.enums.SystemType; import com.ether.pms.asset.repository.EquipmentRepository; import com.ether.pms.asset.service.EquipmentService; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; import lombok.RequiredArgsConstructor; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -16,14 +22,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; - @Service @RequiredArgsConstructor public class EquipmentServiceImpl implements EquipmentService { @@ -48,8 +46,10 @@ public class EquipmentServiceImpl implements EquipmentService { @Override @Transactional public Equipment updateEquipment(UUID id, Equipment equipment) { - Equipment existing = equipmentRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); + Equipment existing = + equipmentRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); updateFields(existing, equipment); return equipmentRepository.save(existing); } @@ -57,8 +57,10 @@ public class EquipmentServiceImpl implements EquipmentService { @Override @Transactional public void deleteEquipment(UUID id) { - Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); + Equipment equipment = + equipmentRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); equipment.setIsDeleted(true); equipment.setStatus(EquipmentStatus.INACTIVE); equipmentRepository.save(equipment); @@ -66,7 +68,8 @@ public class EquipmentServiceImpl implements EquipmentService { @Override public Equipment getEquipmentById(UUID id) { - return equipmentRepository.findByIdAndIsDeletedFalse(id) + return equipmentRepository + .findByIdAndIsDeletedFalse(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "设备不存在")); } @@ -120,7 +123,9 @@ public class EquipmentServiceImpl implements EquipmentService { } private String generateEquipmentCode() { - return "EQC-" + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + return "EQC-" + + LocalDateTime.now() + .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); } private void updateFields(Equipment existing, Equipment updated) { @@ -210,15 +215,31 @@ public class EquipmentServiceImpl implements EquipmentService { @Override public byte[] exportToExcel(UUID projectId) { - List equipmentList = equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId); + List equipmentList = + equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId); try (Workbook workbook = new XSSFWorkbook(); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { + ByteArrayOutputStream out = new ByteArrayOutputStream()) { Sheet sheet = workbook.createSheet("设备列表"); - String[] headers = {"设备编码", "设备名称", "设备类型", "系统类型", "归属类型", - "型号", "厂商", "额定功率(kW)", "额定电压(V)", "安装位置", - "维保商", "维保电话", "年检周期(月)", "购置日期", "购置价格", "保修到期"}; + String[] headers = { + "设备编码", + "设备名称", + "设备类型", + "系统类型", + "归属类型", + "型号", + "厂商", + "额定功率(kW)", + "额定电压(V)", + "安装位置", + "维保商", + "维保电话", + "年检周期(月)", + "购置日期", + "购置价格", + "保修到期" + }; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { Cell cell = headerRow.createCell(i); @@ -229,22 +250,64 @@ public class EquipmentServiceImpl implements EquipmentService { int rowNum = 1; for (Equipment eq : equipmentList) { Row row = sheet.createRow(rowNum++); - row.createCell(0).setCellValue(eq.getEquipmentCode() != null ? eq.getEquipmentCode() : ""); - row.createCell(1).setCellValue(eq.getEquipmentName() != null ? eq.getEquipmentName() : ""); - row.createCell(2).setCellValue(eq.getEquipmentType() != null ? eq.getEquipmentType().getDescription() : ""); - row.createCell(3).setCellValue(eq.getSystemType() != null ? eq.getSystemType().getDescription() : ""); - row.createCell(4).setCellValue(eq.getOwnershipType() != null ? eq.getOwnershipType().getDescription() : ""); + row.createCell(0) + .setCellValue(eq.getEquipmentCode() != null ? eq.getEquipmentCode() : ""); + row.createCell(1) + .setCellValue(eq.getEquipmentName() != null ? eq.getEquipmentName() : ""); + row.createCell(2) + .setCellValue( + eq.getEquipmentType() != null + ? eq.getEquipmentType().getDescription() + : ""); + row.createCell(3) + .setCellValue( + eq.getSystemType() != null + ? eq.getSystemType().getDescription() + : ""); + row.createCell(4) + .setCellValue( + eq.getOwnershipType() != null + ? eq.getOwnershipType().getDescription() + : ""); row.createCell(5).setCellValue(eq.getModel() != null ? eq.getModel() : ""); - row.createCell(6).setCellValue(eq.getManufacturer() != null ? eq.getManufacturer() : ""); - row.createCell(7).setCellValue(eq.getRatedPower() != null ? eq.getRatedPower().doubleValue() : 0); - row.createCell(8).setCellValue(eq.getRatedVoltage() != null ? eq.getRatedVoltage() : ""); - row.createCell(9).setCellValue(eq.getInstallationLocation() != null ? eq.getInstallationLocation() : ""); - row.createCell(10).setCellValue(eq.getMaintenanceVendor() != null ? eq.getMaintenanceVendor() : ""); - row.createCell(11).setCellValue(eq.getMaintenanceVendorPhone() != null ? eq.getMaintenanceVendorPhone() : ""); - row.createCell(12).setCellValue(eq.getInspectionCycle() != null ? eq.getInspectionCycle() : 0); - row.createCell(13).setCellValue(eq.getPurchaseDate() != null ? eq.getPurchaseDate().toString() : ""); - row.createCell(14).setCellValue(eq.getPurchasePrice() != null ? eq.getPurchasePrice().doubleValue() : 0); - row.createCell(15).setCellValue(eq.getWarrantyExpireDate() != null ? eq.getWarrantyExpireDate().toString() : ""); + row.createCell(6) + .setCellValue(eq.getManufacturer() != null ? eq.getManufacturer() : ""); + row.createCell(7) + .setCellValue( + eq.getRatedPower() != null ? eq.getRatedPower().doubleValue() : 0); + row.createCell(8) + .setCellValue(eq.getRatedVoltage() != null ? eq.getRatedVoltage() : ""); + row.createCell(9) + .setCellValue( + eq.getInstallationLocation() != null + ? eq.getInstallationLocation() + : ""); + row.createCell(10) + .setCellValue( + eq.getMaintenanceVendor() != null ? eq.getMaintenanceVendor() : ""); + row.createCell(11) + .setCellValue( + eq.getMaintenanceVendorPhone() != null + ? eq.getMaintenanceVendorPhone() + : ""); + row.createCell(12) + .setCellValue( + eq.getInspectionCycle() != null ? eq.getInspectionCycle() : 0); + row.createCell(13) + .setCellValue( + eq.getPurchaseDate() != null + ? eq.getPurchaseDate().toString() + : ""); + row.createCell(14) + .setCellValue( + eq.getPurchasePrice() != null + ? eq.getPurchasePrice().doubleValue() + : 0); + row.createCell(15) + .setCellValue( + eq.getWarrantyExpireDate() != null + ? eq.getWarrantyExpireDate().toString() + : ""); } for (int i = 0; i < headers.length; i++) { diff --git a/module-asset/src/test/java/com/ether/pms/asset/TestApplication.java b/module-asset/src/test/java/com/ether/pms/asset/TestApplication.java index 15ec8d4..5e508fa 100644 --- a/module-asset/src/test/java/com/ether/pms/asset/TestApplication.java +++ b/module-asset/src/test/java/com/ether/pms/asset/TestApplication.java @@ -3,5 +3,4 @@ package com.ether.pms.asset; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class TestApplication { -} \ No newline at end of file +public class TestApplication {} diff --git a/module-asset/src/test/java/com/ether/pms/asset/repository/EquipmentRepositoryTest.java b/module-asset/src/test/java/com/ether/pms/asset/repository/EquipmentRepositoryTest.java index 04c2c08..31aa1a5 100644 --- a/module-asset/src/test/java/com/ether/pms/asset/repository/EquipmentRepositoryTest.java +++ b/module-asset/src/test/java/com/ether/pms/asset/repository/EquipmentRepositoryTest.java @@ -1,10 +1,15 @@ package com.ether.pms.asset.repository; +import static org.junit.jupiter.api.Assertions.*; + import com.ether.pms.asset.TestApplication; import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.enums.EquipmentStatus; import com.ether.pms.asset.enums.EquipmentType; import com.ether.pms.asset.enums.OwnershipType; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -13,27 +18,19 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.context.ContextConfiguration; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; - /** * EquipmentRepository 测试类 - TDD方式 * - * 使用 @DataJpaTest 切片测试,只加载 JPA 相关组件 + *

使用 @DataJpaTest 切片测试,只加载 JPA 相关组件 */ @DataJpaTest @ContextConfiguration(classes = TestApplication.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class EquipmentRepositoryTest { - @Autowired - private TestEntityManager entityManager; + @Autowired private TestEntityManager entityManager; - @Autowired - private EquipmentRepository equipmentRepository; + @Autowired private EquipmentRepository equipmentRepository; private UUID projectId; private UUID spaceNodeId; @@ -82,7 +79,8 @@ class EquipmentRepositoryTest { entityManager.persist(testEquipment); entityManager.flush(); - Optional found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); + Optional found = + equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); assertTrue(found.isEmpty()); } @@ -92,7 +90,8 @@ class EquipmentRepositoryTest { entityManager.persist(testEquipment); entityManager.flush(); - Optional found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); + Optional found = + equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); assertTrue(found.isPresent()); assertFalse(found.get().getIsDeleted()); @@ -107,7 +106,8 @@ class EquipmentRepositoryTest { equipmentRepository.save(testEquipment); entityManager.flush(); - Optional found = equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); + Optional found = + equipmentRepository.findByIdAndIsDeletedFalse(testEquipment.getId()); assertTrue(found.isEmpty()); } @@ -130,7 +130,8 @@ class EquipmentRepositoryTest { entityManager.persist(equipment); entityManager.flush(); - List result = equipmentRepository.findByProjectIdAndIsDeletedFalse(UUID.randomUUID()); + List result = + equipmentRepository.findByProjectIdAndIsDeletedFalse(UUID.randomUUID()); assertTrue(result.isEmpty()); } @@ -147,7 +148,8 @@ class EquipmentRepositoryTest { entityManager.persist(hvac); entityManager.flush(); - List elevators = equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.ELEVATOR); + List elevators = + equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.ELEVATOR); assertEquals(1, elevators.size()); assertEquals(EquipmentType.ELEVATOR, elevators.get(0).getEquipmentType()); @@ -165,7 +167,8 @@ class EquipmentRepositoryTest { entityManager.persist(ownerOwned); entityManager.flush(); - List projectOwnedEquipments = equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT); + List projectOwnedEquipments = + equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT); assertEquals(1, projectOwnedEquipments.size()); assertEquals(OwnershipType.PROJECT, projectOwnedEquipments.get(0).getOwnershipType()); @@ -179,7 +182,8 @@ class EquipmentRepositoryTest { entityManager.persist(equipment1); entityManager.flush(); - List result = equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId); + List result = + equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId); assertEquals(1, result.size()); assertEquals(spaceNodeId, result.get(0).getSpaceNodeId()); @@ -235,4 +239,4 @@ class EquipmentRepositoryTest { equipment.setIsDeleted(false); return equipment; } -} \ No newline at end of file +} diff --git a/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentElevatorServiceTest.java b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentElevatorServiceTest.java new file mode 100644 index 0000000..ed3a3f7 --- /dev/null +++ b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentElevatorServiceTest.java @@ -0,0 +1,160 @@ +package com.ether.pms.asset.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.ether.pms.asset.entity.EquipmentElevator; +import com.ether.pms.asset.repository.EquipmentElevatorRepository; +import com.ether.pms.asset.service.impl.EquipmentElevatorServiceImpl; +import java.math.BigDecimal; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EquipmentElevatorServiceTest { + + @Mock private EquipmentElevatorRepository elevatorRepository; + + @InjectMocks private EquipmentElevatorServiceImpl elevatorService; + + private EquipmentElevator testElevator; + private UUID testId; + private UUID equipmentId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + equipmentId = UUID.randomUUID(); + testElevator = new EquipmentElevator(); + testElevator.setId(testId); + testElevator.setEquipmentId(equipmentId); + testElevator.setElevatorType("乘客电梯"); + testElevator.setElevatorModel("XYZ-1000"); + testElevator.setLoadCapacity(1000); + testElevator.setSpeed(new BigDecimal("2.5")); + testElevator.setFloorCount(20); + testElevator.setRegistrationNo("REG-001"); + testElevator.setMaintenanceLevel("A级"); + } + + @Test + void saveOrUpdate_shouldCreateNew_whenNotExists() { + when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.empty()); + when(elevatorRepository.save(any(EquipmentElevator.class))) + .thenAnswer( + invocation -> { + EquipmentElevator saved = invocation.getArgument(0); + saved.setId(testId); + return saved; + }); + + EquipmentElevator result = elevatorService.saveOrUpdate(testElevator); + + assertNotNull(result); + assertEquals(equipmentId, result.getEquipmentId()); + verify(elevatorRepository).save(testElevator); + } + + @Test + void saveOrUpdate_shouldUpdateExisting_whenExists() { + EquipmentElevator existing = new EquipmentElevator(); + existing.setId(testId); + existing.setEquipmentId(equipmentId); + + EquipmentElevator updateData = new EquipmentElevator(); + updateData.setEquipmentId(equipmentId); + updateData.setElevatorType("货梯"); + updateData.setLoadCapacity(2000); + + when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.of(existing)); + when(elevatorRepository.save(any(EquipmentElevator.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentElevator result = elevatorService.saveOrUpdate(updateData); + + assertNotNull(result); + assertEquals(testId, result.getId()); + assertEquals("货梯", result.getElevatorType()); + assertEquals(2000, result.getLoadCapacity()); + } + + @Test + void getByEquipmentId_shouldReturnElevator_whenExists() { + when(elevatorRepository.findByEquipmentId(equipmentId)) + .thenReturn(Optional.of(testElevator)); + + Optional result = elevatorService.getByEquipmentId(equipmentId); + + assertTrue(result.isPresent()); + assertEquals(testId, result.get().getId()); + assertEquals("乘客电梯", result.get().getElevatorType()); + } + + @Test + void getByEquipmentId_shouldReturnEmpty_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(elevatorRepository.findByEquipmentId(nonExistentId)).thenReturn(Optional.empty()); + + Optional result = elevatorService.getByEquipmentId(nonExistentId); + + assertFalse(result.isPresent()); + } + + @Test + void deleteByEquipmentId_shouldCallRepository() { + elevatorService.deleteByEquipmentId(equipmentId); + + verify(elevatorRepository).deleteByEquipmentId(equipmentId); + } + + @Test + void saveOrUpdate_shouldPreserveAllFields_whenUpdating() { + EquipmentElevator existing = new EquipmentElevator(); + existing.setId(testId); + existing.setEquipmentId(equipmentId); + existing.setElevatorType("旧类型"); + existing.setElevatorModel("旧型号"); + + EquipmentElevator updateData = new EquipmentElevator(); + updateData.setEquipmentId(equipmentId); + updateData.setElevatorType("乘客电梯"); + updateData.setElevatorModel("XYZ-1000"); + updateData.setLoadCapacity(1000); + updateData.setSpeed(new BigDecimal("2.5")); + updateData.setFloorCount(20); + updateData.setShaftDimensions("2m x 2m"); + updateData.setPitDepth(new BigDecimal("3.5")); + updateData.setOverheadHeight(new BigDecimal("4.0")); + updateData.setRegistrationNo("REG-001"); + updateData.setInspectionCertificate("CERT-001"); + updateData.setMaintenanceLevel("A级"); + updateData.setRescuePlan("紧急救援计划内容"); + + when(elevatorRepository.findByEquipmentId(equipmentId)).thenReturn(Optional.of(existing)); + when(elevatorRepository.save(any(EquipmentElevator.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentElevator result = elevatorService.saveOrUpdate(updateData); + + assertEquals(testId, result.getId()); + assertEquals("乘客电梯", result.getElevatorType()); + assertEquals("XYZ-1000", result.getElevatorModel()); + assertEquals(1000, result.getLoadCapacity()); + assertEquals(new BigDecimal("2.5"), result.getSpeed()); + assertEquals(20, result.getFloorCount()); + assertEquals("2m x 2m", result.getShaftDimensions()); + assertEquals(new BigDecimal("3.5"), result.getPitDepth()); + assertEquals(new BigDecimal("4.0"), result.getOverheadHeight()); + assertEquals("REG-001", result.getRegistrationNo()); + assertEquals("CERT-001", result.getInspectionCertificate()); + assertEquals("A级", result.getMaintenanceLevel()); + assertEquals("紧急救援计划内容", result.getRescuePlan()); + } +} diff --git a/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentHealthServiceTest.java b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentHealthServiceTest.java new file mode 100644 index 0000000..0ab33b7 --- /dev/null +++ b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentHealthServiceTest.java @@ -0,0 +1,379 @@ +package com.ether.pms.asset.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import com.ether.pms.asset.entity.Equipment; +import com.ether.pms.asset.entity.EquipmentFailureHistory; +import com.ether.pms.asset.entity.EquipmentHealthScore; +import com.ether.pms.asset.enums.EquipmentStatus; +import com.ether.pms.asset.enums.EquipmentType; +import com.ether.pms.asset.enums.OwnershipType; +import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository; +import com.ether.pms.asset.repository.EquipmentHealthScoreRepository; +import com.ether.pms.asset.repository.EquipmentRepository; +import com.ether.pms.asset.service.impl.EquipmentHealthServiceImpl; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.MaintenanceTaskRepository; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +@ExtendWith(MockitoExtension.class) +class EquipmentHealthServiceTest { + + @Mock private EquipmentFailureHistoryRepository failureHistoryRepository; + + @Mock private EquipmentHealthScoreRepository healthScoreRepository; + + @Mock private EquipmentRepository equipmentRepository; + + @Mock private MaintenanceTaskRepository maintenanceTaskRepository; + + @Mock private ApplicationEventPublisher eventPublisher; + + @InjectMocks private EquipmentHealthServiceImpl healthService; + + private Equipment testEquipment; + private UUID testId; + private UUID projectId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + projectId = UUID.randomUUID(); + testEquipment = new Equipment(); + testEquipment.setId(testId); + testEquipment.setProjectId(projectId); + testEquipment.setEquipmentCode("EQ-001"); + testEquipment.setEquipmentName("测试设备"); + testEquipment.setEquipmentType(EquipmentType.HVAC); + testEquipment.setOwnershipType(OwnershipType.PROJECT); + testEquipment.setStatus(EquipmentStatus.ACTIVE); + testEquipment.setInstallationDate(LocalDate.now().minusYears(2)); + } + + @Test + void calculateHealthScore_shouldCalculateCorrectly_withNoFailures() { + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + when(maintenanceTaskRepository.findByEquipmentId(testId)).thenReturn(List.of()); + when(failureHistoryRepository.countByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(0L); + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince( + eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + when(healthScoreRepository.save(any(EquipmentHealthScore.class))) + .thenAnswer( + invocation -> { + EquipmentHealthScore score = invocation.getArgument(0); + score.setId(UUID.randomUUID()); + return score; + }); + + EquipmentHealthScore result = healthService.calculateHealthScore(testId); + + assertNotNull(result); + assertEquals(0, result.getFailureCount30d()); + assertEquals(0, result.getFailureDeduction().intValue()); + assertTrue(result.getHealthScore().compareTo(BigDecimal.ZERO) > 0); + } + + @Test + void calculateHealthScore_shouldDeductForFailures() { + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + when(maintenanceTaskRepository.findByEquipmentId(testId)).thenReturn(List.of()); + when(failureHistoryRepository.countByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(3L); + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince( + eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + when(healthScoreRepository.save(any(EquipmentHealthScore.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentHealthScore result = healthService.calculateHealthScore(testId); + + assertNotNull(result); + assertEquals(3, result.getFailureCount30d()); + assertEquals(new BigDecimal("15.00"), result.getFailureDeduction()); + } + + @Test + void calculateHealthScore_shouldThrowException_whenEquipmentNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + BusinessException exception = + assertThrows( + BusinessException.class, + () -> healthService.calculateHealthScore(nonExistentId)); + + assertEquals(6001, exception.getCode()); + } + + @Test + void calculateMTBF_shouldReturnZero_whenNoFailures() { + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + + BigDecimal result = healthService.calculateMTBF(testId, 30); + + assertEquals(BigDecimal.ZERO, result); + } + + @Test + void calculateMTBF_shouldCalculateCorrectly_withMultipleFailures() { + LocalDateTime now = LocalDateTime.now(); + EquipmentFailureHistory failure1 = createFailure(testId, now.minusDays(5)); + EquipmentFailureHistory failure2 = createFailure(testId, now.minusDays(15)); + EquipmentFailureHistory failure3 = createFailure(testId, now.minusDays(25)); + + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(failure1, failure2, failure3)); + + BigDecimal result = healthService.calculateMTBF(testId, 30); + + assertNotNull(result); + assertTrue(result.compareTo(BigDecimal.ZERO) > 0); + } + + @Test + void calculateMTBF_shouldUseDefaultDays_whenDaysIsNull() { + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + + BigDecimal result = healthService.calculateMTBF(testId, null); + + assertEquals(BigDecimal.ZERO, result); + } + + @Test + void calculateMTBF_shouldUseDefaultDays_whenDaysIsNegative() { + when(failureHistoryRepository.findByEquipmentIdSince(eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + + BigDecimal result = healthService.calculateMTBF(testId, -5); + + assertEquals(BigDecimal.ZERO, result); + } + + @Test + void calculateMTTR_shouldReturnZero_whenNoRepairedFailures() { + when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince( + eq(testId), any(LocalDateTime.class))) + .thenReturn(List.of()); + + BigDecimal result = healthService.calculateMTTR(testId, 30); + + assertEquals(BigDecimal.ZERO, result); + } + + @Test + void calculateMTTR_shouldCalculateCorrectly_withRepairedFailures() { + EquipmentFailureHistory failure1 = createRepairedFailure(testId, 2.0); + EquipmentFailureHistory failure2 = createRepairedFailure(testId, 4.0); + + when(failureHistoryRepository.findRepairedFailuresByEquipmentIdSince( + eq(testId), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(failure1, failure2)); + + BigDecimal result = healthService.calculateMTTR(testId, 30); + + assertNotNull(result); + assertEquals(new BigDecimal("3.00"), result); + } + + @Test + void recordFailure_shouldSetProjectId_whenNull() { + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setEquipmentId(testId); + failure.setFailureTime(LocalDateTime.now()); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + doNothing().when(eventPublisher).publishEvent(any()); + when(failureHistoryRepository.save(any(EquipmentFailureHistory.class))) + .thenAnswer( + invocation -> { + EquipmentFailureHistory saved = invocation.getArgument(0); + saved.setId(UUID.randomUUID()); + return saved; + }); + + EquipmentFailureHistory result = healthService.recordFailure(failure); + + assertNotNull(result); + assertEquals(projectId, result.getProjectId()); + } + + @Test + void recordFailure_shouldSetEquipmentName_whenNull() { + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setEquipmentId(testId); + failure.setFailureTime(LocalDateTime.now()); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + doNothing().when(eventPublisher).publishEvent(any()); + when(failureHistoryRepository.save(any(EquipmentFailureHistory.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentFailureHistory result = healthService.recordFailure(failure); + + assertEquals("测试设备", result.getEquipmentName()); + } + + @Test + void recordFailure_shouldCalculateRepairDuration() { + LocalDateTime repairStart = LocalDateTime.now().minusHours(2); + LocalDateTime repairEnd = LocalDateTime.now(); + + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setEquipmentId(testId); + failure.setFailureTime(LocalDateTime.now().minusHours(3)); + failure.setRepairStartTime(repairStart); + failure.setRepairEndTime(repairEnd); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + doNothing().when(eventPublisher).publishEvent(any()); + when(failureHistoryRepository.save(any(EquipmentFailureHistory.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentFailureHistory result = healthService.recordFailure(failure); + + assertNotNull(result.getRepairDurationHours()); + assertTrue(result.getRepairDurationHours() >= 1.9); + } + + @Test + void recordFailure_shouldCalculateDowntime() { + LocalDateTime failureTime = LocalDateTime.now().minusHours(1); + LocalDateTime repairStart = LocalDateTime.now(); + + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setEquipmentId(testId); + failure.setFailureTime(failureTime); + failure.setRepairStartTime(repairStart); + failure.setRepairEndTime(repairStart.plusHours(2)); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + doNothing().when(eventPublisher).publishEvent(any()); + when(failureHistoryRepository.save(any(EquipmentFailureHistory.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + EquipmentFailureHistory result = healthService.recordFailure(failure); + + assertNotNull(result.getDowntimeHours()); + assertTrue(result.getDowntimeHours() >= 0.9); + } + + @Test + void recordFailure_shouldThrowException_whenEquipmentNotFound() { + UUID nonExistentId = UUID.randomUUID(); + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setEquipmentId(nonExistentId); + + when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + BusinessException exception = + assertThrows(BusinessException.class, () -> healthService.recordFailure(failure)); + + assertEquals(6001, exception.getCode()); + } + + @Test + void getHealthHistory_shouldReturnList() { + List history = + Arrays.asList(new EquipmentHealthScore(), new EquipmentHealthScore()); + when(healthScoreRepository.findByEquipmentIdOrderByCalculatedAtDesc(testId)) + .thenReturn(history); + + List result = healthService.getHealthHistory(testId); + + assertEquals(2, result.size()); + } + + @Test + void getLatestHealthScore_shouldReturnLatest() { + EquipmentHealthScore latest = new EquipmentHealthScore(); + latest.setId(UUID.randomUUID()); + latest.setHealthScore(new BigDecimal("85.00")); + + when(healthScoreRepository.findLatestByEquipmentId(testId)).thenReturn(Optional.of(latest)); + + EquipmentHealthScore result = healthService.getLatestHealthScore(testId); + + assertNotNull(result); + assertEquals(new BigDecimal("85.00"), result.getHealthScore()); + } + + @Test + void getLatestHealthScore_shouldThrowException_whenNoRecords() { + when(healthScoreRepository.findLatestByEquipmentId(testId)).thenReturn(Optional.empty()); + + BusinessException exception = + assertThrows( + BusinessException.class, () -> healthService.getLatestHealthScore(testId)); + + assertEquals(6003, exception.getCode()); + } + + @Test + void getFailureHistory_shouldReturnList() { + List history = + Arrays.asList(new EquipmentFailureHistory(), new EquipmentFailureHistory()); + when(failureHistoryRepository.findByEquipmentIdOrderByFailureTimeDesc(testId)) + .thenReturn(history); + + List result = healthService.getFailureHistory(testId); + + assertEquals(2, result.size()); + } + + private EquipmentFailureHistory createFailure(UUID equipmentId, LocalDateTime failureTime) { + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setId(UUID.randomUUID()); + failure.setEquipmentId(equipmentId); + failure.setFailureTime(failureTime); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + return failure; + } + + private EquipmentFailureHistory createRepairedFailure(UUID equipmentId, double repairHours) { + EquipmentFailureHistory failure = new EquipmentFailureHistory(); + failure.setId(UUID.randomUUID()); + failure.setEquipmentId(equipmentId); + failure.setFailureTime(LocalDateTime.now().minusDays(1)); + failure.setRepairStartTime(LocalDateTime.now().minusDays(1)); + failure.setRepairEndTime(LocalDateTime.now()); + failure.setRepairDurationHours(repairHours); + failure.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + return failure; + } +} diff --git a/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentServiceTest.java b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentServiceTest.java new file mode 100644 index 0000000..9096f67 --- /dev/null +++ b/module-asset/src/test/java/com/ether/pms/asset/service/EquipmentServiceTest.java @@ -0,0 +1,369 @@ +package com.ether.pms.asset.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.ether.pms.asset.entity.Equipment; +import com.ether.pms.asset.enums.EquipmentStatus; +import com.ether.pms.asset.enums.EquipmentType; +import com.ether.pms.asset.enums.OwnershipType; +import com.ether.pms.asset.enums.SystemType; +import com.ether.pms.asset.repository.EquipmentRepository; +import com.ether.pms.asset.service.impl.EquipmentServiceImpl; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EquipmentServiceTest { + + @Mock private EquipmentRepository equipmentRepository; + + @InjectMocks private EquipmentServiceImpl equipmentService; + + private Equipment testEquipment; + private UUID testId; + private UUID projectId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + projectId = UUID.randomUUID(); + testEquipment = new Equipment(); + testEquipment.setId(testId); + testEquipment.setProjectId(projectId); + testEquipment.setEquipmentCode("EQ-001"); + testEquipment.setEquipmentName("测试设备"); + testEquipment.setEquipmentType(EquipmentType.HVAC); + testEquipment.setOwnershipType(OwnershipType.PROJECT); + testEquipment.setStatus(EquipmentStatus.ACTIVE); + } + + @Test + void createEquipment_shouldGenerateCode_whenCodeIsNull() { + testEquipment.setEquipmentCode(null); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer( + invocation -> { + Equipment saved = invocation.getArgument(0); + saved.setId(testId); + return saved; + }); + + Equipment result = equipmentService.createEquipment(testEquipment); + + assertNotNull(result); + assertNotNull(result.getEquipmentCode()); + assertTrue(result.getEquipmentCode().startsWith("EQC-")); + verify(equipmentRepository).save(any(Equipment.class)); + } + + @Test + void createEquipment_shouldSetDefaultStatus_whenStatusIsNull() { + testEquipment.setStatus(null); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + Equipment result = equipmentService.createEquipment(testEquipment); + + assertEquals(EquipmentStatus.ACTIVE, result.getStatus()); + } + + @Test + void createEquipment_shouldSetDefaultOwnershipType_whenOwnershipTypeIsNull() { + testEquipment.setOwnershipType(null); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + Equipment result = equipmentService.createEquipment(testEquipment); + + assertEquals(OwnershipType.PROJECT, result.getOwnershipType()); + } + + @Test + void createEquipment_shouldSaveEquipment_whenValid() { + when(equipmentRepository.save(any(Equipment.class))).thenReturn(testEquipment); + + Equipment result = equipmentService.createEquipment(testEquipment); + + assertNotNull(result); + assertEquals("EQ-001", result.getEquipmentCode()); + verify(equipmentRepository).save(testEquipment); + } + + @Test + void getEquipmentById_shouldReturnEquipment_whenExists() { + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + + Equipment result = equipmentService.getEquipmentById(testId); + + assertNotNull(result); + assertEquals(testId, result.getId()); + assertEquals("EQ-001", result.getEquipmentCode()); + } + + @Test + void getEquipmentById_shouldThrowException_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + BusinessException exception = + assertThrows( + BusinessException.class, + () -> equipmentService.getEquipmentById(nonExistentId)); + + assertEquals(ErrorCode.NOT_FOUND.getCode(), exception.getCode()); + } + + @Test + void updateEquipment_shouldUpdateFields_whenEquipmentExists() { + Equipment updateData = new Equipment(); + updateData.setEquipmentName("更新后的设备名称"); + updateData.setEquipmentType(EquipmentType.ELEVATOR); + updateData.setModel("NewModel"); + updateData.setManufacturer("NewManufacturer"); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + Equipment result = equipmentService.updateEquipment(testId, updateData); + + assertEquals("更新后的设备名称", result.getEquipmentName()); + assertEquals(EquipmentType.ELEVATOR, result.getEquipmentType()); + assertEquals("NewModel", result.getModel()); + assertEquals("NewManufacturer", result.getManufacturer()); + } + + @Test + void updateEquipment_shouldThrowException_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + assertThrows( + BusinessException.class, + () -> equipmentService.updateEquipment(nonExistentId, new Equipment())); + } + + @Test + void deleteEquipment_shouldSetDeletedFlag_whenExists() { + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + equipmentService.deleteEquipment(testId); + + assertTrue(testEquipment.getIsDeleted()); + assertEquals(EquipmentStatus.INACTIVE, testEquipment.getStatus()); + verify(equipmentRepository).save(testEquipment); + } + + @Test + void deleteEquipment_shouldThrowException_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(equipmentRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + assertThrows( + BusinessException.class, () -> equipmentService.deleteEquipment(nonExistentId)); + } + + @Test + void getEquipmentsByProject_shouldReturnList() { + List equipmentList = Arrays.asList(testEquipment); + when(equipmentRepository.findByProjectIdAndIsDeletedFalse(projectId)) + .thenReturn(equipmentList); + + List result = equipmentService.getEquipmentsByProject(projectId); + + assertEquals(1, result.size()); + assertEquals(testEquipment, result.get(0)); + } + + @Test + void getEquipmentsBySpaceNode_shouldReturnList() { + UUID spaceNodeId = UUID.randomUUID(); + testEquipment.setSpaceNodeId(spaceNodeId); + List equipmentList = Arrays.asList(testEquipment); + when(equipmentRepository.findBySpaceNodeIdAndIsDeletedFalse(spaceNodeId)) + .thenReturn(equipmentList); + + List result = equipmentService.getEquipmentsBySpaceNode(spaceNodeId); + + assertEquals(1, result.size()); + } + + @Test + void getEquipmentsByType_shouldReturnList() { + List equipmentList = Arrays.asList(testEquipment); + when(equipmentRepository.findByEquipmentTypeAndIsDeletedFalse(EquipmentType.HVAC)) + .thenReturn(equipmentList); + + List result = equipmentService.getEquipmentsByType(EquipmentType.HVAC); + + assertEquals(1, result.size()); + } + + @Test + void getEquipmentsByOwnership_shouldReturnList() { + List equipmentList = Arrays.asList(testEquipment); + when(equipmentRepository.findByOwnershipTypeAndIsDeletedFalse(OwnershipType.PROJECT)) + .thenReturn(equipmentList); + + List result = equipmentService.getEquipmentsByOwnership(OwnershipType.PROJECT); + + assertEquals(1, result.size()); + } + + @Test + void getEquipmentStatsByType_shouldReturnStatsMap() { + Object[] stat1 = new Object[] {EquipmentType.HVAC, 5L}; + Object[] stat2 = new Object[] {EquipmentType.ELEVATOR, 3L}; + when(equipmentRepository.countByType(projectId)).thenReturn(Arrays.asList(stat1, stat2)); + + Map result = equipmentService.getEquipmentStatsByType(projectId); + + assertEquals(2, result.size()); + assertEquals(5L, result.get("HVAC")); + assertEquals(3L, result.get("ELEVATOR")); + } + + @Test + void getEquipmentStatsByOwnership_shouldReturnStatsMap() { + Object[] stat1 = new Object[] {OwnershipType.PROJECT, 10L}; + Object[] stat2 = new Object[] {OwnershipType.OWNER, 5L}; + when(equipmentRepository.countByOwnership(projectId)) + .thenReturn(Arrays.asList(stat1, stat2)); + + Map result = equipmentService.getEquipmentStatsByOwnership(projectId); + + assertEquals(2, result.size()); + assertEquals(10L, result.get("PROJECT")); + assertEquals(5L, result.get("OWNER")); + } + + @Test + void countByProject_shouldReturnCount() { + when(equipmentRepository.countByProject(projectId)).thenReturn(15L); + + long result = equipmentService.countByProject(projectId); + + assertEquals(15L, result); + } + + @Test + void deleteEquipmentBatch_shouldDeleteMultipleEquipments() { + UUID id1 = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + Equipment eq1 = new Equipment(); + eq1.setId(id1); + Equipment eq2 = new Equipment(); + eq2.setId(id2); + + when(equipmentRepository.findByIdAndIsDeletedFalse(id1)).thenReturn(Optional.of(eq1)); + when(equipmentRepository.findByIdAndIsDeletedFalse(id2)).thenReturn(Optional.of(eq2)); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + equipmentService.deleteEquipmentBatch(Arrays.asList(id1, id2)); + + assertTrue(eq1.getIsDeleted()); + assertTrue(eq2.getIsDeleted()); + verify(equipmentRepository, times(2)).save(any(Equipment.class)); + } + + @Test + void updateEquipment_shouldUpdateAllFields() { + Equipment updateData = new Equipment(); + updateData.setEquipmentName("New Name"); + updateData.setEquipmentType(EquipmentType.FIRE_PROTECTION); + updateData.setSystemType(SystemType.FIRE_PROTECTION); + updateData.setOwnershipType(OwnershipType.OWNER); + updateData.setOwningEntityId(UUID.randomUUID()); + updateData.setOwningEntityName("Owner Entity"); + updateData.setAssetCode("ASSET-001"); + updateData.setSerialNumber("SN-001"); + updateData.setModel("Model-X"); + updateData.setManufacturer("Manufacturer-X"); + updateData.setSupplier("Supplier-X"); + updateData.setStatus(EquipmentStatus.MAINTENANCE); + updateData.setOperationStatus("running"); + updateData.setInstallationLocation("1F"); + updateData.setInstallationDate(LocalDate.of(2023, 1, 1)); + updateData.setDesignLifeYears(20); + updateData.setRatedPower(new BigDecimal("100.50")); + updateData.setRatedVoltage("220V"); + updateData.setRatedCurrent(new BigDecimal("50.25")); + updateData.setMaintenanceVendor("Vendor-X"); + updateData.setMaintenanceVendorContact("Contact-X"); + updateData.setMaintenanceVendorPhone("123456"); + updateData.setMaintenanceContractNo("CONTRACT-001"); + updateData.setMaintenanceContractStart(LocalDate.of(2023, 1, 1)); + updateData.setMaintenanceContractEnd(LocalDate.of(2024, 1, 1)); + updateData.setEnergyConsumptionStandard(new BigDecimal("80.00")); + updateData.setInspectionCycle(12); + updateData.setNextInspectionDate(LocalDate.of(2024, 6, 1)); + updateData.setLastInspectionDate(LocalDate.of(2023, 6, 1)); + updateData.setLastInspectionResult("合格"); + updateData.setSpecialEquipmentType("特种设备"); + updateData.setSpecialEquipmentCert("CERT-001"); + updateData.setRemarks("测试备注"); + + when(equipmentRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testEquipment)); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + Equipment result = equipmentService.updateEquipment(testId, updateData); + + assertEquals("New Name", result.getEquipmentName()); + assertEquals(EquipmentType.FIRE_PROTECTION, result.getEquipmentType()); + assertEquals(SystemType.FIRE_PROTECTION, result.getSystemType()); + assertEquals(OwnershipType.OWNER, result.getOwnershipType()); + assertEquals("ASSET-001", result.getAssetCode()); + assertEquals("SN-001", result.getSerialNumber()); + assertEquals("Model-X", result.getModel()); + assertEquals("Manufacturer-X", result.getManufacturer()); + assertEquals("Supplier-X", result.getSupplier()); + assertEquals(EquipmentStatus.MAINTENANCE, result.getStatus()); + assertEquals("running", result.getOperationStatus()); + assertEquals("1F", result.getInstallationLocation()); + assertEquals(LocalDate.of(2023, 1, 1), result.getInstallationDate()); + assertEquals(20, result.getDesignLifeYears()); + assertEquals(new BigDecimal("100.50"), result.getRatedPower()); + assertEquals("220V", result.getRatedVoltage()); + assertEquals(new BigDecimal("50.25"), result.getRatedCurrent()); + assertEquals("Vendor-X", result.getMaintenanceVendor()); + assertEquals("Contact-X", result.getMaintenanceVendorContact()); + assertEquals("123456", result.getMaintenanceVendorPhone()); + assertEquals("CONTRACT-001", result.getMaintenanceContractNo()); + assertEquals(LocalDate.of(2023, 1, 1), result.getMaintenanceContractStart()); + assertEquals(LocalDate.of(2024, 1, 1), result.getMaintenanceContractEnd()); + assertEquals(new BigDecimal("80.00"), result.getEnergyConsumptionStandard()); + assertEquals(12, result.getInspectionCycle()); + assertEquals(LocalDate.of(2024, 6, 1), result.getNextInspectionDate()); + assertEquals(LocalDate.of(2023, 6, 1), result.getLastInspectionDate()); + assertEquals("合格", result.getLastInspectionResult()); + assertEquals("特种设备", result.getSpecialEquipmentType()); + assertEquals("CERT-001", result.getSpecialEquipmentCert()); + assertEquals("测试备注", result.getRemarks()); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/annotation/OperationLog.java b/module-auth/src/main/java/com/ether/pms/auth/annotation/OperationLog.java index c95fc10..a85bff2 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/annotation/OperationLog.java +++ b/module-auth/src/main/java/com/ether/pms/auth/annotation/OperationLog.java @@ -1,60 +1,38 @@ package com.ether.pms.auth.annotation; import com.ether.pms.auth.entity.AuditLog; - import java.lang.annotation.*; -/** - * 审计日志注解 - * 用于标记需要记录审计日志的方法 - */ +/** 审计日志注解 用于标记需要记录审计日志的方法 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OperationLog { - /** - * 操作描述 - */ + /** 操作描述 */ String operation(); - /** - * 功能模块 - */ + /** 功能模块 */ String module(); - /** - * 操作类型 - */ + /** 操作类型 */ AuditLog.ActionType action() default AuditLog.ActionType.UPDATE; - /** - * 目标对象类型(支持SpEL表达式) - */ + /** 目标对象类型(支持SpEL表达式) */ String targetType() default ""; - /** - * 目标对象ID(支持SpEL表达式) - */ + /** 目标对象ID(支持SpEL表达式) */ String targetId() default ""; - /** - * 操作内容(支持SpEL表达式) - */ + /** 操作内容(支持SpEL表达式) */ String content() default ""; - /** - * 是否记录请求参数 - */ + /** 是否记录请求参数 */ boolean recordParams() default true; - /** - * 是否记录响应结果 - */ + /** 是否记录响应结果 */ boolean recordResult() default false; - /** - * 需要过滤的敏感字段(不记录) - */ + /** 需要过滤的敏感字段(不记录) */ String[] sensitiveFields() default {"password", "token", "secret", "creditCard"}; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/aspect/AuditLogAspect.java b/module-auth/src/main/java/com/ether/pms/auth/aspect/AuditLogAspect.java index 9b349e0..767f997 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/aspect/AuditLogAspect.java +++ b/module-auth/src/main/java/com/ether/pms/auth/aspect/AuditLogAspect.java @@ -1,13 +1,17 @@ package com.ether.pms.auth.aspect; import com.ether.pms.auth.annotation.OperationLog; -import com.ether.pms.auth.entity.AuditLog.ActionType; import com.ether.pms.auth.entity.AuditLog.Status; import com.ether.pms.auth.service.AuditLogService; import com.ether.pms.auth.util.SecurityUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; @@ -18,12 +22,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - @Aspect @Component @RequiredArgsConstructor @@ -111,7 +109,8 @@ public class AuditLogAspect { } private HttpServletRequest getRequest() { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { return attributes.getRequest(); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/config/AsyncConfig.java b/module-auth/src/main/java/com/ether/pms/auth/config/AsyncConfig.java index 83d1e93..8ce47b0 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/config/AsyncConfig.java +++ b/module-auth/src/main/java/com/ether/pms/auth/config/AsyncConfig.java @@ -1,19 +1,16 @@ package com.ether.pms.auth.config; +import java.util.concurrent.Executor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import java.util.concurrent.Executor; - @Configuration @EnableAsync public class AsyncConfig { - /** - * 审计日志异步执行器 - */ + /** 审计日志异步执行器 */ @Bean("auditLogExecutor") public Executor auditLogExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); @@ -21,7 +18,8 @@ public class AsyncConfig { executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setThreadNamePrefix("audit-log-"); - executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); + executor.setRejectedExecutionHandler( + new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java b/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java index 77e41ce..ccbd472 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java +++ b/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java @@ -6,6 +6,8 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -15,25 +17,21 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.List; - @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -53,26 +51,34 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) - .securityContext(context -> context - .securityContextRepository(securityContextRepository()) - ) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/login", "/api/auth/logout", "/api/auth/refresh").permitAll() - // 根据配置动态控制 Swagger/API 文档访问 - // 开发环境:允许所有用户访问(便于开发调试) - // 生产环境:即使 URL 匹配,springdoc 也会返回 404(因为已禁用) - .requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**").permitAll() - .requestMatchers("/actuator/**").hasRole("ADMIN") // ✅ 需要管理员权限 - .anyRequest().authenticated() - ) - .exceptionHandling(ex -> ex - .authenticationEntryPoint(unauthorizedEntryPoint()) - ) - .addFilterBefore(jwtAuthenticationFilter(), AuthorizationFilter.class); + http.csrf(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) + .securityContext( + context -> context.securityContextRepository(securityContextRepository())) + .authorizeHttpRequests( + auth -> + auth.requestMatchers( + "/api/auth/login", + "/api/auth/logout", + "/api/auth/refresh") + .permitAll() + // 根据配置动态控制 Swagger/API 文档访问 + // 开发环境:允许所有用户访问(便于开发调试) + // 生产环境:即使 URL 匹配,springdoc 也会返回 404(因为已禁用) + .requestMatchers( + "/api-docs/**", + "/swagger-ui/**", + "/swagger-ui.html", + "/v3/api-docs/**") + .permitAll() + .requestMatchers("/actuator/**") + .hasRole("ADMIN") // ✅ 需要管理员权限 + .anyRequest() + .authenticated()) + .exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedEntryPoint())) + .addFilterBefore(jwtAuthenticationFilter(), AuthorizationFilter.class); return http.build(); } @@ -80,24 +86,24 @@ public class SecurityConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(List.of( - "http://127.0.0.1:5173", - "http://127.0.0.1:5174", - "http://127.0.0.1:5175", - "http://127.0.0.1:5176", - "http://127.0.0.1:5177", - "http://127.0.0.1:5178", - "http://127.0.0.1:5179", - "http://127.0.0.1:5180", - "http://localhost:5173", - "http://localhost:5174", - "http://localhost:5175", - "http://localhost:5176", - "http://localhost:5177", - "http://localhost:5178", - "http://localhost:5179", - "http://localhost:5180" - )); + configuration.setAllowedOrigins( + List.of( + "http://127.0.0.1:5173", + "http://127.0.0.1:5174", + "http://127.0.0.1:5175", + "http://127.0.0.1:5176", + "http://127.0.0.1:5177", + "http://127.0.0.1:5178", + "http://127.0.0.1:5179", + "http://127.0.0.1:5180", + "http://localhost:5173", + "http://localhost:5174", + "http://localhost:5175", + "http://localhost:5176", + "http://localhost:5177", + "http://localhost:5178", + "http://localhost:5179", + "http://localhost:5180")); configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true); @@ -125,17 +131,21 @@ public class SecurityConfig { public OncePerRequestFilter jwtAuthenticationFilter() { return new OncePerRequestFilter() { @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { String username = jwtTokenProvider.getUsernameFromToken(token); - List authorities = jwtTokenProvider.getAuthoritiesFromToken(token); + List authorities = + jwtTokenProvider.getAuthoritiesFromToken(token); UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken(username, token, authorities); + new UsernamePasswordAuthenticationToken(username, token, authorities); SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authentication); @@ -159,4 +169,4 @@ public class SecurityConfig { } return null; } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/AuditLogController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/AuditLogController.java index eb1cf3a..c135293 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/AuditLogController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/AuditLogController.java @@ -4,6 +4,11 @@ import com.ether.pms.auth.entity.AuditLog; import com.ether.pms.auth.service.AuditLogService; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.util.PaginationValidator; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -14,12 +19,6 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - @RestController @RequestMapping("/api/audit-logs") @RequiredArgsConstructor @@ -29,9 +28,7 @@ public class AuditLogController { private final AuditLogService auditLogService; - /** - * 查询审计日志列表(最近30天) - */ + /** 查询审计日志列表(最近30天) */ @GetMapping public ApiResponse> list( @RequestParam(defaultValue = "0") int page, @@ -39,57 +36,53 @@ public class AuditLogController { @RequestParam(required = false) String module, @RequestParam(required = false) String action, @RequestParam(required = false) String username, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate) { + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + LocalDateTime endDate) { // 使用 PaginationValidator 校验分页参数(防止 OOM 和数据库过载) int safeSize = PaginationValidator.getSafeSize(size); Pageable pageable = PageRequest.of(page, safeSize, Sort.by("createdAt").descending()); - Page result = auditLogService.searchLogs(module, action, username, startDate, endDate, pageable); + Page result = + auditLogService.searchLogs(module, action, username, startDate, endDate, pageable); return ApiResponse.success(result); } - /** - * 获取模块列表(用于筛选) - */ + /** 获取模块列表(用于筛选) */ @GetMapping("/modules") public ApiResponse>> getModules() { - List> modules = Arrays.asList( - Map.of("value", "USER", "label", "用户管理"), - Map.of("value", "ROLE", "label", "角色管理"), - Map.of("value", "PERMISSION", "label", "权限管理"), - Map.of("value", "PROJECT", "label", "项目管理"), - Map.of("value", "AUTH", "label", "登录认证") - ); + List> modules = + Arrays.asList( + Map.of("value", "USER", "label", "用户管理"), + Map.of("value", "ROLE", "label", "角色管理"), + Map.of("value", "PERMISSION", "label", "权限管理"), + Map.of("value", "PROJECT", "label", "项目管理"), + Map.of("value", "AUTH", "label", "登录认证")); return ApiResponse.success(modules); } - /** - * 获取操作类型列表(用于筛选) - */ + /** 获取操作类型列表(用于筛选) */ @GetMapping("/actions") public ApiResponse>> getActions() { - List> actions = Arrays.stream(AuditLog.ActionType.values()) - .map(action -> Map.of( - "value", action.name(), - "label", action.getDesc() - )) - .collect(Collectors.toList()); + List> actions = + Arrays.stream(AuditLog.ActionType.values()) + .map( + action -> + Map.of( + "value", action.name(), + "label", action.getDesc())) + .collect(Collectors.toList()); return ApiResponse.success(actions); } - /** - * 获取最近30天的日志统计 - */ + /** 获取最近30天的日志统计 */ @GetMapping("/stats") public ApiResponse> getStats() { long count = auditLogService.getRecentLogCount(); - return ApiResponse.success(Map.of( - "total", count, - "retentionDays", 30, - "description", "最近30天的审计日志数量" - )); + return ApiResponse.success( + Map.of("total", count, "retentionDays", 30, "description", "最近30天的审计日志数量")); } } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java index 42b9b1b..aec9ac4 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java @@ -7,86 +7,86 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.util.HashMap; +import java.util.Map; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; -import java.util.Map; - @RestController @RequestMapping("/api/auth") @RequiredArgsConstructor @Validated public class AuthController { - + private final LoginService loginService; private final JwtTokenProvider jwtTokenProvider; - + @PostMapping("/login") public ResponseEntity>> login( - @Valid @RequestBody LoginRequest request, - HttpServletRequest httpRequest) { - + @Valid @RequestBody LoginRequest request, HttpServletRequest httpRequest) { + String ip = getClientIp(httpRequest); - Map result = loginService.login(request.getUsername(), request.getPassword(), ip); + Map result = + loginService.login(request.getUsername(), request.getPassword(), ip); return ResponseEntity.ok(ApiResponse.success(result)); } - + @PostMapping("/logout") - public ResponseEntity> logout(@RequestHeader(value = "Authorization", required = false) String token) { + public ResponseEntity> logout( + @RequestHeader(value = "Authorization", required = false) String token) { return ResponseEntity.ok(ApiResponse.success()); } - + @GetMapping("/me") public ResponseEntity>> getCurrentUser( @RequestHeader(value = "Authorization", required = false) String token) { - + if (token == null || !token.startsWith("Bearer ")) { return ResponseEntity.ok(ApiResponse.error(401, "未授权")); } - + String jwt = token.substring(7); if (!jwtTokenProvider.validateToken(jwt)) { return ResponseEntity.ok(ApiResponse.error(401, "Token无效")); } - + String username = jwtTokenProvider.getUsernameFromToken(jwt); Map result = new HashMap<>(); result.put("username", username); - + return ResponseEntity.ok(ApiResponse.success(result)); } - + @PostMapping("/refresh") public ResponseEntity>> refreshToken( @RequestHeader(value = "Authorization", required = false) String token) { - + if (token == null || !token.startsWith("Bearer ")) { return ResponseEntity.ok(ApiResponse.error(401, "未授权")); } - + String jwt = token.substring(7); if (!jwtTokenProvider.validateToken(jwt)) { return ResponseEntity.ok(ApiResponse.error(401, "Token无效")); } - + if (jwtTokenProvider.isTokenExpired(jwt)) { return ResponseEntity.ok(ApiResponse.error(401, "Token已过期")); } - + String username = jwtTokenProvider.getUsernameFromToken(jwt); - String newToken = jwtTokenProvider.generateToken( - jwtTokenProvider.getUserIdFromToken(jwt), username); - + String newToken = + jwtTokenProvider.generateToken(jwtTokenProvider.getUserIdFromToken(jwt), username); + Map result = new HashMap<>(); result.put("token", newToken); - + return ResponseEntity.ok(ApiResponse.success(result)); } - + private String getClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { @@ -97,7 +97,7 @@ public class AuthController { } return ip; } - + @Data public static class LoginRequest { @NotBlank(message = "用户名不能为空") diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/DataAccessController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/DataAccessController.java deleted file mode 100644 index e4b354d..0000000 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/DataAccessController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ether.pms.auth.controller; - -import com.ether.pms.auth.controller.dto.DataAccessRequest; -import com.ether.pms.auth.entity.DataAccess; -import com.ether.pms.auth.service.DataAccessService; -import com.ether.pms.auth.util.SecurityUtils; -import com.ether.pms.common.ApiResponse; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - -@RestController -@RequestMapping("/api/data-access") -@RequiredArgsConstructor -@Validated -public class DataAccessController { - - private final DataAccessService dataAccessService; - - @Deprecated - @PostMapping - public ResponseEntity> grantAccess(@Valid @RequestBody DataAccessRequest request) { - UUID currentUserId = SecurityUtils.getCurrentUserId(); - if (currentUserId == null) { - return ResponseEntity.status(401).body(ApiResponse.error(401, "未登录")); - } - dataAccessService.grantAccess( - request.getDataType(), - request.getDataId(), - request.getAccessType(), - request.getAccessId(), - request.getAccessLevel(), - currentUserId - ); - return ResponseEntity.ok(ApiResponse.success()); - } - - @Deprecated - @DeleteMapping("/{id}") - public ResponseEntity> revokeAccess(@PathVariable UUID id) { - dataAccessService.revokeAccess(id); - return ResponseEntity.ok(ApiResponse.success()); - } - - @Deprecated - @GetMapping - public ResponseEntity>> getDataAccess( - @RequestParam String dataType, - @RequestParam UUID dataId) { - return ResponseEntity.ok(ApiResponse.success(dataAccessService.getDataAccess(dataType, dataId))); - } -} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/DeptController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/DeptController.java index 1445196..38343a8 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/DeptController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/DeptController.java @@ -10,18 +10,17 @@ import com.ether.pms.auth.entity.User; import com.ether.pms.auth.service.DeptService; import com.ether.pms.common.ApiResponse; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - /** * 部门管理控制器 * - *

提供部门相关的RESTful API接口,包括部门树查询、创建部门、获取部门成员等功能。

+ *

提供部门相关的RESTful API接口,包括部门树查询、创建部门、获取部门成员等功能。 * * @author Ether开发团队 * @version 1.0.0 @@ -35,9 +34,7 @@ public class DeptController { private final DeptService deptService; - /** - * 获取部门树 - */ + /** 获取部门树 */ @GetMapping("/tree") public ApiResponse> getDeptTree() { List depts = deptService.getDeptTree(); @@ -45,27 +42,19 @@ public class DeptController { return ApiResponse.success(tree); } - /** - * 获取所有启用的部门列表 - */ + /** 获取所有启用的部门列表 */ @GetMapping public ApiResponse> getAllDepts() { return ApiResponse.success(deptService.getActiveDepts()); } - /** - * 根据ID获取部门 - */ + /** 根据ID获取部门 */ @GetMapping("/{id}") public ApiResponse getById(@PathVariable UUID id) { - return deptService.getById(id) - .map(ApiResponse::success) - .orElse(ApiResponse.error("部门不存在")); + return deptService.getById(id).map(ApiResponse::success).orElse(ApiResponse.error("部门不存在")); } - /** - * 创建部门 - */ + /** 创建部门 */ @PostMapping @OperationLog(operation = "创建部门", module = "DEPT", action = AuditLog.ActionType.CREATE) public ApiResponse createDept(@RequestBody @Valid DeptDTO dto) { @@ -79,9 +68,7 @@ public class DeptController { return ApiResponse.success(deptService.createDept(dept)); } - /** - * 更新部门 - */ + /** 更新部门 */ @PutMapping("/{id}") @OperationLog(operation = "更新部门", module = "DEPT", action = AuditLog.ActionType.UPDATE) public ApiResponse updateDept(@PathVariable UUID id, @RequestBody @Valid DeptDTO dto) { @@ -95,9 +82,7 @@ public class DeptController { return ApiResponse.success(deptService.updateDept(id, dept)); } - /** - * 删除部门 - */ + /** 删除部门 */ @DeleteMapping("/{id}") @OperationLog(operation = "删除部门", module = "DEPT", action = AuditLog.ActionType.DELETE) public ApiResponse deleteDept(@PathVariable UUID id) { @@ -105,18 +90,15 @@ public class DeptController { return ApiResponse.success(); } - /** - * 获取部门成员 - */ + /** 获取部门成员 */ @GetMapping("/{deptId}/members") public ApiResponse> getDeptMembers(@PathVariable UUID deptId) { List members = deptService.getDeptEmployees(deptId); - return ApiResponse.success(members.stream().map(UserVO::fromEntity).collect(Collectors.toList())); + return ApiResponse.success( + members.stream().map(UserVO::fromEntity).collect(Collectors.toList())); } - /** - * 根据部门类型查询部门 - */ + /** 根据部门类型查询部门 */ @GetMapping("/by-type/{deptType}") public ApiResponse> getByType(@PathVariable String deptType) { return ApiResponse.success(deptService.getByType(deptType)); @@ -124,13 +106,16 @@ public class DeptController { private List buildDeptTree(List depts, UUID parentId) { return depts.stream() - .filter(d -> (parentId == null && d.getParentId() == null) || - (parentId != null && parentId.equals(d.getParentId()))) - .map(d -> { - DeptVO vo = DeptVO.fromEntity(d); - vo.setChildren(buildDeptTree(depts, d.getId())); - return vo; - }) + .filter( + d -> + (parentId == null && d.getParentId() == null) + || (parentId != null && parentId.equals(d.getParentId()))) + .map( + d -> { + DeptVO vo = DeptVO.fromEntity(d); + vo.setChildren(buildDeptTree(depts, d.getId())); + return vo; + }) .collect(Collectors.toList()); } } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java index 67be55b..3c28173 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java @@ -2,35 +2,36 @@ package com.ether.pms.auth.controller; import com.ether.pms.auth.entity.Permission; import com.ether.pms.auth.service.PermissionService; +import com.ether.pms.auth.util.SecurityUtils; +import com.ether.pms.auth.vo.MenuVO; +import com.ether.pms.auth.vo.PermissionVO; import com.ether.pms.common.ApiResponse; +import com.ether.pms.common.util.PaginationValidator; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import com.ether.pms.common.util.PaginationValidator; - /** * 权限管理REST接口控制器 * - *

提供权限(Permission)相关的HTTP API接口,包括权限的增删改查、类型筛选、菜单权限获取等功能。 - * 所有接口均遵循RESTful设计规范,返回统一的ApiResponse格式。

+ *

提供权限(Permission)相关的HTTP API接口,包括权限的增删改查、类型筛选、菜单权限获取等功能。 所有接口均遵循RESTful设计规范,返回统一的ApiResponse格式。 + * + *

主要接口: * - *

主要接口:

*
    - *
  • GET /api/auth/permissions - 查询所有权限
  • - *
  • GET /api/auth/permissions/{id} - 根据ID查询权限
  • - *
  • GET /api/auth/permissions/type/{type} - 根据类型查询权限
  • - *
  • GET /api/auth/permissions/menus - 查询所有菜单权限
  • - *
  • POST /api/auth/permissions - 创建权限
  • - *
  • PUT /api/auth/permissions/{id} - 更新权限
  • - *
  • DELETE /api/auth/permissions/{id} - 删除权限
  • + *
  • GET /api/auth/permissions - 查询所有权限 + *
  • GET /api/auth/permissions/{id} - 根据ID查询权限 + *
  • GET /api/auth/permissions/type/{type} - 根据类型查询权限 + *
  • GET /api/auth/permissions/menus - 查询所有菜单权限 + *
  • POST /api/auth/permissions - 创建权限 + *
  • PUT /api/auth/permissions/{id} - 更新权限 + *
  • DELETE /api/auth/permissions/{id} - 删除权限 *
* * @author Ether开发团队 @@ -75,7 +76,7 @@ public class PermissionController { /** * 根据权限类型查询权限列表 * - *

按权限类型筛选,如MENU、BUTTON、API等。

+ *

按权限类型筛选,如MENU、BUTTON、API等。 * * @param type 权限类型 * @return 包含该类型权限列表的响应 @@ -88,46 +89,48 @@ public class PermissionController { /** * 查询所有菜单权限 * - *

获取type为MENU的所有权限,通常用于前端菜单渲染。

+ *

获取type为MENU的所有权限,通常用于管理员查看所有菜单配置。 * * @return 包含菜单权限列表的响应 */ - @GetMapping("/menus") - public ResponseEntity>> findMenus() { + @GetMapping("/all-menus") + public ResponseEntity>> findAllMenus() { return ResponseEntity.ok(ApiResponse.success(permissionService.findMenuPermissions())); } /** * 创建新权限 * - *

创建一个新的权限。

+ *

创建一个新的权限。 * * @param permission 要创建的权限信息(请求体) * @return 包含创建后权限信息的响应 */ @PostMapping - public ResponseEntity> create(@Valid @RequestBody Permission permission) { + public ResponseEntity> create( + @Valid @RequestBody Permission permission) { return ResponseEntity.ok(ApiResponse.success(permissionService.create(permission))); } /** * 更新权限信息 * - *

更新指定权限的信息。

+ *

更新指定权限的信息。 * * @param id 要更新的权限ID * @param permission 包含新数据的权限信息(请求体) * @return 包含更新后权限信息的响应 */ @PutMapping("/{id}") - public ResponseEntity> update(@PathVariable UUID id, @Valid @RequestBody Permission permission) { + public ResponseEntity> update( + @PathVariable UUID id, @Valid @RequestBody Permission permission) { return ResponseEntity.ok(ApiResponse.success(permissionService.update(id, permission))); } /** * 删除权限 * - *

删除指定的权限。

+ *

删除指定的权限。 * * @param id 要删除的权限ID * @return 空响应(表示操作成功) @@ -137,4 +140,36 @@ public class PermissionController { permissionService.delete(id); return ResponseEntity.ok(ApiResponse.success()); } -} \ No newline at end of file + + /** + * 获取当前用户菜单 + * + *

返回当前用户有权限访问的菜单,用于前端动态渲染菜单。 + * + * @param projectId 项目ID(可选) + * @return 用户菜单列表 + */ + @GetMapping("/menus") + public ResponseEntity>> getUserMenus( + @RequestParam(required = false) UUID projectId) { + UUID userId = SecurityUtils.getCurrentUserId(); + return ResponseEntity.ok( + ApiResponse.success(permissionService.getUserMenus(userId, projectId))); + } + + /** + * 获取当前用户权限 + * + *

返回当前用户的所有权限标识,用于按钮级别权限控制。 + * + * @param projectId 项目ID(可选) + * @return 用户权限标识列表 + */ + @GetMapping("/permissions") + public ResponseEntity> getUserPermissions( + @RequestParam(required = false) UUID projectId) { + UUID userId = SecurityUtils.getCurrentUserId(); + return ResponseEntity.ok( + ApiResponse.success(permissionService.getUserPermissions(userId, projectId))); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionTreeController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionTreeController.java new file mode 100644 index 0000000..b312c29 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionTreeController.java @@ -0,0 +1,24 @@ +package com.ether.pms.auth.controller; + +import com.ether.pms.auth.service.PermissionService; +import com.ether.pms.auth.vo.PermissionTreeVO; +import com.ether.pms.common.ApiResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/permissions") +@RequiredArgsConstructor +public class PermissionTreeController { + + private final PermissionService permissionService; + + @GetMapping("/tree") + public ResponseEntity>> getPermissionTree() { + return ResponseEntity.ok(ApiResponse.success(permissionService.getPermissionTree())); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/ProjectMemberController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/ProjectMemberController.java index a0ac97a..0c1c753 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/ProjectMemberController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/ProjectMemberController.java @@ -1,19 +1,5 @@ package com.ether.pms.auth.controller; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - import com.ether.pms.auth.annotation.OperationLog; import com.ether.pms.auth.controller.dto.AddProjectMemberDTO; import com.ether.pms.auth.controller.dto.PageResponse; @@ -25,16 +11,27 @@ import com.ether.pms.auth.entity.User; import com.ether.pms.auth.service.UserManagementService; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.util.PaginationValidator; - import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; /** * 项目成员管理控制器 * - *

提供项目成员相关的RESTful API接口,用于管理项目成员列表、添加成员、移除成员等功能。

+ *

提供项目成员相关的RESTful API接口,用于管理项目成员列表、添加成员、移除成员等功能。 * - *

所有接口路径前缀为/api/auth/projects/{projectId}/members。

+ *

所有接口路径前缀为/api/auth/projects/{projectId}/members。 * * @author Ether开发团队 * @version 1.0.0 @@ -52,7 +49,7 @@ public class ProjectMemberController { /** * 查询项目成员列表 * - *

分页返回指定项目下的所有成员信息,包含角色信息。

+ *

分页返回指定项目下的所有成员信息,包含角色信息。 * * @param projectId 项目唯一标识符 * @param page 页码,从1开始 @@ -74,17 +71,19 @@ public class ProjectMemberController { if (start < 0) start = 0; if (start > staffList.size()) start = staffList.size(); int end = Math.min(start + safeSize, staffList.size()); - List pageData = staffList.subList(start, end).stream() - .map(ProjectMemberVO::fromEntity) - .collect(Collectors.toList()); - PageResponse response = new PageResponse<>(pageData, (long) staffList.size(), pageIndex + 1, size); + List pageData = + staffList.subList(start, end).stream() + .map(ProjectMemberVO::fromEntity) + .collect(Collectors.toList()); + PageResponse response = + new PageResponse<>(pageData, (long) staffList.size(), pageIndex + 1, size); return ApiResponse.success(response); } /** * 获取可添加到项目的成员列表(企业员工) * - *

返回尚未加入该项目的所有企业员工列表,支持模糊搜索用户。

+ *

返回尚未加入该项目的所有企业员工列表,支持模糊搜索用户。 * * @param projectId 项目唯一标识符 * @param search 搜索关键字(可选),支持用户名和真实姓名模糊匹配 @@ -92,60 +91,65 @@ public class ProjectMemberController { */ @GetMapping("/{projectId}/available-members") public ApiResponse> getAvailableMembers( - @PathVariable UUID projectId, - @RequestParam(required = false) String search) { + @PathVariable UUID projectId, @RequestParam(required = false) String search) { List users = userManagementService.findEnterpriseUsers(); // 支持模糊搜索 if (search != null && !search.isBlank()) { String keyword = search.toLowerCase(); - users = users.stream() - .filter(u -> u.getUsername().toLowerCase().contains(keyword) - || (u.getRealName() != null && u.getRealName().toLowerCase().contains(keyword))) - .collect(Collectors.toList()); + users = + users.stream() + .filter( + u -> + u.getUsername().toLowerCase().contains(keyword) + || (u.getRealName() != null + && u.getRealName() + .toLowerCase() + .contains(keyword))) + .collect(Collectors.toList()); } - return ApiResponse.success(users.stream() - .map(UserVO::fromEntity) - .collect(Collectors.toList())); + return ApiResponse.success( + users.stream().map(UserVO::fromEntity).collect(Collectors.toList())); } /** * 添加项目成员 * - *

将指定用户添加到项目中,并分配多个角色。

+ *

将指定用户添加到项目中,并分配多个角色。 * * @param projectId 项目唯一标识符 * @param dto 添加项目成员的请求数据 * @return 成功响应 */ @PostMapping("/{projectId}/members") - @OperationLog(operation = "添加项目成员", module = "PROJECT_MEMBER", action = AuditLog.ActionType.CREATE) + @OperationLog( + operation = "添加项目成员", + module = "PROJECT_MEMBER", + action = AuditLog.ActionType.CREATE) public ApiResponse addProjectMember( - @PathVariable UUID projectId, - @Valid @RequestBody AddProjectMemberDTO dto) { + @PathVariable UUID projectId, @Valid @RequestBody AddProjectMemberDTO dto) { userManagementService.assignStaffToProject( - dto.getUserId(), - projectId, - dto.getStaffType(), - dto.getRoleIds()); + dto.getUserId(), projectId, dto.getStaffType(), dto.getRoleIds()); return ApiResponse.success(); } /** * 移除项目成员 * - *

将指定用户从项目中移除。

+ *

将指定用户从项目中移除。 * * @param projectId 项目唯一标识符 * @param userId 用户唯一标识符 * @return 成功响应 */ @DeleteMapping("/{projectId}/members/{userId}") - @OperationLog(operation = "移除项目成员", module = "PROJECT_MEMBER", action = AuditLog.ActionType.DELETE) + @OperationLog( + operation = "移除项目成员", + module = "PROJECT_MEMBER", + action = AuditLog.ActionType.DELETE) public ApiResponse removeProjectMember( - @PathVariable UUID projectId, - @PathVariable UUID userId) { + @PathVariable UUID projectId, @PathVariable UUID userId) { userManagementService.removeStaffFromProject(userId, projectId); return ApiResponse.success(); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java index e59d9db..543aa91 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java @@ -7,37 +7,34 @@ import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.User; import com.ether.pms.auth.service.RoleService; import com.ether.pms.common.ApiResponse; +import com.ether.pms.common.util.PaginationValidator; import jakarta.validation.Valid; +import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import com.ether.pms.common.util.PaginationValidator; - /** * 角色管理REST接口控制器 * - *

提供角色(Role)相关的HTTP API接口,包括角色的增删改查、权限分配、用户关联等功能。 - * 所有接口均遵循RESTful设计规范,返回统一的ApiResponse格式。

+ *

提供角色(Role)相关的HTTP API接口,包括角色的增删改查、权限分配、用户关联等功能。 所有接口均遵循RESTful设计规范,返回统一的ApiResponse格式。 + * + *

主要接口: * - *

主要接口:

*
    - *
  • GET /api/auth/roles - 查询所有角色
  • - *
  • GET /api/auth/roles/{id} - 根据ID查询角色
  • - *
  • GET /api/auth/roles/project/{projectId} - 根据项目ID查询角色
  • - *
  • POST /api/auth/roles - 创建角色
  • - *
  • PUT /api/auth/roles/{id} - 更新角色
  • - *
  • DELETE /api/auth/roles/{id} - 删除角色
  • - *
  • POST /api/auth/roles/{id}/permissions - 为角色分配权限
  • - *
  • GET /api/auth/roles/{id}/permissions - 获取角色的权限
  • - *
  • GET /api/auth/roles/{id}/users - 获取拥有某角色的用户
  • + *
  • GET /api/auth/roles - 查询所有角色 + *
  • GET /api/auth/roles/{id} - 根据ID查询角色 + *
  • GET /api/auth/roles/project/{projectId} - 根据项目ID查询角色 + *
  • POST /api/auth/roles - 创建角色 + *
  • PUT /api/auth/roles/{id} - 更新角色 + *
  • DELETE /api/auth/roles/{id} - 删除角色 + *
  • POST /api/auth/roles/{id}/permissions - 为角色分配权限 + *
  • GET /api/auth/roles/{id}/permissions - 获取角色的权限 + *
  • GET /api/auth/roles/{id}/users - 获取拥有某角色的用户 *
* * @author Ether开发团队 @@ -82,7 +79,7 @@ public class RoleController { /** * 根据项目ID查询角色列表 * - *

获取指定项目下的所有角色。

+ *

获取指定项目下的所有角色。 * * @param projectId 项目ID * @return 包含该项目角色列表的响应 @@ -95,7 +92,7 @@ public class RoleController { /** * 创建新角色 * - *

创建一个新的角色,记录审计日志。

+ *

创建一个新的角色,记录审计日志。 * * @param role 要创建的角色信息(请求体) * @return 包含创建后角色信息的响应 @@ -109,7 +106,7 @@ public class RoleController { /** * 更新角色信息 * - *

更新指定角色的信息,记录审计日志。

+ *

更新指定角色的信息,记录审计日志。 * * @param id 要更新的角色ID * @param role 包含新数据的角色信息(请求体) @@ -117,14 +114,15 @@ public class RoleController { */ @PutMapping("/{id}") @OperationLog(operation = "更新角色", module = "ROLE", action = AuditLog.ActionType.UPDATE) - public ResponseEntity> update(@PathVariable UUID id, @Valid @RequestBody Role role) { + public ResponseEntity> update( + @PathVariable UUID id, @Valid @RequestBody Role role) { return ResponseEntity.ok(ApiResponse.success(roleService.update(id, role))); } /** * 删除角色 * - *

删除指定的角色,记录审计日志。

+ *

删除指定的角色,记录审计日志。 * * @param id 要删除的角色ID * @return 空响应(表示操作成功) @@ -139,7 +137,7 @@ public class RoleController { /** * 为角色分配权限 * - *

替换角色原有的所有权限,记录审计日志。

+ *

替换角色原有的所有权限,记录审计日志。 * * @param id 角色ID * @param permissionIds 要分配的权限ID列表(请求体) @@ -148,8 +146,7 @@ public class RoleController { @PostMapping("/{id}/permissions") @OperationLog(operation = "分配权限", module = "ROLE", action = AuditLog.ActionType.ASSIGN) public ResponseEntity> assignPermissions( - @PathVariable UUID id, - @Valid @RequestBody List permissionIds) { + @PathVariable UUID id, @Valid @RequestBody List permissionIds) { roleService.assignPermissions(id, permissionIds); return ResponseEntity.ok(ApiResponse.success()); } @@ -157,7 +154,7 @@ public class RoleController { /** * 获取角色的所有权限 * - *

查询指定角色关联的所有权限。

+ *

查询指定角色关联的所有权限。 * * @param id 角色ID * @return 包含角色权限列表的响应 @@ -170,7 +167,7 @@ public class RoleController { /** * 获取拥有某角色的所有用户 * - *

查询所有拥有指定角色ID的用户。

+ *

查询所有拥有指定角色ID的用户。 * * @param id 角色ID * @return 包含用户列表的响应 @@ -179,4 +176,4 @@ public class RoleController { public ResponseEntity>> getUsersByRoleId(@PathVariable UUID id) { return ResponseEntity.ok(ApiResponse.success(roleService.getUsersByRoleId(id))); } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/SysConfigController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/SysConfigController.java index dd64283..03da759 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/SysConfigController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/SysConfigController.java @@ -7,19 +7,15 @@ import com.ether.pms.auth.service.SysConfigService; import com.ether.pms.common.ApiResponse; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; +import java.util.Map; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Map; -import java.util.UUID; - /** - * 系统配置控制器 - * 提供系统配置的 RESTful API 接口 - * 包括配置的查询、更新等操作 + * 系统配置控制器 提供系统配置的 RESTful API 接口 包括配置的查询、更新等操作 * * @author Ether Team * @since 1.0.0 @@ -30,14 +26,11 @@ import java.util.UUID; @Validated public class SysConfigController { - /** - * 系统配置服务对象 - */ + /** 系统配置服务对象 */ private final SysConfigService sysConfigService; /** - * 获取所有配置项 - * 返回系统中所有配置的键值对 + * 获取所有配置项 返回系统中所有配置的键值对 * * @return 配置键值对 Map */ @@ -58,43 +51,38 @@ public class SysConfigController { } /** - * 更新单个配置项 - * 根据配置键更新对应的配置值,并记录审计日志 + * 更新单个配置项 根据配置键更新对应的配置值,并记录审计日志 * * @param configKey 配置键 - * @param request 更新请求对象,包含新的配置值 + * @param request 更新请求对象,包含新的配置值 * @return 更新后的配置项实体 */ @PutMapping("/{configKey}") @OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE) public ResponseEntity> updateConfig( - @PathVariable String configKey, - @Valid @RequestBody ConfigUpdateRequest request) { - return ResponseEntity.ok(ApiResponse.success(sysConfigService.updateConfig(configKey, request.getConfigValue()))); + @PathVariable String configKey, @Valid @RequestBody ConfigUpdateRequest request) { + return ResponseEntity.ok( + ApiResponse.success( + sysConfigService.updateConfig(configKey, request.getConfigValue()))); } /** - * 批量更新配置项 - * 同时更新多个配置键值对,并记录审计日志 + * 批量更新配置项 同时更新多个配置键值对,并记录审计日志 * * @param configs 配置键值对 Map * @return 更新后的配置键值对 Map */ @PutMapping @OperationLog(operation = "更新系统设置", module = "SYSTEM", action = AuditLog.ActionType.UPDATE) - public ResponseEntity>> updateConfigs(@RequestBody Map configs) { + public ResponseEntity>> updateConfigs( + @RequestBody Map configs) { return ResponseEntity.ok(ApiResponse.success(sysConfigService.updateConfigs(configs))); } - /** - * 配置更新请求对象 - * 用于接收单个配置项的更新请求 - */ + /** 配置更新请求对象 用于接收单个配置项的更新请求 */ @Data public static class ConfigUpdateRequest { - /** - * 新的配置值 - */ + /** 新的配置值 */ @NotBlank(message = "配置值不能为空") private String configValue; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java index fcd39f0..aee5187 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java @@ -11,29 +11,27 @@ import com.ether.pms.auth.service.UserProjectService; import com.ether.pms.auth.service.UserService; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.util.BatchOperationValidator; +import com.ether.pms.common.util.PaginationValidator; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.Data; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import com.ether.pms.common.util.PaginationValidator; - /** * 用户管理控制器 * - *

提供用户相关的RESTful API接口,包括用户CRUD、密码管理、角色分配、项目关联等功能。

+ *

提供用户相关的RESTful API接口,包括用户CRUD、密码管理、角色分配、项目关联等功能。 * - *

所有接口路径前缀为/api/auth/users,使用JSON格式进行数据交互。

+ *

所有接口路径前缀为/api/auth/users,使用JSON格式进行数据交互。 * * @author Ether开发团队 * @version 1.0.0 @@ -47,15 +45,17 @@ public class UserController { /** 用户服务 */ private final UserService userService; + /** 用户项目关联服务 */ private final UserProjectService userProjectService; + /** 用户管理服务 */ private final UserManagementService userManagementService; /** * 分页查询用户列表 * - *

返回系统中所有用户的信息,包含关联的角色列表,支持分页。

+ *

返回系统中所有用户的信息,包含关联的角色列表,支持分页。 * * @param page 页码,从0开始,默认为0 * @param size 每页大小,默认为10 @@ -72,7 +72,7 @@ public class UserController { /** * 根据ID获取用户信息 * - *

根据用户唯一标识符查询指定用户的详细信息。

+ *

根据用户唯一标识符查询指定用户的详细信息。 * * @param id 用户唯一标识符 * @return 包含用户信息的成功响应 @@ -85,7 +85,7 @@ public class UserController { /** * 创建新用户 * - *

创建一个新的用户账号,需要提供用户名、密码等基本信息。

+ *

创建一个新的用户账号,需要提供用户名、密码等基本信息。 * * @param user 待创建的用户信息 * @return 包含创建成功的用户信息的成功响应 @@ -99,7 +99,7 @@ public class UserController { /** * 更新用户信息 * - *

更新指定用户的个人信息,如真实姓名、手机号、邮箱等。

+ *

更新指定用户的个人信息,如真实姓名、手机号、邮箱等。 * * @param id 用户唯一标识符 * @param user 包含更新信息的用户对象 @@ -107,14 +107,15 @@ public class UserController { */ @PutMapping("/{id}") @OperationLog(operation = "更新用户", module = "USER", action = AuditLog.ActionType.UPDATE) - public ResponseEntity> update(@PathVariable UUID id, @Valid @RequestBody User user) { + public ResponseEntity> update( + @PathVariable UUID id, @Valid @RequestBody User user) { return ResponseEntity.ok(ApiResponse.success(userService.update(id, user))); } /** * 删除用户 * - *

根据用户ID删除指定用户账号。

+ *

根据用户ID删除指定用户账号。 * * @param id 用户唯一标识符 * @return 成功响应 @@ -129,7 +130,7 @@ public class UserController { /** * 修改用户密码 * - *

用户修改自己的密码,需要提供原密码和新密码。

+ *

用户修改自己的密码,需要提供原密码和新密码。 * * @param id 用户唯一标识符 * @param request 包含原密码和新密码的请求对象 @@ -138,8 +139,7 @@ public class UserController { @PutMapping("/{id}/password") @OperationLog(operation = "修改密码", module = "USER", action = AuditLog.ActionType.UPDATE) public ResponseEntity> updatePassword( - @PathVariable UUID id, - @Valid @RequestBody PasswordRequest request) { + @PathVariable UUID id, @Valid @RequestBody PasswordRequest request) { userService.updatePassword(id, request.getOldPassword(), request.getNewPassword()); return ResponseEntity.ok(ApiResponse.success()); } @@ -147,7 +147,7 @@ public class UserController { /** * 分配用户角色 * - *

为指定用户分配一个或多个角色,替换现有的角色。

+ *

为指定用户分配一个或多个角色,替换现有的角色。 * * @param id 用户唯一标识符 * @param roleIds 要分配的角色ID列表 @@ -156,8 +156,7 @@ public class UserController { @PostMapping("/{id}/roles") @OperationLog(operation = "分配角色", module = "USER", action = AuditLog.ActionType.ASSIGN) public ResponseEntity> assignRoles( - @PathVariable UUID id, - @Valid @RequestBody List roleIds) { + @PathVariable UUID id, @Valid @RequestBody List roleIds) { BatchOperationValidator.validateAssignmentSize(roleIds.size()); userService.assignRoles(id, roleIds); return ResponseEntity.ok(ApiResponse.success()); @@ -166,7 +165,7 @@ public class UserController { /** * 获取用户参与的项目列表 * - *

查询指定用户参与的所有项目及其在项目中的角色。

+ *

查询指定用户参与的所有项目及其在项目中的角色。 * * @param id 用户唯一标识符 * @return 包含用户参与项目列表的成功响应 @@ -179,7 +178,7 @@ public class UserController { /** * 添加用户到项目 * - *

将指定用户添加到某个项目中,并设置用户在项目中的角色。

+ *

将指定用户添加到某个项目中,并设置用户在项目中的角色。 * * @param id 用户唯一标识符 * @param request 包含项目ID和角色信息的请求对象 @@ -187,8 +186,7 @@ public class UserController { */ @PostMapping("/{id}/projects") public ResponseEntity> addUserToProject( - @PathVariable UUID id, - @Valid @RequestBody UserProjectRequest request) { + @PathVariable UUID id, @Valid @RequestBody UserProjectRequest request) { userProjectService.addUserToProject(id, request.getProjectId(), request.getRoleInProject()); return ResponseEntity.ok(ApiResponse.success()); } @@ -196,7 +194,7 @@ public class UserController { /** * 从项目中移除用户 * - *

将指定用户从某个项目中移除,删除用户与项目的关联关系。

+ *

将指定用户从某个项目中移除,删除用户与项目的关联关系。 * * @param id 用户唯一标识符 * @param projectId 项目唯一标识符 @@ -204,8 +202,7 @@ public class UserController { */ @DeleteMapping("/{id}/projects/{projectId}") public ResponseEntity> removeUserFromProject( - @PathVariable UUID id, - @PathVariable UUID projectId) { + @PathVariable UUID id, @PathVariable UUID projectId) { userProjectService.removeUserFromProject(id, projectId); return ResponseEntity.ok(ApiResponse.success()); } @@ -213,20 +210,22 @@ public class UserController { /** * 获取企业员工列表(用于项目管理添加成员) * - *

返回所有企业员工的用户信息列表。

+ *

返回所有企业员工的用户信息列表。 * * @return 包含企业员工列表的成功响应 */ @GetMapping("/enterprise") public ResponseEntity>> getEnterpriseUsers() { List users = userManagementService.findEnterpriseUsers(); - return ResponseEntity.ok(ApiResponse.success(users.stream().map(UserVO::fromEntity).collect(Collectors.toList()))); + return ResponseEntity.ok( + ApiResponse.success( + users.stream().map(UserVO::fromEntity).collect(Collectors.toList()))); } /** * 密码修改请求对象 * - *

包含修改密码所需的原密码和新密码。

+ *

包含修改密码所需的原密码和新密码。 */ @Data public static class PasswordRequest { @@ -240,4 +239,4 @@ public class UserController { @Size(min = 8, max = 128, message = "新密码长度必须在8-128位之间") private String newPassword; } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/AddProjectMemberDTO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/AddProjectMemberDTO.java index 1e6e87d..07de6bc 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/AddProjectMemberDTO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/AddProjectMemberDTO.java @@ -1,15 +1,14 @@ package com.ether.pms.auth.controller.dto; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.UUID; - -import jakarta.validation.constraints.NotNull; import lombok.Data; /** * 添加项目成员请求DTO * - *

用于接收将用户添加到项目的请求数据。

+ *

用于接收将用户添加到项目的请求数据。 * * @author Ether开发团队 * @version 1.0.0 @@ -18,10 +17,7 @@ import lombok.Data; @Data public class AddProjectMemberDTO { - /** - * 用户ID - * 要添加到项目的用户唯一标识符 - */ + /** 用户ID 要添加到项目的用户唯一标识符 */ @NotNull(message = "用户ID不能为空") private UUID userId; @@ -31,9 +27,6 @@ public class AddProjectMemberDTO { */ private String staffType; - /** - * 角色ID列表 - * 可选参数,分配的角色ID列表 - */ + /** 角色ID列表 可选参数,分配的角色ID列表 */ private List roleIds; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/CreateEnterpriseUserDTO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/CreateEnterpriseUserDTO.java index c5c59bc..df0646c 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/CreateEnterpriseUserDTO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/CreateEnterpriseUserDTO.java @@ -1,15 +1,14 @@ package com.ether.pms.auth.controller.dto; import jakarta.validation.constraints.NotBlank; -import lombok.Data; - import java.time.LocalDate; import java.util.UUID; +import lombok.Data; /** * 创建企业用户请求DTO * - *

用于接收创建企业用户的请求数据,包含用户基础信息和企业扩展信息。

+ *

用于接收创建企业用户的请求数据,包含用户基础信息和企业扩展信息。 * * @author Ether开发团队 * @version 1.0.0 @@ -18,65 +17,35 @@ import java.util.UUID; @Data public class CreateEnterpriseUserDTO { - /** - * 用户名 - * 用于登录系统 - */ + /** 用户名 用于登录系统 */ @NotBlank(message = "用户名不能为空") private String username; - /** - * 密码 - * 用户登录密码 - */ + /** 密码 用户登录密码 */ @NotBlank(message = "密码不能为空") private String password; - /** - * 真实姓名 - * 用户的真实姓名 - */ + /** 真实姓名 用户的真实姓名 */ private String realName; - /** - * 手机号码 - * 中国大陆手机号 - */ + /** 手机号码 中国大陆手机号 */ private String phone; - /** - * 电子邮箱 - * 用户的邮箱地址 - */ + /** 电子邮箱 用户的邮箱地址 */ private String email; - /** - * 员工工号 - * 企业内部员工编号 - */ + /** 员工工号 企业内部员工编号 */ private String employeeNo; - /** - * 部门ID - * 用户所属的部门标识符 - */ + /** 部门ID 用户所属的部门标识符 */ private UUID deptId; - /** - * 职位 - * 用户在企业中的职位 - */ + /** 职位 用户在企业中的职位 */ private String position; - /** - * 入职日期 - * 员工加入企业的日期 - */ + /** 入职日期 员工加入企业的日期 */ private LocalDate entryDate; - /** - * 员工类别 - * 标识员工类型:ENTERPRISE(普通员工)、MANAGEMENT(管理层) - */ + /** 员工类别 标识员工类型:ENTERPRISE(普通员工)、MANAGEMENT(管理层) */ private String userCategory; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DataAccessRequest.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DataAccessRequest.java deleted file mode 100644 index 4c6c29a..0000000 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DataAccessRequest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ether.pms.auth.controller.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import java.util.UUID; - -@Data -public class DataAccessRequest { - @NotBlank(message = "数据类型不能为空") - private String dataType; - - @NotNull(message = "数据ID不能为空") - private UUID dataId; - - @NotBlank(message = "访问类型不能为空") - private String accessType; - - private UUID accessId; - - @NotBlank(message = "访问级别不能为空") - private String accessLevel = "read"; -} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptDTO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptDTO.java index e332779..450587b 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptDTO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptDTO.java @@ -1,14 +1,13 @@ package com.ether.pms.auth.controller.dto; import jakarta.validation.constraints.NotBlank; -import lombok.Data; - import java.util.UUID; +import lombok.Data; /** * 部门数据传输对象 * - *

用于接收创建或更新部门的请求数据。

+ *

用于接收创建或更新部门的请求数据。 * * @author Ether开发团队 * @version 1.0.0 @@ -17,35 +16,20 @@ import java.util.UUID; @Data public class DeptDTO { - /** - * 部门名称 - */ + /** 部门名称 */ @NotBlank(message = "部门名称不能为空") private String deptName; - /** - * 上级部门ID - */ + /** 上级部门ID */ private UUID parentId; - /** - * 部门类型 - * ADMIN: 行政管理 - * ENGINEERING: 工程部 - * SECURITY: 安保部 - * CS: 客服部 - * CLEANING: 保洁部 - */ + /** 部门类型 ADMIN: 行政管理 ENGINEERING: 工程部 SECURITY: 安保部 CS: 客服部 CLEANING: 保洁部 */ private String deptType = "ADMIN"; - /** - * 部门负责人ID - */ + /** 部门负责人ID */ private UUID leaderId; - /** - * 排序序号 - */ + /** 排序序号 */ private Integer sortOrder; private String status = "ACTIVE"; diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptVO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptVO.java index 614d227..468a4c1 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptVO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/DeptVO.java @@ -1,16 +1,15 @@ package com.ether.pms.auth.controller.dto; import com.ether.pms.auth.entity.Dept; -import lombok.Data; - import java.util.ArrayList; import java.util.List; import java.util.UUID; +import lombok.Data; /** * 部门视图对象 * - *

用于返回部门信息的视图对象,包含部门基本信息和子部门列表。

+ *

用于返回部门信息的视图对象,包含部门基本信息和子部门列表。 * * @author Ether开发团队 * @version 1.0.0 @@ -19,49 +18,31 @@ import java.util.UUID; @Data public class DeptVO { - /** - * 部门唯一标识符 - */ + /** 部门唯一标识符 */ private UUID id; - /** - * 上级部门ID - */ + /** 上级部门ID */ private UUID parentId; - /** - * 部门名称 - */ + /** 部门名称 */ private String deptName; - /** - * 部门类型 - */ + /** 部门类型 */ private String deptType; - /** - * 部门类型描述 - */ + /** 部门类型描述 */ private String deptTypeDesc; - /** - * 部门负责人ID - */ + /** 部门负责人ID */ private UUID leaderId; - /** - * 排序序号 - */ + /** 排序序号 */ private Integer sortOrder; - /** - * 部门状态 - */ + /** 部门状态 */ private String status; - /** - * 子部门列表 - */ + /** 子部门列表 */ private List children = new ArrayList<>(); /** diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/PageResponse.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/PageResponse.java index 3478812..6bd226d 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/PageResponse.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/PageResponse.java @@ -1,15 +1,14 @@ package com.ether.pms.auth.controller.dto; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; - /** * 分页响应DTO * - *

用于返回分页数据的统一格式,包含内容列表、分页信息和总数。

+ *

用于返回分页数据的统一格式,包含内容列表、分页信息和总数。 * * @author Ether开发团队 * @version 1.0.0 @@ -20,24 +19,16 @@ import java.util.List; @AllArgsConstructor public class PageResponse { - /** - * 分页数据内容列表 - */ + /** 分页数据内容列表 */ private List content; - /** - * 当前页数据条数 - */ + /** 当前页数据条数 */ private int size; - /** - * 当前页码 - */ + /** 当前页码 */ private int page; - /** - * 数据总条数 - */ + /** 数据总条数 */ private long totalElements; /** @@ -54,4 +45,4 @@ public class PageResponse { this.page = page; this.size = size; } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/ProjectMemberVO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/ProjectMemberVO.java index 03d9bf9..efb7ce0 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/ProjectMemberVO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/ProjectMemberVO.java @@ -2,18 +2,16 @@ package com.ether.pms.auth.controller.dto; import com.ether.pms.auth.entity.ProjectStaff; import com.ether.pms.auth.entity.ProjectStaffRole; -import lombok.Data; - import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; +import lombok.Data; /** * 项目成员视图对象 * - *

用于返回项目成员信息,包含用户基本信息、角色信息和加入时间。

+ *

用于返回项目成员信息,包含用户基本信息、角色信息和加入时间。 * * @author Ether开发团队 * @version 1.0.0 @@ -22,59 +20,37 @@ import java.util.stream.Collectors; @Data public class ProjectMemberVO { - /** - * 用户唯一标识符 - */ + /** 用户唯一标识符 */ private UUID id; - /** - * 用户名 - */ + /** 用户名 */ private String username; - /** - * 真实姓名 - */ + /** 真实姓名 */ private String realName; - /** - * 手机号 - */ + /** 手机号 */ private String phone; - /** - * 邮箱 - */ + /** 邮箱 */ private String email; - /** - * 用户类型 - */ + /** 用户类型 */ private String userType; - /** - * 用户状态 - */ + /** 用户状态 */ private String status; - /** - * 员工类型 - */ + /** 员工类型 */ private String staffType; - /** - * 项目中的角色列表 - */ + /** 项目中的角色列表 */ private List roles; - /** - * 角色名称列表(用于显示) - */ + /** 角色名称列表(用于显示) */ private String roleNames; - /** - * 加入时间 - */ + /** 加入时间 */ private LocalDateTime createdAt; /** @@ -91,7 +67,8 @@ public class ProjectMemberVO { vo.setPhone(staff.getUser().getPhone()); vo.setEmail(staff.getUser().getEmail()); vo.setUserType(staff.getUser().getUserType()); - vo.setStatus(staff.getUser().getStatus() != null ? staff.getUser().getStatus().name() : null); + vo.setStatus( + staff.getUser().getStatus() != null ? staff.getUser().getStatus().name() : null); vo.setStaffType(staff.getStaffType()); vo.setCreatedAt(staff.getCreatedAt()); diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/RoleWithUsersDTO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/RoleWithUsersDTO.java index fbefe29..8b23ddd 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/RoleWithUsersDTO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/RoleWithUsersDTO.java @@ -2,12 +2,10 @@ package com.ether.pms.auth.controller.dto; import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.User; -import lombok.Data; - import java.time.LocalDateTime; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; +import lombok.Data; @Data public class RoleWithUsersDTO { diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserProjectRequest.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserProjectRequest.java index 900457a..56a1ecd 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserProjectRequest.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserProjectRequest.java @@ -2,8 +2,8 @@ package com.ether.pms.auth.controller.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import java.util.UUID; +import lombok.Data; @Data public class UserProjectRequest { diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserVO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserVO.java index e98584a..438ceae 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserVO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserVO.java @@ -1,16 +1,15 @@ package com.ether.pms.auth.controller.dto; import com.ether.pms.auth.entity.User; -import lombok.Data; - import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import lombok.Data; /** * 用户视图对象 * - *

用于返回用户信息的简化视图,包含用户基本信息、部门信息和角色列表。

+ *

用于返回用户信息的简化视图,包含用户基本信息、部门信息和角色列表。 * * @author Ether开发团队 * @version 1.0.0 @@ -19,64 +18,40 @@ import java.util.stream.Collectors; @Data public class UserVO { - /** - * 用户唯一标识符 - */ + /** 用户唯一标识符 */ private UUID id; - /** - * 用户名 - */ + /** 用户名 */ private String username; - /** - * 真实姓名 - */ + /** 真实姓名 */ private String realName; - /** - * 手机号 - */ + /** 手机号 */ private String phone; - /** - * 邮箱 - */ + /** 邮箱 */ private String email; - /** - * 用户类型 - */ + /** 用户类型 */ private String userType; - /** - * 用户状态 - */ + /** 用户状态 */ private String status; - /** - * 部门ID - */ + /** 部门ID */ private UUID deptId; - /** - * 部门名称 - */ + /** 部门名称 */ private String deptName; - /** - * 角色列表 - */ + /** 角色列表 */ private List roles; - /** - * 角色名称列表(逗号分隔) - */ + /** 角色名称列表(逗号分隔) */ private String roleNames; - /** - * 角色信息内部类 - */ + /** 角色信息内部类 */ @Data public static class RoleInfo { private UUID id; @@ -103,17 +78,20 @@ public class UserVO { // 转换角色信息 if (user.getRoles() != null && !user.getRoles().isEmpty()) { - List roleInfos = user.getRoles().stream() - .map(role -> { - RoleInfo info = new RoleInfo(); - info.setId(role.getId()); - info.setCode(role.getCode()); - info.setName(role.getName()); - return info; - }) - .collect(Collectors.toList()); + List roleInfos = + user.getRoles().stream() + .map( + role -> { + RoleInfo info = new RoleInfo(); + info.setId(role.getId()); + info.setCode(role.getCode()); + info.setName(role.getName()); + return info; + }) + .collect(Collectors.toList()); vo.setRoles(roleInfos); - vo.setRoleNames(roleInfos.stream().map(RoleInfo::getName).collect(Collectors.joining("、"))); + vo.setRoleNames( + roleInfos.stream().map(RoleInfo::getName).collect(Collectors.joining("、"))); } return vo; diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserWithRolesDTO.java b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserWithRolesDTO.java index 8bd1118..5620a7d 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserWithRolesDTO.java +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/dto/UserWithRolesDTO.java @@ -2,12 +2,11 @@ package com.ether.pms.auth.controller.dto; import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.User; -import lombok.Data; - import java.time.LocalDateTime; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import lombok.Data; @Data public class UserWithRolesDTO { @@ -51,9 +50,8 @@ public class UserWithRolesDTO { dto.setLastLoginIp(user.getLastLoginIp()); dto.setCreatedAt(user.getCreatedAt()); if (user.getRoles() != null) { - dto.setRoles(user.getRoles().stream() - .map(RoleInfo::fromRole) - .collect(Collectors.toList())); + dto.setRoles( + user.getRoles().stream().map(RoleInfo::fromRole).collect(Collectors.toList())); } return dto; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/AuditLog.java b/module-auth/src/main/java/com/ether/pms/auth/entity/AuditLog.java index fa2171b..021217e 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/AuditLog.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/AuditLog.java @@ -1,22 +1,23 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; -import java.time.LocalDateTime; -import java.util.UUID; - @Entity -@Table(name = "sys_audit_log", indexes = { - @Index(name = "idx_audit_log_created_at", columnList = "createdAt"), - @Index(name = "idx_audit_log_user_id", columnList = "userId"), - @Index(name = "idx_audit_log_module", columnList = "module"), - @Index(name = "idx_audit_log_action", columnList = "action"), - @Index(name = "idx_al_user_createdat", columnList = "user_id, createdAt DESC") -}) +@Table( + name = "sys_audit_log", + indexes = { + @Index(name = "idx_audit_log_created_at", columnList = "createdAt"), + @Index(name = "idx_audit_log_user_id", columnList = "userId"), + @Index(name = "idx_audit_log_module", columnList = "module"), + @Index(name = "idx_audit_log_action", columnList = "action"), + @Index(name = "idx_al_user_createdat", columnList = "user_id, createdAt DESC") + }) @Data public class AuditLog { diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/DataAccess.java b/module-auth/src/main/java/com/ether/pms/auth/entity/DataAccess.java deleted file mode 100644 index ef2718c..0000000 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/DataAccess.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ether.pms.auth.entity; - -import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDateTime; -import java.util.UUID; - -@Entity -@Table(name = "biz_data_access") -@Data -@Deprecated -public class DataAccess { - - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private UUID id; - - @Column(name = "data_type", nullable = false) - private String dataType; - - @Column(name = "data_id", nullable = false) - private UUID dataId; - - @Column(name = "access_type", nullable = false) - private String accessType; - - @Column(name = "access_id", nullable = false) - private UUID accessId; - - @Column(name = "access_level", nullable = false) - private String accessLevel = "read"; - - @Column(name = "granted_by") - private UUID grantedBy; - - @Column(name = "granted_at", nullable = false) - private LocalDateTime grantedAt = LocalDateTime.now(); - - @PrePersist - public void prePersist() { - if (this.grantedAt == null) { - this.grantedAt = LocalDateTime.now(); - } - } -} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Dept.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Dept.java index 34f9872..da257b4 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/Dept.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Dept.java @@ -1,23 +1,19 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; /** * 部门实体类 * - *

表示组织架构中的部门信息,包含部门名称、类型、负责人等。

+ *

表示组织架构中的部门信息,包含部门名称、类型、负责人等。 * - *

支持树形结构,通过parentId指向上级部门。

+ *

支持树形结构,通过parentId指向上级部门。 * - *

部门类型用于区分不同业务职能的部门: - * - ADMIN: 行政管理部门,如总部、行政部、财务部、人力资源部等 - * - ENGINEERING: 工程部门,负责设施设备维护、维修等 - * - SECURITY: 安保部门,负责安全保卫、门禁管理等 - * - CS: 客服部门,负责业主服务、投诉处理等 - * - CLEANING: 保洁部门,负责清洁卫生、绿化养护等

+ *

部门类型用于区分不同业务职能的部门: - ADMIN: 行政管理部门,如总部、行政部、财务部、人力资源部等 - ENGINEERING: 工程部门,负责设施设备维护、维修等 - + * SECURITY: 安保部门,负责安全保卫、门禁管理等 - CS: 客服部门,负责业主服务、投诉处理等 - CLEANING: 保洁部门,负责清洁卫生、绿化养护等 * * @author Ether开发团队 * @version 1.0.0 @@ -28,77 +24,47 @@ import java.util.UUID; @Data public class Dept { - /** - * 部门唯一标识符 - * 采用UUID策略自动生成 - */ + /** 部门唯一标识符 采用UUID策略自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 上级部门ID - * 指向父部门的ID,用于构建部门树形结构,顶级部门此字段为空 - */ + /** 上级部门ID 指向父部门的ID,用于构建部门树形结构,顶级部门此字段为空 */ private UUID parentId; - /** - * 部门名称 - * 部门的显示名称 - */ + /** 部门名称 部门的显示名称 */ @Column(nullable = false, length = 100) private String deptName; /** - * 部门类型 - * 标识部门所属的业务类型,用于区分不同职能的部门 - * ADMIN: 行政管理 - 负责公司行政、财务、人事等管理职能 - * ENGINEERING: 工程部 - 负责设施设备维护、维修、保养等技术工作 - * SECURITY: 安保部 - 负责安全保卫、门禁管理、巡逻等工作 - * CS: 客服部 - 负责业主服务、投诉处理、满意度调查等 - * CLEANING: 保洁部 - 负责清洁卫生、绿化养护、垃圾处理等 + * 部门类型 标识部门所属的业务类型,用于区分不同职能的部门 ADMIN: 行政管理 - 负责公司行政、财务、人事等管理职能 ENGINEERING: 工程部 - + * 负责设施设备维护、维修、保养等技术工作 SECURITY: 安保部 - 负责安全保卫、门禁管理、巡逻等工作 CS: 客服部 - 负责业主服务、投诉处理、满意度调查等 CLEANING: + * 保洁部 - 负责清洁卫生、绿化养护、垃圾处理等 */ @Column(length = 20) private String deptType = "ADMIN"; - /** - * 部门负责人ID - * 部门负责人的用户ID - */ + /** 部门负责人ID 部门负责人的用户ID */ private UUID leaderId; - /** - * 排序序号 - * 部门在同一级别中的排序顺序,数字越小越靠前 - */ + /** 排序序号 部门在同一级别中的排序顺序,数字越小越靠前 */ private Integer sortOrder; - /** - * 部门状态 - * 标识部门的状态:ACTIVE-正常、DISABLED-停用 - */ + /** 部门状态 标识部门的状态:ACTIVE-正常、DISABLED-停用 */ @Column(length = 20) private String status = "ACTIVE"; - /** - * 创建时间 - */ + /** 创建时间 */ private LocalDateTime createdAt; - /** - * 更新时间 - */ + /** 更新时间 */ private LocalDateTime updatedAt; - /** - * 创建人ID - */ + /** 创建人ID */ @Column(name = "created_by") private UUID createdBy; - /** - * 更新人ID - */ + /** 更新人ID */ @Column(name = "updated_by") private UUID updatedBy; @@ -113,9 +79,7 @@ public class Dept { updatedAt = LocalDateTime.now(); } - /** - * 部门类型枚举 - */ + /** 部门类型枚举 */ public enum DeptType { ADMIN("行政管理"), ENGINEERING("工程部"), diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/EnterpriseUser.java b/module-auth/src/main/java/com/ether/pms/auth/entity/EnterpriseUser.java index 2af111f..4b882cb 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/EnterpriseUser.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/EnterpriseUser.java @@ -1,16 +1,16 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDate; import java.util.UUID; +import lombok.Data; /** * 企业用户实体类 * - *

表示企业类型的用户信息,包含员工的职位、入职日期等企业相关信息。

+ *

表示企业类型的用户信息,包含员工的职位、入职日期等企业相关信息。 * - *

与User实体为一对一关系,共享主键。

+ *

与User实体为一对一关系,共享主键。 * * @author Ether开发团队 * @version 1.0.0 @@ -21,50 +21,28 @@ import java.util.UUID; @Data public class EnterpriseUser { - /** - * 用户ID - * 主键,也是外键指向auth_user表 - */ - @Id - private UUID userId; + /** 用户ID 主键,也是外键指向auth_user表 */ + @Id private UUID userId; - /** - * 员工工号 - * 企业内部员工编号 - */ + /** 员工工号 企业内部员工编号 */ @Column(length = 50) private String employeeNo; - /** - * 部门ID - * 用户所属的部门标识符 - */ + /** 部门ID 用户所属的部门标识符 */ private UUID deptId; - /** - * 职位 - * 用户在企业中的职位 - */ + /** 职位 用户在企业中的职位 */ @Column(length = 50) private String position; - /** - * 入职日期 - * 员工加入企业的日期 - */ + /** 入职日期 员工加入企业的日期 */ private LocalDate entryDate; - /** - * 员工类别 - * 标识员工类型:ENTERPRISE(普通员工)、MANAGEMENT(管理层) - */ + /** 员工类别 标识员工类型:ENTERPRISE(普通员工)、MANAGEMENT(管理层) */ @Column(length = 50) private String userCategory; - /** - * 关联的User实体 - * 采用一对一关系,共享主键 - */ + /** 关联的User实体 采用一对一关系,共享主键 */ @OneToOne @MapsId @JoinColumn(name = "user_id") diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java index 5aaedcf..fa23580 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java @@ -1,20 +1,19 @@ package com.ether.pms.auth.entity; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Data; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.Data; /** * 权限实体类 * - *

用于定义系统中的权限信息,包括权限代码、名称、类型、资源路径等属性。 - * 权限是系统安全的基础单元,用于控制用户对具体功能的访问能力。

+ *

用于定义系统中的权限信息,包括权限代码、名称、类型、资源路径等属性。 权限是系统安全的基础单元,用于控制用户对具体功能的访问能力。 * * @author Ether开发团队 * @version 1.0.0 @@ -25,79 +24,49 @@ import java.util.UUID; @Data public class Permission { - /** - * 权限唯一标识符 - * 使用UUID自动生成 - */ + /** 权限唯一标识符 使用UUID自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 权限代码 - * 系统内部识别符,必须唯一 - * 格式:只能包含字母、数字、冒号和下划线,长度2-100位 - * 例如:system:user:create, menu:project:view - */ + /** 权限代码 系统内部识别符,必须唯一 格式:只能包含字母、数字、冒号和下划线,长度2-100位 例如:system:user:create, menu:project:view */ @NotNull(message = "权限代码不能为空") @Size(min = 2, max = 100, message = "权限代码长度必须在2-100位之间") @Pattern(regexp = "^[a-zA-Z0-9_:]+$", message = "权限代码只能包含字母、数字、冒号和下划线") @Column(unique = true, nullable = false, length = 100) private String code; - /** - * 权限名称 - * 用于前端展示和用户理解,长度2-100位 - */ + /** 权限名称 用于前端展示和用户理解,长度2-100位 */ @NotNull(message = "权限名称不能为空") @Size(min = 2, max = 100, message = "权限名称长度必须在2-100位之间") @Column(nullable = false, length = 100) private String name; - /** - * 权限类型 - * 用于分类权限,如:MENU(菜单)、BUTTON(按钮)、API(接口)等 - */ + /** 权限类型 用于分类权限,如:MENU(菜单)、BUTTON(按钮)、API(接口)等 */ @Size(max = 20, message = "权限类型长度不能超过20位") @Column(length = 20) private String type; - /** - * 资源路径 - * 对于API类型的权限,表示对应的接口路径 - */ + /** 资源路径 对于API类型的权限,表示对应的接口路径 */ @Size(max = 50, message = "资源路径长度不能超过50位") @Column(length = 50) private String resource; - /** - * 请求方法 - * 对于API类型的权限,表示对应的HTTP方法(如GET、POST、PUT、DELETE) - */ + /** 请求方法 对于API类型的权限,表示对应的HTTP方法(如GET、POST、PUT、DELETE) */ @Size(max = 50, message = "请求方法长度不能超过50位") @Column(length = 50) private String method; - /** - * 权限描述 - * 对权限的详细说明,用于帮助理解权限用途,长度不超过200位 - */ + /** 权限描述 对权限的详细说明,用于帮助理解权限用途,长度不超过200位 */ @Size(max = 200, message = "权限描述长度不能超过200位") @Column(length = 200) private String description; - /** - * 父权限代码 - * 用于构建权限树形结构,支持层级管理 - * 顶级权限的父代码为空 - */ + /** 父权限代码 用于构建权限树形结构,支持层级管理 顶级权限的父代码为空 */ @Column(length = 50) private String parentCode; - /** - * 排序序号 - * 用于前端展示时的排序,数字越小越靠前 - */ + /** 排序序号 用于前端展示时的排序,数字越小越靠前 */ @Size(max = 200, message = "路由路径长度不能超过200位") @Column(length = 200) private String path; @@ -115,48 +84,31 @@ public class Permission { @Column(nullable = false) private Boolean visible = true; - /** - * 权限关联的角色列表 - * 多对多关系,通过auth_role_permission关联表维护 - * JsonIgnoreProperties避免循环序列化 - */ + /** 权限关联的角色列表 多对多关系,通过auth_role_permission关联表维护 JsonIgnoreProperties避免循环序列化 */ @JsonIgnoreProperties({"permissions", "roles"}) @ManyToMany(fetch = FetchType.LAZY) @JoinTable( - name = "auth_role_permission", - joinColumns = @JoinColumn(name = "permission_id"), - inverseJoinColumns = @JoinColumn(name = "role_id") - ) + name = "auth_role_permission", + joinColumns = @JoinColumn(name = "permission_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) private List roles; - /** - * 权限创建时间 - * 自动设置,记录创建时刻 - */ + /** 权限创建时间 自动设置,记录创建时刻 */ private LocalDateTime createdAt; - /** - * 权限更新时间 - * 自动设置,每次更新时自动修改 - */ + /** 权限更新时间 自动设置,每次更新时自动修改 */ private LocalDateTime updatedAt; - /** - * 持久化前回调 - * 自动设置创建时间和更新时间 - */ + /** 持久化前回调 自动设置创建时间和更新时间 */ @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } - /** - * 更新前回调 - * 自动设置更新时间 - */ + /** 更新前回调 自动设置更新时间 */ @PreUpdate public void preUpdate() { this.updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaff.java b/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaff.java index 4b7c0cf..ca05053 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaff.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaff.java @@ -1,10 +1,5 @@ package com.ether.pms.auth.entity; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -16,14 +11,18 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import lombok.Data; /** * 项目员工实体类 * - *

表示项目类型的员工信息,包含所属项目、员工类型、班次等信息。

+ *

表示项目类型的员工信息,包含所属项目、员工类型、班次等信息。 * - *

与User实体为一对一关系,共享主键。

+ *

与User实体为一对一关系,共享主键。 * * @author Ether开发团队 * @version 1.0.0 @@ -34,32 +33,21 @@ import lombok.Data; @Data public class ProjectStaff { - /** - * 主键ID - */ + /** 主键ID */ @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "id") private UUID id; - /** - * 用户ID - * 外键指向auth_user表 - */ + /** 用户ID 外键指向auth_user表 */ @Column(name = "user_id", insertable = false, updatable = false) private UUID userId; - /** - * 所属项目ID - * 员工所属项目的唯一标识符 - */ + /** 所属项目ID 员工所属项目的唯一标识符 */ @Column(name = "project_id") private UUID projectId; - /** - * 所属部门ID - * 员工所属部门的唯一标识符,用于组织架构管理 - */ + /** 所属部门ID 员工所属部门的唯一标识符,用于组织架构管理 */ @Column(name = "dept_id") private UUID deptId; @@ -70,48 +58,30 @@ public class ProjectStaff { @Column(length = 50) private String staffType; - /** - * 班次类型 - * 标识员工的班次:DAY(白班)、NIGHT(夜班)、ROTATION(轮班) - */ + /** 班次类型 标识员工的班次:DAY(白班)、NIGHT(夜班)、ROTATION(轮班) */ @Column(length = 20) private String shiftType; - /** - * 班组长ID - * 员工所属班组长的用户ID - */ + /** 班组长ID 员工所属班组长的用户ID */ private UUID leaderId; - /** - * 在岗状态 - * 标识员工的在岗状态:ASSIGNED(已分配)、ON_LEAVE(请假)、TRANSFERRED(已调离) - */ + /** 在岗状态 标识员工的在岗状态:ASSIGNED(已分配)、ON_LEAVE(请假)、TRANSFERRED(已调离) */ @Column(length = 20) private String assignmentStatus; - /** - * 员工角色列表 - */ + /** 员工角色列表 */ @OneToMany(mappedBy = "staff", cascade = CascadeType.ALL, orphanRemoval = true) private List staffRoles = new ArrayList<>(); - /** - * 创建时间 - */ + /** 创建时间 */ @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; - /** - * 更新时间 - */ + /** 更新时间 */ @Column(name = "updated_at") private LocalDateTime updatedAt; - /** - * 关联的User实体 - * 采用一对一关系 - */ + /** 关联的User实体 采用一对一关系 */ @OneToOne @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_project_staff_user")) private User user; diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaffRole.java b/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaffRole.java index 7ac93d3..b576586 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaffRole.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/ProjectStaffRole.java @@ -1,8 +1,5 @@ package com.ether.pms.auth.entity; -import java.time.LocalDateTime; -import java.util.UUID; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -13,6 +10,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.PrePersist; import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.Data; @Entity @@ -39,4 +38,4 @@ public class ProjectStaffRole { protected void onCreate() { createdAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Resident.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Resident.java index 4acb20e..b63e7ff 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/Resident.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Resident.java @@ -2,16 +2,16 @@ package com.ether.pms.auth.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; /** * 住户实体类 * - *

表示住户类型的用户信息,包含身份证、认证状态等住户相关信息。

+ *

表示住户类型的用户信息,包含身份证、认证状态等住户相关信息。 * - *

与User实体为一对一关系,共享主键。

+ *

与User实体为一对一关系,共享主键。 * * @author Ether开发团队 * @version 1.0.0 @@ -22,77 +22,47 @@ import java.util.UUID; @Data public class Resident { - /** - * 用户ID - * 主键,也是外键指向auth_user表 - */ - @Id - private UUID userId; + /** 用户ID 主键,也是外键指向auth_user表 */ + @Id private UUID userId; - /** - * 身份证号码 - * 住户的身份证号,用于身份验证 - */ + /** 身份证号码 住户的身份证号,用于身份验证 */ @JsonIgnore @Column(length = 18) private String idCard; - /** - * 住户类型 - * 标识住户类型:OWNER(业主)、FAMILY(家属)、TENANT(租户) - */ + /** 住户类型 标识住户类型:OWNER(业主)、FAMILY(家属)、TENANT(租户) */ @Column(length = 20) private String residentType; - /** - * 认证状态 - * 标识住户的认证状态:UNVERIFIED(未认证)、PENDING(待审核)、VERIFIED(已认证)、REJECTED(已拒绝) - */ + /** 认证状态 标识住户的认证状态:UNVERIFIED(未认证)、PENDING(待审核)、VERIFIED(已认证)、REJECTED(已拒绝) */ @Column(length = 20) private String verificationStatus; - /** - * 认证时间 - * 记录住户认证通过的时间 - */ + /** 认证时间 记录住户认证通过的时间 */ private LocalDateTime verifiedAt; - /** - * 认证人ID - * 执行认证操作的管理员用户ID - */ + /** 认证人ID 执行认证操作的管理员用户ID */ private UUID verifiedBy; - /** - * 关联的User实体 - * 采用一对一关系,共享主键 - */ + /** 关联的User实体 采用一对一关系,共享主键 */ @OneToOne @MapsId @JoinColumn(name = "user_id") private User user; - /** - * 创建时间 - */ + /** 创建时间 */ @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - /** - * 更新时间 - */ + /** 更新时间 */ @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - /** - * 创建人ID - */ + /** 创建人ID */ @Column(name = "created_by") private UUID createdBy; - /** - * 更新人ID - */ + /** 更新人ID */ @Column(name = "updated_by") private UUID updatedBy; diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/ResidentSpace.java b/module-auth/src/main/java/com/ether/pms/auth/entity/ResidentSpace.java index 3fdc583..0618ee3 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/ResidentSpace.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/ResidentSpace.java @@ -1,16 +1,16 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDate; import java.util.UUID; +import lombok.Data; /** * 住户房屋关联实体类 * - *

表示住户与房屋之间的关联关系,记录住户与房屋的绑定信息。

+ *

表示住户与房屋之间的关联关系,记录住户与房屋的绑定信息。 * - *

支持业主、家属、租户等多种关系类型,以及绑定状态管理。

+ *

支持业主、家属、租户等多种关系类型,以及绑定状态管理。 * * @author Ether开发团队 * @version 1.0.0 @@ -21,51 +21,30 @@ import java.util.UUID; @Data public class ResidentSpace { - /** - * 关联记录唯一标识符 - * 采用UUID策略自动生成 - */ + /** 关联记录唯一标识符 采用UUID策略自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 用户ID - * 关联的用户唯一标识符 - */ + /** 用户ID 关联的用户唯一标识符 */ @Column(name = "user_id", nullable = false) private UUID userId; - /** - * 房屋ID - * 关联的房屋唯一标识符 - */ + /** 房屋ID 关联的房屋唯一标识符 */ @Column(name = "space_id", nullable = false) private UUID spaceId; - /** - * 关系类型 - * 标识住户与房屋的关系:OWNER(业主)、FAMILY(家属)、TENANT(租户) - */ + /** 关系类型 标识住户与房屋的关系:OWNER(业主)、FAMILY(家属)、TENANT(租户) */ @Column(length = 20) private String relationType; - /** - * 绑定状态 - * 标识关联的状态:PENDING(待生效)、ACTIVE(生效中)、EXPIRED(已过期)、CANCELLED(已取消) - */ + /** 绑定状态 标识关联的状态:PENDING(待生效)、ACTIVE(生效中)、EXPIRED(已过期)、CANCELLED(已取消) */ @Column(length = 20) private String bindingStatus; - /** - * 开始日期 - * 关联关系的生效日期 - */ + /** 开始日期 关联关系的生效日期 */ private LocalDate startDate; - /** - * 结束日期 - * 关联关系的失效日期,永久关联则为空 - */ + /** 结束日期 关联关系的失效日期,永久关联则为空 */ private LocalDate endDate; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java index 751f26e..5f5450b 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java @@ -4,16 +4,15 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Data; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.Data; /** * 角色实体类 * - *

用于定义系统中的角色信息,包括角色代码、名称、描述、类型、数据范围等属性。 - * 角色与权限通过多对多关联,一个角色可以拥有多个权限。

+ *

用于定义系统中的角色信息,包括角色代码、名称、描述、类型、数据范围等属性。 角色与权限通过多对多关联,一个角色可以拥有多个权限。 * * @author Ether开发团队 * @version 1.0.0 @@ -24,122 +23,84 @@ import java.util.UUID; @Data public class Role { - /** - * 角色唯一标识符 - * 使用UUID自动生成 - */ + /** 角色唯一标识符 使用UUID自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 角色代码 - * 用于系统内部识别,必须唯一 - * 格式:只能包含字母、数字和下划线,长度2-50位 - */ + /** 角色代码 用于系统内部识别,必须唯一 格式:只能包含字母、数字和下划线,长度2-50位 */ @NotNull(message = "角色代码不能为空") @Size(min = 2, max = 50, message = "角色代码长度必须在2-50位之间") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "角色代码只能包含字母、数字和下划线") @Column(unique = true, nullable = false, length = 50) private String code; - /** - * 角色名称 - * 用于前端展示,长度2-50位 - */ + /** 角色名称 用于前端展示,长度2-50位 */ @NotNull(message = "角色名称不能为空") @Size(min = 2, max = 50, message = "角色名称长度必须在2-50位之间") @Column(nullable = false, length = 50) private String name; - /** - * 角色描述 - * 对角色的详细说明,长度不超过200位 - */ + /** 角色描述 对角色的详细说明,长度不超过200位 */ @Size(max = 200, message = "角色描述长度不能超过200位") @Column(length = 200) private String description; - /** - * 角色类型 - * 区分系统级、项目级、部门级角色 - */ + /** 角色类型 区分系统级、项目级、部门级角色 */ @Enumerated(EnumType.STRING) @Column(length = 20, nullable = false) private RoleType type; - /** - * 数据范围 - * 定义角色可访问的数据范围,默认为本人数据 - */ + /** 数据范围 定义角色可访问的数据范围,默认为本人数据 */ @Enumerated(EnumType.STRING) @Column(length = 20) private DataScope dataScope = DataScope.SELF; - /** - * 所属项目ID - * 用于项目级角色的项目归属 - */ + /** 所属项目ID 用于项目级角色的项目归属 */ @Column(name = "project_id", columnDefinition = "uuid") private UUID projectId; - /** - * 角色状态 - * 启用或禁用,默认为启用 - */ + /** 角色状态 启用或禁用,默认为启用 */ @Enumerated(EnumType.STRING) @Column(length = 20, nullable = false) private RoleStatus status = RoleStatus.ENABLED; - /** - * 角色关联的权限列表 - * 多对多关系,通过auth_role_permission关联表维护 - */ + /** 角色关联的权限列表 多对多关系,通过auth_role_permission关联表维护 */ @ManyToMany(fetch = FetchType.LAZY) @JoinTable( - name = "auth_role_permission", - joinColumns = @JoinColumn(name = "role_id", foreignKey = @ForeignKey(name = "fk_auth_role_permission_role")), - inverseJoinColumns = @JoinColumn(name = "permission_id", foreignKey = @ForeignKey(name = "fk_auth_role_permission_permission")) - ) + name = "auth_role_permission", + joinColumns = + @JoinColumn( + name = "role_id", + foreignKey = @ForeignKey(name = "fk_auth_role_permission_role")), + inverseJoinColumns = + @JoinColumn( + name = "permission_id", + foreignKey = @ForeignKey(name = "fk_auth_role_permission_permission"))) private List permissions; - /** - * 角色创建时间 - * 自动设置,记录创建时刻 - */ + /** 角色创建时间 自动设置,记录创建时刻 */ @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - /** - * 角色更新时间 - * 自动设置,每次更新时自动修改 - */ + /** 角色更新时间 自动设置,每次更新时自动修改 */ @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - /** - * 持久化前回调 - * 自动设置创建时间和更新时间 - */ + /** 持久化前回调 自动设置创建时间和更新时间 */ @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } - /** - * 更新前回调 - * 自动设置更新时间 - */ + /** 更新前回调 自动设置更新时间 */ @PreUpdate public void preUpdate() { this.updatedAt = LocalDateTime.now(); } - /** - * 角色类型枚举 - * 定义角色的层级分类 - */ + /** 角色类型枚举 定义角色的层级分类 */ public enum RoleType { /** 系统级角色,可访问所有项目数据 */ SYSTEM("系统级"), @@ -155,10 +116,7 @@ public class Role { } } - /** - * 数据范围枚举 - * 定义角色可查看的数据范围级别 - */ + /** 数据范围枚举 定义角色可查看的数据范围级别 */ public enum DataScope { /** 全部数据,可查看所有数据 */ ALL("全部"), @@ -176,10 +134,7 @@ public class Role { } } - /** - * 角色状态枚举 - * 定义角色的启用/禁用状态 - */ + /** 角色状态枚举 定义角色的启用/禁用状态 */ public enum RoleStatus { /** 角色已启用,可以正常使用 */ ENABLED("启用"), @@ -192,4 +147,4 @@ public class Role { this.desc = desc; } } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Space.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Space.java index 98298c9..c123ec9 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/Space.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Space.java @@ -1,16 +1,16 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; import java.math.BigDecimal; import java.util.UUID; +import lombok.Data; /** * 房屋空间实体类 * - *

表示项目中的房屋或空间信息,包含楼栋、单元、房号等。

+ *

表示项目中的房屋或空间信息,包含楼栋、单元、房号等。 * - *

支持住宅和商业两种类型的空间管理。

+ *

支持住宅和商业两种类型的空间管理。 * * @author Ether开发团队 * @version 1.0.0 @@ -21,65 +21,38 @@ import java.util.UUID; @Data public class Space { - /** - * 空间唯一标识符 - * 采用UUID策略自动生成 - */ + /** 空间唯一标识符 采用UUID策略自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 所属项目ID - * 空间所属项目的唯一标识符 - */ + /** 所属项目ID 空间所属项目的唯一标识符 */ @Column(name = "project_id") private UUID projectId; - /** - * 楼栋 - * 房屋所在的楼栋号或名称 - */ + /** 楼栋 房屋所在的楼栋号或名称 */ @Column(length = 50) private String building; - /** - * 单元 - * 房屋所在的单元号 - */ + /** 单元 房屋所在的单元号 */ @Column(length = 50) private String unit; - /** - * 房号 - * 房屋的房间号 - */ + /** 房号 房屋的房间号 */ @Column(length = 50) private String roomNo; - /** - * 房屋类型 - * 标识房屋类型:RESIDENTIAL(住宅)、COMMERCIAL(商业) - */ + /** 房屋类型 标识房屋类型:RESIDENTIAL(住宅)、COMMERCIAL(商业) */ @Column(length = 20) private String spaceType; - /** - * 楼层 - * 房屋所在的楼层 - */ + /** 楼层 房屋所在的楼层 */ private Integer floor; - /** - * 建筑面积 - * 房屋的建筑面积,单位平方米 - */ + /** 建筑面积 房屋的建筑面积,单位平方米 */ private BigDecimal unitArea; - /** - * 状态 - * 标识房屋的状态:正常、空置、已售等 - */ + /** 状态 标识房屋的状态:正常、空置、已售等 */ @Column(length = 20) private String status; } diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/SysConfig.java b/module-auth/src/main/java/com/ether/pms/auth/entity/SysConfig.java index eaa329b..a6598d5 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/SysConfig.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/SysConfig.java @@ -1,19 +1,17 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; -import lombok.NoArgsConstructor; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; -import java.time.LocalDateTime; -import java.util.UUID; - /** - * 系统配置实体类 - * 用于存储系统的键值对配置信息,如物业企业名称等 + * 系统配置实体类 用于存储系统的键值对配置信息,如物业企业名称等 * * @author Ether Team * @since 1.0.0 @@ -26,47 +24,29 @@ import java.util.UUID; @Builder public class SysConfig { - /** - * 配置项唯一标识 - * 使用 UUID 自动生成 - */ + /** 配置项唯一标识 使用 UUID 自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 配置键 - * 唯一标识一个配置项,如:property_company_name - */ + /** 配置键 唯一标识一个配置项,如:property_company_name */ @Column(name = "config_key", nullable = false, unique = true, length = 128) private String configKey; - /** - * 配置值 - * 使用 TEXT 类型存储,支持长文本 - */ + /** 配置值 使用 TEXT 类型存储,支持长文本 */ @Column(name = "config_value", length = 5000) private String configValue; - /** - * 配置描述 - * 用于说明该配置项的用途 - */ + /** 配置描述 用于说明该配置项的用途 */ @Column(name = "description", length = 256) private String description; - /** - * 创建时间 - * 自动填充,不可更新 - */ + /** 创建时间 自动填充,不可更新 */ @CreationTimestamp @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; - /** - * 更新时间 - * 自动填充,更新时自动修改 - */ + /** 更新时间 自动填充,更新时自动修改 */ @UpdateTimestamp @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/User.java b/module-auth/src/main/java/com/ether/pms/auth/entity/User.java index 10f7026..da46e1b 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/User.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/User.java @@ -6,17 +6,17 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Data; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.Data; /** * 用户实体类 * - *

表示系统中的用户信息,包含用户的基本认证信息和个人资料。

+ *

表示系统中的用户信息,包含用户的基本认证信息和个人资料。 * - *

使用JPA注解映射到auth_user表,支持基本的CRUD操作。

+ *

使用JPA注解映射到auth_user表,支持基本的CRUD操作。 * * @author Ether开发团队 * @version 1.0.0 @@ -27,157 +27,101 @@ import java.util.UUID; @Data public class User { - /** - * 用户唯一标识符 - * 采用UUID策略自动生成 - */ + /** 用户唯一标识符 采用UUID策略自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 用户名 - * 用于用户登录,唯一标识,长度3-50位,只能包含字母、数字和下划线 - */ + /** 用户名 用于用户登录,唯一标识,长度3-50位,只能包含字母、数字和下划线 */ @NotNull(message = "用户名不能为空") @Size(min = 3, max = 50, message = "用户名长度必须在3-50位之间") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线") @Column(unique = true, nullable = false, length = 50) private String username; - /** - * 密码(加密存储) - * 使用BCrypt加密后的密码原文 - */ + /** 密码(加密存储) 使用BCrypt加密后的密码原文 */ @NotNull(message = "密码不能为空") @JsonIgnore @Column(nullable = false, length = 255) private String password; - /** - * 密码盐值 - * 用于增强密码加密的安全性 - */ - @JsonIgnore - private String salt; + /** 密码盐值 用于增强密码加密的安全性 */ + @JsonIgnore private String salt; - /** - * 真实姓名 - * 用户的真实姓名,用于显示 - */ + /** 真实姓名 用户的真实姓名,用于显示 */ @Column(length = 50) private String realName; - /** - * 手机号码 - * 中国大陆手机号格式,11位数字 - */ + /** 手机号码 中国大陆手机号格式,11位数字 */ @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @Column(length = 20) private String phone; - /** - * 电子邮箱 - * 用户的邮箱地址,用于通知和验证 - */ + /** 电子邮箱 用户的邮箱地址,用于通知和验证 */ @Email(message = "邮箱格式不正确") @Column(length = 100) private String email; - /** - * 头像URL - * 用户头像的存储路径或URL - */ + /** 头像URL 用户头像的存储路径或URL */ private String avatar; - /** - * 用户状态 - * 标识用户的当前状态:正常、锁定或禁用 - */ + /** 用户状态 标识用户的当前状态:正常、锁定或禁用 */ @Enumerated(EnumType.STRING) @Column(length = 20, nullable = false) private UserStatus status = UserStatus.ACTIVE; - /** - * 用户类型 - * 标识用户的类型:ENTERPRISE(企业用户)、PROJECT_STAFF(项目员工)、RESIDENT(住户)、CUSTOMER(客户) - */ + /** 用户类型 标识用户的类型:ENTERPRISE(企业用户)、PROJECT_STAFF(项目员工)、RESIDENT(住户)、CUSTOMER(客户) */ @Column(length = 20, nullable = false) private String userType; - /** - * 部门ID - * 用户所属的部门标识符 - */ + /** 部门ID 用户所属的部门标识符 */ private UUID deptId; - /** - * 最后登录时间 - * 记录用户最后一次成功登录的时间 - */ + /** 最后登录时间 记录用户最后一次成功登录的时间 */ private LocalDateTime lastLoginTime; - /** - * 最后登录IP - * 记录用户最后一次成功登录的IP地址 - */ + /** 最后登录IP 记录用户最后一次成功登录的IP地址 */ private String lastLoginIp; - /** - * 用户关联的角色列表 - * 采用懒加载方式获取用户的所有角色 - */ + /** 用户关联的角色列表 采用懒加载方式获取用户的所有角色 */ @ManyToMany(fetch = FetchType.LAZY) @JoinTable( - name = "auth_user_role", - joinColumns = @JoinColumn(name = "user_id", foreignKey = @ForeignKey(name = "fk_auth_user_role_user")), - inverseJoinColumns = @JoinColumn(name = "role_id", foreignKey = @ForeignKey(name = "fk_auth_user_role_role")) - ) + name = "auth_user_role", + joinColumns = + @JoinColumn( + name = "user_id", + foreignKey = @ForeignKey(name = "fk_auth_user_role_user")), + inverseJoinColumns = + @JoinColumn( + name = "role_id", + foreignKey = @ForeignKey(name = "fk_auth_user_role_role"))) private List roles; - /** - * 创建时间 - * 记录用户账号的创建时间 - */ + /** 创建时间 记录用户账号的创建时间 */ @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; - /** - * 更新时间 - * 记录用户信息的最后修改时间 - */ + /** 更新时间 记录用户信息的最后修改时间 */ @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - /** - * 创建人ID - * 记录创建该用户的管理员或系统ID - */ + /** 创建人ID 记录创建该用户的管理员或系统ID */ private UUID createdBy; - /** - * 持久化前回调 - * 在用户对象首次保存到数据库前自动设置创建时间和更新时间 - */ + /** 持久化前回调 在用户对象首次保存到数据库前自动设置创建时间和更新时间 */ @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } - /** - * 更新前回调 - * 在用户对象更新前自动设置更新时间 - */ + /** 更新前回调 在用户对象更新前自动设置更新时间 */ @PreUpdate public void preUpdate() { this.updatedAt = LocalDateTime.now(); } - /** - * 用户状态枚举 - * 定义用户的三种状态:正常、锁定、禁用 - */ + /** 用户状态枚举 定义用户的三种状态:正常、锁定、禁用 */ public enum UserStatus { /** 正常状态 - 用户可以正常登录和使用系统 */ ACTIVE("正常"), diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/UserProject.java b/module-auth/src/main/java/com/ether/pms/auth/entity/UserProject.java index e16078c..fb8b64c 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/entity/UserProject.java +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/UserProject.java @@ -1,16 +1,16 @@ package com.ether.pms.auth.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; /** * 用户项目关联实体类 * - *

表示用户与项目之间的关联关系,记录用户在特定项目中的角色和加入时间。

+ *

表示用户与项目之间的关联关系,记录用户在特定项目中的角色和加入时间。 * - *

一个用户可以关联多个项目,一个项目也可以关联多个用户,形成多对多关系。

+ *

一个用户可以关联多个项目,一个项目也可以关联多个用户,形成多对多关系。 * * @author Ether开发团队 * @version 1.0.0 @@ -21,50 +21,32 @@ import java.util.UUID; @Data public class UserProject { - /** - * 关联记录唯一标识符 - * 采用UUID策略自动生成 - */ + /** 关联记录唯一标识符 采用UUID策略自动生成 */ @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - /** - * 用户ID - * 关联的用户唯一标识符 - */ + /** 用户ID 关联的用户唯一标识符 */ @Column(name = "user_id", nullable = false) private UUID userId; - /** - * 项目ID - * 关联的项目唯一标识符 - */ + /** 项目ID 关联的项目唯一标识符 */ @Column(name = "project_id", nullable = false) private UUID projectId; - /** - * 在项目中的角色 - * 记录用户在关联项目中的角色,默认值为"member"(成员) - */ + /** 在项目中的角色 记录用户在关联项目中的角色,默认值为"member"(成员) */ @Column(name = "role_in_project", nullable = false) private String roleInProject = "member"; - /** - * 加入时间 - * 记录用户加入项目的时间,默认为当前时间 - */ + /** 加入时间 记录用户加入项目的时间,默认为当前时间 */ @Column(name = "joined_at", nullable = false) private LocalDateTime joinedAt = LocalDateTime.now(); - /** - * 持久化前回调 - * 在关联记录首次保存前,如果加入时间为空则设置为当前时间 - */ + /** 持久化前回调 在关联记录首次保存前,如果加入时间为空则设置为当前时间 */ @PrePersist public void prePersist() { if (this.joinedAt == null) { this.joinedAt = LocalDateTime.now(); } } -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/AuditLogRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/AuditLogRepository.java index d3bac43..45412f4 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/AuditLogRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/AuditLogRepository.java @@ -1,6 +1,9 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.AuditLog; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,41 +13,28 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - @Repository -public interface AuditLogRepository extends JpaRepository, JpaSpecificationExecutor { +public interface AuditLogRepository + extends JpaRepository, JpaSpecificationExecutor { - /** - * 查询最近30天的审计日志(分页) - */ + /** 查询最近30天的审计日志(分页) */ @Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC") Page findRecentLogs(@Param("startTime") LocalDateTime startTime, Pageable pageable); - /** - * 查询最近30天的审计日志(不分页) - */ + /** 查询最近30天的审计日志(不分页) */ @Query("SELECT a FROM AuditLog a WHERE a.createdAt >= :startTime ORDER BY a.createdAt DESC") List findRecentLogs(@Param("startTime") LocalDateTime startTime); - /** - * 删除超过指定时间的日志(归档用) - */ + /** 删除超过指定时间的日志(归档用) */ @Modifying @Query("DELETE FROM AuditLog a WHERE a.createdAt < :cutoffTime") int deleteByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime); - /** - * 查询超过指定时间的日志(归档用) - */ + /** 查询超过指定时间的日志(归档用) */ @Query("SELECT a FROM AuditLog a WHERE a.createdAt < :cutoffTime ORDER BY a.createdAt DESC") List findByCreatedAtBefore(@Param("cutoffTime") LocalDateTime cutoffTime); - /** - * 统计最近30天的日志数量 - */ + /** 统计最近30天的日志数量 */ @Query("SELECT COUNT(a) FROM AuditLog a WHERE a.createdAt >= :startTime") long countRecentLogs(@Param("startTime") LocalDateTime startTime); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/DataAccessRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/DataAccessRepository.java deleted file mode 100644 index 3329055..0000000 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/DataAccessRepository.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.ether.pms.auth.repository; - -import com.ether.pms.auth.entity.DataAccess; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@Repository -public interface DataAccessRepository extends JpaRepository { - - List findByDataTypeAndDataId(String dataType, UUID dataId); - - /** - * 根据多条件精确查询数据访问记录 - * - *

用于 grantAccess 方法中检查是否已存在相同的访问授权记录。

- * - * @param dataType 数据类型 - * @param dataId 数据ID - * @param accessType 访问类型 - * @param accessId 访问者ID - * @return 匹配的访问记录(如果存在) - */ - Optional findByDataTypeAndDataIdAndAccessTypeAndAccessId( - String dataType, UUID dataId, String accessType, UUID accessId); - - @Query("SELECT da FROM DataAccess da WHERE da.accessType = :accessType AND da.accessId = :accessId") - List findByAccessTypeAndAccessId(@Param("accessType") String accessType, @Param("accessId") UUID accessId); - - @Query("SELECT da.dataId FROM DataAccess da WHERE da.accessType = 'user' AND da.accessId = :userId AND da.dataType = :dataType") - List findDataIdsByUserAccess(@Param("userId") UUID userId, @Param("dataType") String dataType); - - void deleteByDataTypeAndDataId(String dataType, UUID dataId); -} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/DeptRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/DeptRepository.java index c8019f5..3648faf 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/DeptRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/DeptRepository.java @@ -1,17 +1,16 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.Dept; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.UUID; - /** * 部门数据访问层接口 * - *

提供部门数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供部门数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 @@ -23,7 +22,7 @@ public interface DeptRepository extends JpaRepository { /** * 查询顶级部门列表 * - *

查询没有上级部门的部门,即树形结构的根节点。

+ *

查询没有上级部门的部门,即树形结构的根节点。 * * @return 顶级部门列表 */ diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/EnterpriseUserRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/EnterpriseUserRepository.java index d7e4595..a542b9b 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/EnterpriseUserRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/EnterpriseUserRepository.java @@ -1,17 +1,16 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.EnterpriseUser; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; /** * 企业用户数据访问层接口 * - *

提供企业用户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供企业用户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java index 7490d48..2d3ead1 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java @@ -1,16 +1,15 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.Permission; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; /** * 权限数据访问层接口 * - *

提供权限(Permission)实体的数据库操作方法,继承Spring Data JPA的JpaRepository。 - * 支持按权限类型、父权限代码等条件查询。

+ *

提供权限(Permission)实体的数据库操作方法,继承Spring Data JPA的JpaRepository。 支持按权限类型、父权限代码等条件查询。 * * @author Ether开发团队 * @version 1.0.0 @@ -22,7 +21,7 @@ public interface PermissionRepository extends JpaRepository { /** * 根据权限类型查询权限列表 * - *

按权限类型筛选,如MENU(菜单)、BUTTON(按钮)、API(接口)等。

+ *

按权限类型筛选,如MENU(菜单)、BUTTON(按钮)、API(接口)等。 * * @param type 权限类型 * @return 该类型的所有权限列表 @@ -32,7 +31,7 @@ public interface PermissionRepository extends JpaRepository { /** * 根据父权限代码查询子权限列表 * - *

用于构建权限树形结构,查找属于某个父权限的所有子权限。

+ *

用于构建权限树形结构,查找属于某个父权限的所有子权限。 * * @param parentCode 父权限代码 * @return 该父权限下的所有子权限列表 @@ -42,10 +41,10 @@ public interface PermissionRepository extends JpaRepository { /** * 检查权限代码是否存在 * - *

用于创建权限时的唯一性校验。

+ *

用于创建权限时的唯一性校验。 * * @param code 权限代码 * @return 存在返回true,不存在返回false */ boolean existsByCode(String code); -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRepository.java index 6eeaac4..7ba99da 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRepository.java @@ -1,19 +1,17 @@ package com.ether.pms.auth.repository; +import com.ether.pms.auth.entity.ProjectStaff; import java.util.List; import java.util.Optional; import java.util.UUID; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import com.ether.pms.auth.entity.ProjectStaff; - /** * 项目员工数据访问层接口 * - *

提供项目员工数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供项目员工数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 @@ -52,11 +50,12 @@ public interface ProjectStaffRepository extends JpaRepository findByProjectIdWithRoles(UUID projectId); /** diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRoleRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRoleRepository.java index 7a244ee..d9fb81f 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRoleRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/ProjectStaffRoleRepository.java @@ -1,13 +1,11 @@ package com.ether.pms.auth.repository; +import com.ether.pms.auth.entity.ProjectStaffRole; import java.util.List; import java.util.UUID; - import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.ether.pms.auth.entity.ProjectStaffRole; - @Repository public interface ProjectStaffRoleRepository extends JpaRepository { @@ -34,4 +32,4 @@ public interface ProjectStaffRoleRepository extends JpaRepository提供住户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供住户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/ResidentSpaceRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/ResidentSpaceRepository.java index afa46ee..ea38a44 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/ResidentSpaceRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/ResidentSpaceRepository.java @@ -1,17 +1,16 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.ResidentSpace; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; /** * 住户房屋关联数据访问层接口 * - *

提供住户房屋关联数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供住户房屋关联数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java index 47a783f..aa20de2 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java @@ -1,18 +1,17 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.Role; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; /** * 角色数据访问层接口 * - *

提供角色(Role)实体的数据库操作方法,继承Spring Data JPA的JpaRepository。 - * 支持按角色代码、项目ID、类型等条件查询,以及带权限信息的复杂查询。

+ *

提供角色(Role)实体的数据库操作方法,继承Spring Data JPA的JpaRepository。 支持按角色代码、项目ID、类型等条件查询,以及带权限信息的复杂查询。 * * @author Ether开发团队 * @version 1.0.0 @@ -24,7 +23,7 @@ public interface RoleRepository extends JpaRepository { /** * 根据角色代码查询角色 * - *

角色代码在系统中唯一,用于精确查找特定角色。

+ *

角色代码在系统中唯一,用于精确查找特定角色。 * * @param code 角色代码 * @return 角色对象(如果存在) @@ -34,7 +33,7 @@ public interface RoleRepository extends JpaRepository { /** * 检查角色代码是否存在 * - *

用于创建角色时的唯一性校验。

+ *

用于创建角色时的唯一性校验。 * * @param code 角色代码 * @return 存在返回true,不存在返回false @@ -44,7 +43,7 @@ public interface RoleRepository extends JpaRepository { /** * 根据项目ID查询角色列表 * - *

获取指定项目下的所有角色,通常用于项目级别的角色筛选。

+ *

获取指定项目下的所有角色,通常用于项目级别的角色筛选。 * * @param projectId 项目ID * @return 该项目下的角色列表 @@ -54,7 +53,7 @@ public interface RoleRepository extends JpaRepository { /** * 根据角色类型查询角色列表 * - *

按角色类型(如系统级、项目级、部门级)筛选角色。

+ *

按角色类型(如系统级、项目级、部门级)筛选角色。 * * @param type 角色类型枚举 * @return 该类型的所有角色列表 @@ -64,12 +63,11 @@ public interface RoleRepository extends JpaRepository { /** * 根据角色ID查询角色及其关联的权限信息 * - *

使用EntityGraph eagerly加载permissions关联数据, - * 避免N+1查询问题,提升查询性能。

+ *

使用EntityGraph eagerly加载permissions关联数据, 避免N+1查询问题,提升查询性能。 * * @param id 角色ID * @return 包含权限信息的角色对象(如果存在) */ @EntityGraph(attributePaths = {"permissions"}) Optional findWithPermissionsById(UUID id); -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/SpaceRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/SpaceRepository.java index cd340cb..cee4810 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/SpaceRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/SpaceRepository.java @@ -1,17 +1,16 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.Space; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; /** * 房屋空间数据访问层接口 * - *

提供房屋空间数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供房屋空间数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * * @author Ether开发团队 * @version 1.0.0 @@ -37,5 +36,6 @@ public interface SpaceRepository extends JpaRepository { * @param roomNo 房号 * @return 包含房屋空间的Optional对象 */ - Optional findByProjectIdAndBuildingAndUnitAndRoomNo(UUID projectId, String building, String unit, String roomNo); + Optional findByProjectIdAndBuildingAndUnitAndRoomNo( + UUID projectId, String building, String unit, String roomNo); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/SysConfigRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/SysConfigRepository.java index 0edb8a1..32fd250 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/SysConfigRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/SysConfigRepository.java @@ -1,15 +1,13 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.SysConfig; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; -import java.util.UUID; - /** - * 系统配置数据访问层 - * 提供对 sys_config 表的 CRUD 操作 + * 系统配置数据访问层 提供对 sys_config 表的 CRUD 操作 * * @author Ether Team * @since 1.0.0 diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/UserProjectRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/UserProjectRepository.java index ee73234..3b55987 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/UserProjectRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/UserProjectRepository.java @@ -1,21 +1,21 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.UserProject; +import java.util.List; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.UUID; /** * 用户项目关联数据访问层接口 * - *

提供用户与项目关联关系的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供用户与项目关联关系的持久化操作,继承Spring Data JPA的JpaRepository接口。 * - *

支持用户项目关系查询、分页、批量操作等功能。

+ *

支持用户项目关系查询、分页、批量操作等功能。 * * @author Ether开发团队 * @version 1.0.0 @@ -27,7 +27,7 @@ public interface UserProjectRepository extends JpaRepository /** * 根据用户ID查询该用户关联的所有项目 * - *

返回指定用户参与的所有项目关联记录列表。

+ *

返回指定用户参与的所有项目关联记录列表。 * * @param userId 用户唯一标识符 * @return 该用户关联的所有项目列表 @@ -37,7 +37,7 @@ public interface UserProjectRepository extends JpaRepository /** * 根据项目ID查询该项目的所有关联用户 * - *

返回参与指定项目的所有用户关联记录列表。

+ *

返回参与指定项目的所有用户关联记录列表。 * * @param projectId 项目唯一标识符 * @return 该项目关联的所有用户列表 @@ -47,7 +47,7 @@ public interface UserProjectRepository extends JpaRepository /** * 根据项目ID分页查询该项目的所有关联用户 * - *

支持分页展示项目成员列表。

+ *

支持分页展示项目成员列表。 * * @param projectId 项目唯一标识符 * @param pageable 分页参数,包含页码和每页大小 @@ -58,7 +58,7 @@ public interface UserProjectRepository extends JpaRepository /** * 根据用户ID查询该用户关联的所有项目ID * - *

仅返回项目ID列表,用于快速判断用户参与的项目。

+ *

仅返回项目ID列表,用于快速判断用户参与的项目。 * * @param userId 用户唯一标识符 * @return 该用户关联的所有项目ID列表 @@ -69,7 +69,7 @@ public interface UserProjectRepository extends JpaRepository /** * 检查用户与项目的关联是否存在 * - *

用于判断用户是否参与了指定项目。

+ *

用于判断用户是否参与了指定项目。 * * @param userId 用户唯一标识符 * @param projectId 项目唯一标识符 @@ -80,15 +80,13 @@ public interface UserProjectRepository extends JpaRepository /** * 删除用户与项目的关联 * - *

根据用户ID和项目ID删除关联记录,用于用户退出项目或移除项目成员。

+ *

根据用户ID和项目ID删除关联记录,用于用户退出项目或移除项目成员。 * * @param userId 用户唯一标识符 * @param projectId 项目唯一标识符 */ void deleteByUserIdAndProjectId(UUID userId, UUID projectId); - /** - * 统计项目下的成员数量 - */ + /** 统计项目下的成员数量 */ long countByProjectId(UUID projectId); -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java index 34ee7de..f15dc36 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java @@ -1,22 +1,22 @@ package com.ether.pms.auth.repository; import com.ether.pms.auth.entity.User; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; /** * 用户数据访问层接口 * - *

提供用户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。

+ *

提供用户数据的持久化操作,继承Spring Data JPA的JpaRepository接口。 * - *

包含用户查询、角色关联查询等数据库操作方法。

+ *

包含用户查询、角色关联查询等数据库操作方法。 * * @author Ether开发团队 * @version 1.0.0 @@ -28,7 +28,7 @@ public interface UserRepository extends JpaRepository { /** * 根据用户名查询用户 * - *

最基础的按用户名查询方法,返回Optional包装的用户对象。

+ *

最基础的按用户名查询方法,返回Optional包装的用户对象。 * * @param username 用户名 * @return 包含用户的Optional对象,如果不存在则为空 @@ -38,7 +38,7 @@ public interface UserRepository extends JpaRepository { /** * 根据用户名查询用户及其关联的角色 * - *

使用LEFT JOIN FETCH预加载用户的角色信息,避免N+1查询问题。

+ *

使用LEFT JOIN FETCH预加载用户的角色信息,避免N+1查询问题。 * * @param username 用户名 * @return 包含用户及其角色的Optional对象 @@ -49,8 +49,9 @@ public interface UserRepository extends JpaRepository { /** * 查询所有用户及其关联的角色 * - *

一次性加载所有用户及其角色信息,适用于管理后台用户列表。

- *

警告:此方法会加载全表数据,建议使用分页版本。

+ *

一次性加载所有用户及其角色信息,适用于管理后台用户列表。 + * + *

警告:此方法会加载全表数据,建议使用分页版本。 * * @return 包含所有用户及其角色的列表 * @deprecated 建议使用 {@link #findAllWithRoles(Pageable)} 分页版本 @@ -62,7 +63,7 @@ public interface UserRepository extends JpaRepository { /** * 分页查询所有用户及其关联的角色 * - *

支持分页加载用户及其角色信息,避免内存溢出风险。

+ *

支持分页加载用户及其角色信息,避免内存溢出风险。 * * @param pageable 分页参数(页码、每页大小、排序等) * @return 包含用户及其角色的分页数据 @@ -73,7 +74,7 @@ public interface UserRepository extends JpaRepository { /** * 根据ID查询用户及其关联的角色 * - *

使用LEFT JOIN FETCH预加载用户的角色信息。

+ *

使用LEFT JOIN FETCH预加载用户的角色信息。 * * @param id 用户唯一标识符 * @return 包含用户及其角色的Optional对象 @@ -84,7 +85,7 @@ public interface UserRepository extends JpaRepository { /** * 根据角色ID查询所有拥有该角色的用户 * - *

用于查询具有特定角色的所有用户列表。

+ *

用于查询具有特定角色的所有用户列表。 * * @param roleId 角色唯一标识符 * @return 拥有该角色的所有用户列表 @@ -95,7 +96,7 @@ public interface UserRepository extends JpaRepository { /** * 检查用户名是否存在 * - *

用于用户注册时验证用户名唯一性。

+ *

用于用户注册时验证用户名唯一性。 * * @param username 用户名 * @return 存在返回true,不存在返回false @@ -105,7 +106,7 @@ public interface UserRepository extends JpaRepository { /** * 检查手机号是否存在 * - *

用于用户注册或更新时验证手机号唯一性。

+ *

用于用户注册或更新时验证手机号唯一性。 * * @param phone 手机号码 * @return 存在返回true,不存在返回false @@ -115,7 +116,7 @@ public interface UserRepository extends JpaRepository { /** * 根据用户类型查询用户列表 * - *

用于查询特定类型的用户,如ENTERPRISE、PROJECT_STAFF等。

+ *

用于查询特定类型的用户,如ENTERPRISE、PROJECT_STAFF等。 * * @param userType 用户类型 * @return 该类型的所有用户列表 @@ -125,7 +126,7 @@ public interface UserRepository extends JpaRepository { /** * 根据部门ID查询用户列表 * - *

用于查询属于特定部门的所有用户。

+ *

用于查询属于特定部门的所有用户。 * * @param deptId 部门唯一标识符 * @return 该部门下的所有用户列表 @@ -135,11 +136,12 @@ public interface UserRepository extends JpaRepository { /** * 根据项目ID查询项目员工 * - *

通过关联ProjectStaff表查询属于指定项目的所有员工用户。

+ *

通过关联ProjectStaff表查询属于指定项目的所有员工用户。 * * @param projectId 项目唯一标识符 * @return该项目下的所有员工用户列表 */ - @Query("SELECT u FROM User u JOIN ProjectStaff ps ON u.id = ps.userId WHERE ps.projectId = :projectId") + @Query( + "SELECT u FROM User u JOIN ProjectStaff ps ON u.id = ps.userId WHERE ps.projectId = :projectId") List findProjectStaffsByProjectId(@Param("projectId") UUID projectId); -} \ No newline at end of file +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/scheduler/AuditLogArchiveTask.java b/module-auth/src/main/java/com/ether/pms/auth/scheduler/AuditLogArchiveTask.java index b784975..a1eea0e 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/scheduler/AuditLogArchiveTask.java +++ b/module-auth/src/main/java/com/ether/pms/auth/scheduler/AuditLogArchiveTask.java @@ -13,10 +13,7 @@ public class AuditLogArchiveTask { private final AuditLogService auditLogService; - /** - * 每天凌晨2点执行归档任务 - * 将超过90天的审计日志归档(当前实现为删除,实际应导出到文件/对象存储) - */ + /** 每天凌晨2点执行归档任务 将超过90天的审计日志归档(当前实现为删除,实际应导出到文件/对象存储) */ @Scheduled(cron = "0 0 2 * * ?") public void archiveOldAuditLogs() { log.info("开始执行审计日志归档任务..."); diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/AuditLogService.java b/module-auth/src/main/java/com/ether/pms/auth/service/AuditLogService.java index 4611ac9..fddcb1d 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/AuditLogService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/AuditLogService.java @@ -1,138 +1,27 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.AuditLog; -import com.ether.pms.auth.repository.AuditLogRepository; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.persistence.criteria.Predicate; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; +public interface AuditLogService { -@Service -@RequiredArgsConstructor -@Slf4j -public class AuditLogService { + void saveAuditLogAsync(AuditLog auditLog); - private final AuditLogRepository auditLogRepository; - private final ObjectMapper objectMapper; + AuditLog saveAuditLog(AuditLog auditLog); - /** - * 异步保存审计日志 - */ - @Async("auditLogExecutor") - public void saveAuditLogAsync(AuditLog auditLog) { - try { - auditLogRepository.save(auditLog); - } catch (Exception e) { - log.error("保存审计日志失败: {}", e.getMessage(), e); - } - } + Page findRecentLogs(Pageable pageable); - /** - * 同步保存审计日志(用于需要立即确认的场景) - */ - @Transactional - public AuditLog saveAuditLog(AuditLog auditLog) { - return auditLogRepository.save(auditLog); - } + Page searchLogs( + String module, + String action, + String username, + LocalDateTime startDate, + LocalDateTime endDate, + Pageable pageable); - /** - * 分页查询审计日志(最近30天) - */ - public Page findRecentLogs(Pageable pageable) { - LocalDateTime startTime = LocalDateTime.now().minusDays(30); - return auditLogRepository.findRecentLogs(startTime, pageable); - } + int archiveOldLogs(); - /** - * 条件查询审计日志(最近30天内) - */ - public Page searchLogs(String module, String action, String username, - LocalDateTime startDate, LocalDateTime endDate, - Pageable pageable) { - // 确保查询范围不超过30天 - LocalDateTime maxStartDate = LocalDateTime.now().minusDays(30); - if (startDate == null || startDate.isBefore(maxStartDate)) { - startDate = maxStartDate; - } - if (endDate == null) { - endDate = LocalDateTime.now(); - } - - final LocalDateTime finalStartDate = startDate; - final LocalDateTime finalEndDate = endDate; - - Specification spec = (root, query, cb) -> { - List predicates = new ArrayList<>(); - - // 时间范围 - predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), finalStartDate)); - predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), finalEndDate)); - - // 模块筛选 - if (module != null && !module.isEmpty()) { - predicates.add(cb.equal(root.get("module"), module)); - } - - // 操作类型筛选 - if (action != null && !action.isEmpty()) { - predicates.add(cb.equal(root.get("action"), AuditLog.ActionType.valueOf(action))); - } - - // 用户名筛选 - if (username != null && !username.isEmpty()) { - predicates.add(cb.like(root.get("username"), "%" + username + "%")); - } - - return cb.and(predicates.toArray(new Predicate[0])); - }; - - return auditLogRepository.findAll(spec, pageable); - } - - /** - * 归档超过90天的日志 - */ - @Transactional - public int archiveOldLogs() { - LocalDateTime cutoffTime = LocalDateTime.now().minusDays(90); - - // 查询需要归档的日志 - List oldLogs = auditLogRepository.findByCreatedAtBefore(cutoffTime); - - if (oldLogs.isEmpty()) { - log.info("没有需要归档的审计日志"); - return 0; - } - - // TODO: 将日志导出到文件或对象存储 - // 这里简化处理,直接删除 - // 实际项目中应该: - // 1. 将数据导出为JSON/Parquet文件 - // 2. 上传到对象存储(MinIO/S3) - // 3. 记录归档信息 - // 4. 然后删除数据库记录 - - int deleted = auditLogRepository.deleteByCreatedAtBefore(cutoffTime); - log.info("归档审计日志完成,共归档 {} 条记录", deleted); - - return deleted; - } - - /** - * 获取最近30天的日志统计 - */ - public long getRecentLogCount() { - LocalDateTime startTime = LocalDateTime.now().minusDays(30); - return auditLogRepository.countRecentLogs(startTime); - } + long getRecentLogCount(); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/DataAccessService.java b/module-auth/src/main/java/com/ether/pms/auth/service/DataAccessService.java deleted file mode 100644 index 98997b9..0000000 --- a/module-auth/src/main/java/com/ether/pms/auth/service/DataAccessService.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.ether.pms.auth.service; - -import com.ether.pms.auth.entity.DataAccess; -import com.ether.pms.auth.repository.DataAccessRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.*; - -@Service -@RequiredArgsConstructor -public class DataAccessService { - - private final DataAccessRepository dataAccessRepository; - - @Transactional - public DataAccess grantAccess(String dataType, UUID dataId, String accessType, UUID accessId, String level, UUID grantedBy) { - // 使用精确查询替代全表扫描,避免 OOM 风险 - Optional existing = dataAccessRepository - .findByDataTypeAndDataIdAndAccessTypeAndAccessId(dataType, dataId, accessType, accessId); - - if (existing.isPresent()) { - DataAccess existingAccess = existing.get(); - existingAccess.setAccessLevel(level); - existingAccess.setGrantedBy(grantedBy); - return dataAccessRepository.save(existingAccess); - } - - DataAccess access = new DataAccess(); - access.setDataType(dataType); - access.setDataId(dataId); - access.setAccessType(accessType); - access.setAccessId(accessId); - access.setAccessLevel(level); - access.setGrantedBy(grantedBy); - return dataAccessRepository.save(access); - } - - @Transactional - public void revokeAccess(UUID accessId) { - dataAccessRepository.deleteById(accessId); - } - - public List getDataAccess(String dataType, UUID dataId) { - return dataAccessRepository.findByDataTypeAndDataId(dataType, dataId); - } - - public List getAccessibleDataIds(String dataType, UUID userId) { - return dataAccessRepository.findDataIdsByUserAccess(userId, dataType); - } -} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/DataScopeService.java b/module-auth/src/main/java/com/ether/pms/auth/service/DataScopeService.java index 98d2031..1d349db 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/DataScopeService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/DataScopeService.java @@ -1,29 +1,15 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.Role; -import com.ether.pms.auth.repository.UserProjectRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import java.util.*; +import java.util.List; +import java.util.Set; +import java.util.UUID; -@Service -@RequiredArgsConstructor -public class DataScopeService { +public interface DataScopeService { - private final UserProjectRepository userProjectRepository; + List getPermittedProjectIds(UUID userId, Set roles, boolean hasAllScope); - public List getPermittedProjectIds(UUID userId, Set roles, boolean hasAllScope) { - if (hasAllScope) { - return Collections.emptyList(); - } - return userProjectRepository.findProjectIdsByUserId(userId); - } + boolean canAccessAllData(Set roles); - public boolean canAccessAllData(Set roles) { - return roles.stream().anyMatch(r -> r.getDataScope() == Role.DataScope.ALL); - } - - public boolean isSelfScopeOnly(Set roles) { - return roles.stream().allMatch(r -> r.getDataScope() == Role.DataScope.SELF); - } + boolean isSelfScopeOnly(Set roles); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/DeptService.java b/module-auth/src/main/java/com/ether/pms/auth/service/DeptService.java index eca8fb6..cff0ff1 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/DeptService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/DeptService.java @@ -2,141 +2,25 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.Dept; import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.repository.DeptRepository; -import com.ether.pms.auth.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.List; import java.util.Optional; import java.util.UUID; -/** - * 部门服务层 - * - *

提供部门相关的业务逻辑处理,包括部门树查询、部门CRUD、部门员工查询等功能。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class DeptService { +public interface DeptService { - private final DeptRepository deptRepository; - private final UserRepository userRepository; + List getDeptTree(); - /** - * 获取部门树 - * - *

返回所有部门的列表,支持前端构建树形结构。

- * - * @return 所有部门的列表 - */ - public List getDeptTree() { - return deptRepository.findAllForTree(); - } + List getActiveDepts(); - /** - * 获取所有启用的部门列表 - * - * @return 启用的部门列表 - */ - public List getActiveDepts() { - return deptRepository.findByStatusOrderBySortOrder("ACTIVE"); - } + Optional getById(UUID id); - /** - * 根据ID获取部门 - * - * @param id 部门ID - * @return 部门对象 - */ - public Optional getById(UUID id) { - return deptRepository.findById(id); - } + List getDeptEmployees(UUID deptId); - /** - * 获取部门及下属员工 - * - *

根据部门ID查询该部门下的所有员工用户。

- * - * @param deptId 部门唯一标识符 - * @return 该部门下的所有员工用户列表 - */ - public List getDeptEmployees(UUID deptId) { - return userRepository.findByDeptId(deptId); - } + Dept createDept(Dept dept); - /** - * 创建部门 - * - * @param dept 待创建的部门对象 - * @return 创建成功的部门对象 - */ - @Transactional - public Dept createDept(Dept dept) { - if (dept.getSortOrder() == null) { - dept.setSortOrder(0); - } - if (dept.getStatus() == null) { - dept.setStatus("ACTIVE"); - } - if (dept.getDeptType() == null) { - dept.setDeptType("ADMIN"); - } - return deptRepository.save(dept); - } + Dept updateDept(UUID id, Dept dept); - /** - * 更新部门 - * - * @param id 部门ID - * @param dept 更新后的部门信息 - * @return 更新后的部门对象 - */ - @Transactional - public Dept updateDept(UUID id, Dept dept) { - Dept existing = deptRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("部门不存在: " + id)); + void deleteDept(UUID id); - existing.setDeptName(dept.getDeptName()); - existing.setParentId(dept.getParentId()); - existing.setDeptType(dept.getDeptType()); - existing.setLeaderId(dept.getLeaderId()); - existing.setSortOrder(dept.getSortOrder()); - existing.setStatus(dept.getStatus()); - - return deptRepository.save(existing); - } - - /** - * 删除部门 - * - * @param id 部门ID - */ - @Transactional - public void deleteDept(UUID id) { - Dept dept = deptRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("部门不存在: " + id)); - - List children = deptRepository.findByParentIdOrderBySortOrder(id); - if (!children.isEmpty()) { - throw new IllegalStateException("请先删除子部门"); - } - - deptRepository.delete(dept); - } - - /** - * 根据部门类型查询部门 - * - * @param deptType 部门类型 - * @return 该类型的部门列表 - */ - public List getByType(String deptType) { - return deptRepository.findByDeptTypeOrderBySortOrder(deptType); - } + List getByType(String deptType); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/LoginAttemptService.java b/module-auth/src/main/java/com/ether/pms/auth/service/LoginAttemptService.java index bec68c2..4148c84 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/LoginAttemptService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/LoginAttemptService.java @@ -1,80 +1,22 @@ package com.ether.pms.auth.service; -import com.ether.pms.common.util.LogMaskUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.stereotype.Service; +public interface LoginAttemptService { -import java.time.Duration; + void recordFailure(String username); -@Service -@RequiredArgsConstructor -@ConfigurationProperties(prefix = "login") -@Slf4j -public class LoginAttemptService { - - private int maxAttempts = 5; - private int lockoutDurationMinutes = 10; - - private final StringRedisTemplate redisTemplate; - - private static final String KEY_PREFIX = "login:attempt:"; - - public void recordFailure(String username) { - String key = KEY_PREFIX + username; - Long attempts = redisTemplate.opsForValue().increment(key); - if (attempts != null && attempts == 1) { - redisTemplate.expire(key, Duration.ofMinutes(lockoutDurationMinutes)); - } - log.warn("用户 {} 登录失败,当前失败次数: {}", username, attempts); - } - - public void recordSuccess(String username) { - String key = KEY_PREFIX + username; - redisTemplate.delete(key); - log.info("用户 {} 登录成功,已清除登录失败记录", username); - } - - public boolean isLockedOut(String username) { - String key = KEY_PREFIX + username; - String value = redisTemplate.opsForValue().get(key); - if (value == null) { - return false; - } - int attempts = Integer.parseInt(value); - return attempts >= maxAttempts; - } - - public int getRemainingAttempts(String username) { - String key = KEY_PREFIX + username; - String value = redisTemplate.opsForValue().get(key); - if (value == null) { - return maxAttempts; - } - int attempts = Integer.parseInt(value); - return Math.max(0, maxAttempts - attempts); - } - - public void unlock(String username) { - String key = KEY_PREFIX + username; - redisTemplate.delete(key); - } - - public int getMaxAttempts() { - return maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public int getLockoutDurationMinutes() { - return lockoutDurationMinutes; - } - - public void setLockoutDurationMinutes(int lockoutDurationMinutes) { - this.lockoutDurationMinutes = lockoutDurationMinutes; - } + void recordSuccess(String username); + + boolean isLockedOut(String username); + + int getRemainingAttempts(String username); + + void unlock(String username); + + int getMaxAttempts(); + + void setMaxAttempts(int maxAttempts); + + int getLockoutDurationMinutes(); + + void setLockoutDurationMinutes(int lockoutDurationMinutes); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java b/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java index bdf0eee..8795037 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java @@ -1,82 +1,8 @@ package com.ether.pms.auth.service; -import com.ether.pms.auth.entity.ProjectStaff; -import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.repository.ProjectStaffRepository; -import com.ether.pms.auth.repository.UserRepository; -import com.ether.pms.auth.util.JwtTokenProvider; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import java.util.Map; -import java.util.*; -import java.util.stream.Collectors; +public interface LoginService { -@Service -@RequiredArgsConstructor -public class LoginService { - - private final UserRepository userRepository; - private final ProjectStaffRepository projectStaffRepository; - private final JwtTokenProvider jwtTokenProvider; - private final PasswordService passwordService; - private final LoginAttemptService loginAttemptService; - - @Transactional - public Map login(String username, String password, String ip) { - if (loginAttemptService.isLockedOut(username)) { - throw new BusinessException(ErrorCode.AUTH_002); - } - - User user = userRepository.findByUsernameWithRoles(username).orElse(null); - - if (user == null || !passwordService.matches(password, user.getPassword())) { - if (user != null) { - loginAttemptService.recordFailure(username); - } - throw new BusinessException(ErrorCode.AUTH_001); - } - - if (user.getStatus() == User.UserStatus.LOCKED) { - throw new BusinessException(ErrorCode.AUTH_002); - } - if (user.getStatus() == User.UserStatus.DISABLED) { - throw new BusinessException(ErrorCode.AUTH_003); - } - - loginAttemptService.recordSuccess(username); - - Set allRoles = new HashSet<>(); - if (user.getRoles() != null) { - allRoles.addAll(user.getRoles().stream() - .map(r -> r.getCode()) - .toList()); - } - - List projectStaffs = projectStaffRepository.findAllByUserId(user.getId()); - for (ProjectStaff staff : projectStaffs) { - if (staff.getStaffRoles() != null) { - allRoles.addAll(staff.getStaffRoles().stream() - .filter(sr -> sr.getRole() != null) - .map(sr -> sr.getRole().getCode()) - .toList()); - } - } - - Map claims = new HashMap<>(); - claims.put("roles", new ArrayList<>(allRoles)); - - String token = jwtTokenProvider.generateToken(user.getId(), user.getUsername(), claims); - - Map result = new HashMap<>(); - result.put("token", token); - result.put("userId", user.getId()); - result.put("username", user.getUsername()); - result.put("realName", user.getRealName()); - result.put("roles", new ArrayList<>(allRoles)); - - return result; - } + Map login(String username, String password, String ip); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/PasswordService.java b/module-auth/src/main/java/com/ether/pms/auth/service/PasswordService.java index 825481d..026bed0 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/PasswordService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/PasswordService.java @@ -1,307 +1,42 @@ package com.ether.pms.auth.service; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import com.ether.pms.common.util.LogMaskUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; +public interface PasswordService { -import java.util.regex.Pattern; + String encode(String rawPassword); -/** - * 密码服务 - * - *

提供密码加密、验证和强度校验功能。

- * - *

安全特性:

- *
    - *
  • 使用 BCrypt 算法进行密码哈希(自动处理盐值)
  • - *
  • 检测旧的非 BCrypt 格式密码,强制用户重置
  • - *
  • 可配置的密码强度校验规则
  • - *
  • 弱密码检测(常见弱密码黑名单)
  • - *
- * - * @author Ether开发团队 - * @version 2.0.0 - * @since 2024-01-01 - */ -@Service -@ConfigurationProperties(prefix = "password") -@Slf4j -public class PasswordService { + boolean matches(String rawPassword, String encodedPassword); - /** 密码最小长度 */ - private int minLength = 8; + void validateStrength(String password); - /** 密码最大长度 */ - private int maxLength = 20; + boolean isWeakPassword(String password); - /** 是否要求大写字母 */ - private boolean requireUppercase = true; + boolean isBcryptFormat(String encodedPassword); - /** 是否要求小写字母 */ - private boolean requireLowercase = true; + int getMinLength(); - /** 是否要求数字 */ - private boolean requireDigit = true; + void setMinLength(int minLength); - /** 是否要求特殊字符 */ - private boolean requireSpecial = true; + int getMaxLength(); - /** 允许的特殊字符集合 */ - private String specialChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"; + void setMaxLength(int maxLength); - /** 密码编码器(BCrypt) */ - private final PasswordEncoder passwordEncoder; + boolean isRequireUppercase(); - /** - * 构造函数:注入 Spring 管理的 PasswordEncoder Bean - * - * @param passwordEncoder BCryptPasswordEncoder 实例(由 SecurityConfig 提供) - */ - @Autowired - public PasswordService(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - log.info("PasswordService 初始化完成,使用 BCrypt 编码器"); - } + void setRequireUppercase(boolean requireUppercase); - /** - * 加密密码 - * - *

使用 BCrypt 算法对原始密码进行单向哈希。

- *

BCrypt 自动生成随机盐值并包含在输出中,无需单独存储盐值。

- * - * @param rawPassword 原始密码(明文) - * @return BCrypt 格式的密码哈希(以 $2a$ 或 $2b$ 开头) - * @throws IllegalArgumentException 如果密码为空或空白 - */ - public String encode(String rawPassword) { - if (rawPassword == null || rawPassword.isBlank()) { - log.warn("尝试加密空密码"); - throw new IllegalArgumentException("密码不能为空"); - } + boolean isRequireLowercase(); - String encodedPassword = passwordEncoder.encode(rawPassword); - log.debug("密码加密成功,哈希格式: {}", LogMaskUtil.maskPasswordHash(encodedPassword)); - return encodedPassword; - } + void setRequireLowercase(boolean requireLowercase); - /** - * 验证密码 - * - *

将原始密码与已存储的 BCrypt 哈希进行比对。

- * - *

安全特性:

- *
    - *
  • 自动检测非 BCrypt 格式的旧密码,返回 false 并记录警告
  • - *
  • 强制使用新格式的用户重置密码
  • - *
  • 防止时序攻击(BCrypt 内置保护)
  • - *
- * - * @param rawPassword 原始密码(明文) - * @param encodedPassword 已存储的密码哈希 - * @return 如果密码匹配返回 true,否则返回 false - */ - public boolean matches(String rawPassword, String encodedPassword) { - if (rawPassword == null || encodedPassword == null) { - log.debug("密码验证失败:输入参数为空"); - return false; - } + boolean isRequireDigit(); - // 安全检查:检测非 BCrypt 格式的旧密码 - if (!isBcryptFormat(encodedPassword)) { - log.warn( - "检测到非BCrypt格式的密码哈希(前缀: {})," + - "可能使用了不安全的哈希算法(MD5/SHA-1等)," + - "建议用户立即重置密码以升级到 BCrypt", - encodedPassword.length() > 7 ? encodedPassword.substring(0, 7) : "N/A" - ); - return false; // 强制用户使用新格式 - } + void setRequireDigit(boolean requireDigit); - boolean matches = passwordEncoder.matches(rawPassword, encodedPassword); + boolean isRequireSpecial(); - if (!matches) { - log.debug("密码验证失败"); - } + void setRequireSpecial(boolean requireSpecial); - return matches; - } + String getSpecialChars(); - /** - * 密码强度校验 - * - *

根据配置的规则验证密码强度,包括:

- *
    - *
  • 长度要求(minLength ~ maxLength)
  • - *
  • 字符复杂度(大小写、数字、特殊字符)
  • - *
  • 弱密码检测
  • - *
- * - * @param password 待校验的密码 - * @throws BusinessException 如果密码不符合强度要求 - */ - public void validateStrength(String password) { - if (password == null || password.isBlank()) { - throw new BusinessException(ErrorCode.PASSWORD_001); - } - - if (password.length() < minLength) { - throw new BusinessException(ErrorCode.PASSWORD_002); - } - - if (password.length() > maxLength) { - throw new BusinessException(ErrorCode.PASSWORD_003); - } - - if (requireUppercase && !Pattern.compile("[A-Z]").matcher(password).find()) { - throw new BusinessException(ErrorCode.PASSWORD_004); - } - - if (requireLowercase && !Pattern.compile("[a-z]").matcher(password).find()) { - throw new BusinessException(ErrorCode.PASSWORD_005); - } - - if (requireDigit && !Pattern.compile("[0-9]").matcher(password).find()) { - throw new BusinessException(ErrorCode.PASSWORD_006); - } - - if (requireSpecial && !containsAny(password, specialChars.toCharArray())) { - throw new BusinessException(ErrorCode.PASSWORD_007); - } - - // 弱密码检测 - if (isWeakPassword(password)) { - throw new BusinessException(ErrorCode.PASSWORD_008); - } - } - - /** - * 检查是否为弱密码 - * - *

检测常见弱密码模式:

- *
    - *
  • 常见密码词(password、123456、admin 等)
  • - *
  • 连续或重复字符
  • - *
- * - * @param password 待检测的密码 - * @return 如果是弱密码返回 true - */ - public boolean isWeakPassword(String password) { - if (password == null || password.length() < 8) { - return true; - } - - String lower = password.toLowerCase(); - - // 常见弱密码黑名单 - String[] weakPatterns = { - "password", "123456", "admin", "qwerty", - "letmein", "welcome", "monkey", "dragon" - }; - - for (String pattern : weakPatterns) { - if (lower.contains(pattern)) { - log.debug("检测到弱密码模式: {}", pattern); - return true; - } - } - - return false; - } - - /** - * 检查密码哈希是否为 BCrypt 格式 - * - * @param encodedPassword 密码哈希 - * @return 如果是 BCrypt 格式返回 true - */ - public boolean isBcryptFormat(String encodedPassword) { - if (encodedPassword == null || encodedPassword.length() < 7) { - return false; - } - - // BCrypt 格式: $2a$、$2b$、$2y$ - return encodedPassword.startsWith("$2a$") || - encodedPassword.startsWith("$2b$") || - encodedPassword.startsWith("$2y$"); - } - - /** - * 检查字符串是否包含指定字符集中的任意字符 - * - * @param str 待检查的字符串 - * @param chars 字符集 - * @return 如果包含任意字符返回 true - */ - private boolean containsAny(String str, char... chars) { - for (char c : chars) { - if (str.indexOf(c) >= 0) { - return true; - } - } - return false; - } - - // ==================== Getter 和 Setter 方法 ==================== - - public int getMinLength() { - return minLength; - } - - public void setMinLength(int minLength) { - this.minLength = minLength; - } - - public int getMaxLength() { - return maxLength; - } - - public void setMaxLength(int maxLength) { - this.maxLength = maxLength; - } - - public boolean isRequireUppercase() { - return requireUppercase; - } - - public void setRequireUppercase(boolean requireUppercase) { - this.requireUppercase = requireUppercase; - } - - public boolean isRequireLowercase() { - return requireLowercase; - } - - public void setRequireLowercase(boolean requireLowercase) { - this.requireLowercase = requireLowercase; - } - - public boolean isRequireDigit() { - return requireDigit; - } - - public void setRequireDigit(boolean requireDigit) { - this.requireDigit = requireDigit; - } - - public boolean isRequireSpecial() { - return requireSpecial; - } - - public void setRequireSpecial(boolean requireSpecial) { - this.requireSpecial = requireSpecial; - } - - public String getSpecialChars() { - return specialChars; - } - - public void setSpecialChars(String specialChars) { - this.specialChars = specialChars; - } + void setSpecialChars(String specialChars); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java b/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java index 085d7e8..22cd3f9 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java @@ -1,174 +1,38 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.Permission; -import com.ether.pms.auth.repository.PermissionRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - +import com.ether.pms.auth.vo.MenuVO; +import com.ether.pms.auth.vo.PermissionTreeVO; +import com.ether.pms.auth.vo.PermissionVO; import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; -/** - * 权限业务逻辑服务类 - * - *

提供权限(Permission)相关的业务操作,包括权限的增删改查、类型筛选、菜单权限获取等功能。 - * 所有写操作均支持事务管理,确保数据一致性。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class PermissionService { +public interface PermissionService { - /** 权限数据访问仓库 */ - private final PermissionRepository permissionRepository; + Page findAll(Pageable pageable); - /** - * 分页查询所有权限 - * - * @param pageable 分页参数 - * @return 权限分页数据 - */ - public Page findAll(Pageable pageable) { - return permissionRepository.findAll(pageable); - } - - /** - * 查询所有权限(不分页,仅用于内部小规模查询) - * - *

警告:此方法会加载全表数据,仅在确认数据量较小时使用(如权限下拉选择)。

- * - * @return 所有权限的列表 - * @deprecated 建议使用 {@link #findAll(Pageable)} 分页版本 - */ @Deprecated - public List findAll() { - return permissionRepository.findAll(); - } + List findAll(); - /** - * 根据权限ID查询权限 - * - *

如果权限不存在,抛出PERMISSION_002业务异常。

- * - * @param id 权限ID - * @return 权限对象 - * @throws BusinessException 如果权限不存在 - */ - public Permission findById(UUID id) { - return permissionRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.PERMISSION_002)); - } + Permission findById(UUID id); - /** - * 根据权限类型查询权限列表 - * - *

按权限类型筛选,如MENU、BUTTON、API等。

- * - * @param type 权限类型 - * @return 该类型的所有权限列表 - */ - public List findByType(String type) { - return permissionRepository.findByType(type); - } + List findByType(String type); - /** - * 根据父权限代码查询子权限列表 - * - *

用于获取某个父权限下的所有子权限,构建权限树。

- * - * @param parentCode 父权限代码 - * @return 该父权限下的所有子权限列表 - */ - public List findByParentCode(String parentCode) { - return permissionRepository.findByParentCode(parentCode); - } + List findByParentCode(String parentCode); - /** - * 查询所有菜单类型权限 - * - *

获取type为MENU的所有权限,通常用于前端菜单渲染。

- * - * @return 所有菜单权限列表 - */ - public List findMenuPermissions() { - return permissionRepository.findByType("MENU"); - } + List findMenuPermissions(); - /** - * 创建新权限 - * - *

如果权限代码已存在,抛出PERMISSION_001业务异常。 - * 创建时会自动设置创建时间和更新时间。

- * - * @param permission 要创建的权限对象 - * @return 创建后的权限对象(包含数据库生成的主键) - * @throws BusinessException 如果权限代码已存在 - */ - @Transactional - public Permission create(Permission permission) { - if (permissionRepository.existsByCode(permission.getCode())) { - throw new BusinessException(ErrorCode.PERMISSION_001); - } - return permissionRepository.save(permission); - } + Permission create(Permission permission); - /** - * 更新权限信息 - * - *

仅更新传入参数中非空的字段,保持其他字段不变。 - * 如果权限不存在,抛出PERMISSION_002业务异常。

- * - * @param id 要更新的权限ID - * @param permission 包含新数据的权限对象 - * @return 更新后的权限对象 - * @throws BusinessException 如果权限不存在 - */ - @Transactional - public Permission update(UUID id, Permission permission) { - Permission existing = findById(id); + Permission update(UUID id, Permission permission); - if (permission.getName() != null) { - existing.setName(permission.getName()); - } - if (permission.getType() != null) { - existing.setType(permission.getType()); - } - if (permission.getResource() != null) { - existing.setResource(permission.getResource()); - } - if (permission.getMethod() != null) { - existing.setMethod(permission.getMethod()); - } - if (permission.getDescription() != null) { - existing.setDescription(permission.getDescription()); - } - if (permission.getParentCode() != null) { - existing.setParentCode(permission.getParentCode()); - } - if (permission.getSortOrder() != null) { - existing.setSortOrder(permission.getSortOrder()); - } + void delete(UUID id); - return permissionRepository.save(existing); - } + List getPermissionTree(); - /** - * 删除权限 - * - *

根据权限ID删除权限记录。

- * - * @param id 要删除的权限ID - */ - @Transactional - public void delete(UUID id) { - permissionRepository.deleteById(id); - } -} \ No newline at end of file + List getUserMenus(UUID userId, UUID projectId); + + PermissionVO getUserPermissions(UUID userId, UUID projectId); +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java b/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java index b6469f4..a789241 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java @@ -1,235 +1,37 @@ package com.ether.pms.auth.service; -import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.repository.RoleRepository; -import com.ether.pms.auth.repository.PermissionRepository; -import com.ether.pms.auth.repository.UserRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; -/** - * 角色业务逻辑服务类 - * - *

提供角色(Role)相关的业务操作,包括角色的增删改查、权限分配、用户关联等功能。 - * 所有操作均支持事务管理,确保数据一致性。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class RoleService { +public interface RoleService { - /** 角色数据访问仓库 */ - private final RoleRepository roleRepository; + Page findAll(Pageable pageable); - /** 权限数据访问仓库 */ - private final PermissionRepository permissionRepository; - - /** 用户数据访问仓库 */ - private final UserRepository userRepository; - - /** - * 分页查询所有角色 - * - * @param pageable 分页参数 - * @return 角色分页数据 - */ - public Page findAll(Pageable pageable) { - return roleRepository.findAll(pageable); - } - - /** - * 查询所有角色(不分页,仅用于内部小规模查询) - * - *

警告:此方法会加载全表数据,仅在确认数据量较小时使用(如角色下拉选择)。

- * - * @return 所有角色的列表 - * @deprecated 建议使用 {@link #findAll(Pageable)} 分页版本 - */ @Deprecated - public List findAll() { - return roleRepository.findAll(); - } + List findAll(); - /** - * 根据角色ID查询角色 - * - *

如果角色不存在,抛出ROLE_002业务异常。

- * - * @param id 角色ID - * @return 角色对象 - * @throws BusinessException 如果角色不存在 - */ - public Role findById(UUID id) { - return roleRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); - } + Role findById(UUID id); - /** - * 根据角色代码查询角色 - * - *

如果角色不存在,抛出ROLE_002业务异常。

- * - * @param code 角色代码 - * @return 角色对象 - * @throws BusinessException 如果角色不存在 - */ - public Role findByCode(String code) { - return roleRepository.findByCode(code) - .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); - } + Role findByCode(String code); - /** - * 根据项目ID查询角色列表 - * - *

获取指定项目下的所有角色。

- * - * @param projectId 项目ID - * @return 该项目下的角色列表 - */ - public List findByProjectId(UUID projectId) { - return roleRepository.findByProjectId(projectId); - } + List findByProjectId(UUID projectId); - /** - * 创建新角色 - * - *

如果角色代码已存在,抛出ROLE_001业务异常。 - * 创建时会自动设置创建时间和更新时间。

- * - * @param role 要创建的角色对象 - * @return 创建后的角色对象(包含数据库生成的主键) - * @throws BusinessException 如果角色代码已存在 - */ - @Transactional - public Role create(Role role) { - if (roleRepository.existsByCode(role.getCode())) { - throw new BusinessException(ErrorCode.ROLE_001); - } - return roleRepository.save(role); - } + Role create(Role role); - /** - * 更新角色信息 - * - *

仅更新传入参数中非空的字段,保持其他字段不变。 - * 如果角色不存在,抛出ROLE_002业务异常。

- * - * @param id 要更新的角色ID - * @param role 包含新数据的角色对象 - * @return 更新后的角色对象 - * @throws BusinessException 如果角色不存在 - */ - @Transactional - public Role update(UUID id, Role role) { - Role existing = findById(id); + Role update(UUID id, Role role); - if (role.getName() != null) { - existing.setName(role.getName()); - } - if (role.getDescription() != null) { - existing.setDescription(role.getDescription()); - } - if (role.getType() != null) { - existing.setType(role.getType()); - } - if (role.getDataScope() != null) { - existing.setDataScope(role.getDataScope()); - } - if (role.getProjectId() != null) { - existing.setProjectId(role.getProjectId()); - } - if (role.getStatus() != null) { - existing.setStatus(role.getStatus()); - } + void assignPermissions(UUID roleId, List permissionIds); - return roleRepository.save(existing); - } + void delete(UUID id); - /** - * 为角色分配权限 - * - *

替换角色原有的所有权限为新分配的权限列表。 - * 如果角色或权限不存在,抛出相应业务异常。

- * - * @param roleId 角色ID - * @param permissionIds 要分配的权限ID列表 - * @throws BusinessException 如果角色或权限不存在 - */ - @Transactional - public void assignPermissions(UUID roleId, List permissionIds) { - Role role = findById(roleId); - List permissions = permissionRepository.findAllById(permissionIds); - role.setPermissions(permissions); - roleRepository.save(role); - } + List getPermissions(UUID roleId); - /** - * 删除角色 - * - *

根据角色ID删除角色记录。

- * - * @param id 要删除的角色ID - */ - @Transactional - public void delete(UUID id) { - roleRepository.deleteById(id); - } + List getUsersByRoleId(UUID roleId); - /** - * 获取角色的所有权限 - * - *

使用EntityGraph eagerly加载权限信息,避免N+1查询。 - * 如果角色不存在,抛出ROLE_002业务异常。

- * - * @param roleId 角色ID - * @return 该角色关联的所有权限列表 - * @throws BusinessException 如果角色不存在 - */ - public List getPermissions(UUID roleId) { - Role role = roleRepository.findWithPermissionsById(roleId) - .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); - return role.getPermissions(); - } - - /** - * 根据角色ID查询关联的用户列表 - * - *

获取拥有该角色的所有用户。

- * - * @param roleId 角色ID - * @return 拥有该角色的用户列表 - */ - public List getUsersByRoleId(UUID roleId) { - return userRepository.findByRoleId(roleId); - } - - /** - * 为用户分配角色 - * - *

根据角色编码查找角色并分配给用户。

- * - * @param userId 用户ID - * @param roleCode 角色编码 - */ - @Transactional - public void assignRoleToUser(UUID userId, String roleCode) { - Role role = findByCode(roleCode); - User user = userRepository.findById(userId) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); - user.getRoles().add(role); - userRepository.save(user); - } -} \ No newline at end of file + void assignRoleToUser(UUID userId, String roleCode); +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/SysConfigService.java b/module-auth/src/main/java/com/ether/pms/auth/service/SysConfigService.java index 3959bbe..c82821a 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/SysConfigService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/SysConfigService.java @@ -1,94 +1,15 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.SysConfig; -import com.ether.pms.auth.repository.SysConfigRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.UUID; -/** - * 系统配置服务层 - * 提供系统配置的查询、更新等业务逻辑处理 - * - * @author Ether Team - * @since 1.0.0 - */ -@Service -@RequiredArgsConstructor -public class SysConfigService { +public interface SysConfigService { - /** - * 系统配置数据访问对象 - */ - private final SysConfigRepository sysConfigRepository; + Map getAllConfigs(); - /** - * 获取所有配置项 - * 将配置项列表转换为键值对 Map 返回 - * - *

性能说明:系统配置表通常数据量很小(<100条),使用 findAll 是安全的。 - * 如果未来配置项数量增长,应考虑添加缓存或分页查询。

- * - * @return 配置键值对 Map,key 为 configKey,value 为 configValue - */ - public Map getAllConfigs() { - // TODO: 如果配置项数量超过 1000 条,考虑改为分页查询或添加缓存 - List configs = sysConfigRepository.findAll(); - Map result = new HashMap<>(); - configs.forEach(config -> result.put(config.getConfigKey(), config.getConfigValue())); - return result; - } + SysConfig getConfig(String configKey); - /** - * 根据配置键获取单个配置项 - * - * @param configKey 配置键,如:property_company_name - * @return 配置项实体,不存在时返回 null - */ - public SysConfig getConfig(String configKey) { - return sysConfigRepository.findByConfigKey(configKey).orElse(null); - } + SysConfig updateConfig(String configKey, String configValue); - /** - * 更新单个配置项 - * 根据配置键查找并更新对应的配置值 - * - * @param configKey 配置键 - * @param configValue 新的配置值 - * @return 更新后的配置项实体 - * @throws RuntimeException 当配置项不存在时抛出 - */ - @Transactional - public SysConfig updateConfig(String configKey, String configValue) { - SysConfig config = sysConfigRepository.findByConfigKey(configKey) - .orElseThrow(() -> new RuntimeException("配置项不存在: " + configKey)); - config.setConfigValue(configValue); - return sysConfigRepository.save(config); - } - - /** - * 批量更新配置项 - * 同时更新多个配置键值对 - * - * @param configs 配置键值对 Map - * @return 更新后的配置键值对 Map - * @throws RuntimeException 当某个配置项不存在时抛出 - */ - @Transactional - public Map updateConfigs(Map configs) { - Map result = new HashMap<>(); - configs.forEach((key, value) -> { - SysConfig config = sysConfigRepository.findByConfigKey(key) - .orElseThrow(() -> new RuntimeException("配置项不存在: " + key)); - config.setConfigValue(value); - sysConfigRepository.save(config); - result.put(key, value); - }); - return result; - } + Map updateConfigs(Map configs); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/UserManagementService.java b/module-auth/src/main/java/com/ether/pms/auth/service/UserManagementService.java index fb5ebcc..057c896 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/UserManagementService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/UserManagementService.java @@ -1,260 +1,27 @@ package com.ether.pms.auth.service; -import java.time.LocalDateTime; +import com.ether.pms.auth.controller.dto.CreateEnterpriseUserDTO; +import com.ether.pms.auth.entity.ProjectStaff; +import com.ether.pms.auth.entity.User; import java.util.List; -import java.util.Optional; import java.util.UUID; -import org.hibernate.Hibernate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +public interface UserManagementService { -import com.ether.pms.auth.controller.dto.CreateEnterpriseUserDTO; -import com.ether.pms.auth.entity.EnterpriseUser; -import com.ether.pms.auth.entity.ProjectStaff; -import com.ether.pms.auth.entity.ProjectStaffRole; -import com.ether.pms.auth.entity.Role; -import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.repository.EnterpriseUserRepository; -import com.ether.pms.auth.repository.ProjectStaffRepository; -import com.ether.pms.auth.repository.ProjectStaffRoleRepository; -import com.ether.pms.auth.repository.ResidentRepository; -import com.ether.pms.auth.repository.UserRepository; + List findEnterpriseUsers(); -import lombok.RequiredArgsConstructor; + List findProjectStaffs(UUID projectId); -/** - * 用户管理服务层 - * - *

提供用户管理的核心业务逻辑,包括企业员工管理、项目员工分配等功能。

- * - *

作为用户管理的核心服务,供其他模块调用。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class UserManagementService { + User createEnterpriseUser(CreateEnterpriseUserDTO dto); - /** 用户数据访问接口 */ - private final UserRepository userRepository; + void assignRoleToUser(UUID userId, UUID roleId); - /** 企业用户数据访问接口 */ - private final EnterpriseUserRepository enterpriseUserRepository; + ProjectStaff assignStaffToProject( + UUID userId, UUID projectId, String staffType, List roleIds); - /** 项目员工数据访问接口 */ - private final ProjectStaffRepository projectStaffRepository; + List getProjectMembers(UUID projectId); - /** 项目员工角色数据访问接口 */ - private final ProjectStaffRoleRepository projectStaffRoleRepository; + List getProjectStaffsWithRoles(UUID projectId); - /** 角色服务 */ - private final RoleService roleService; - - /** 住户数据访问接口 */ - private final ResidentRepository residentRepository; - - /** 密码服务(BCrypt加密 + 强度校验) */ - private final PasswordService passwordService; - - /** - * 查询所有企业员工 - * - *

返回用户类型为ENTERPRISE的所有用户列表。

- * - * @return 所有企业用户的列表 - */ - public List findEnterpriseUsers() { - return userRepository.findByUserType("ENTERPRISE"); - } - - /** - * 查询项目员工 - * - *

根据项目ID查询该项目下的所有员工。

- * - * @param projectId 项目唯一标识符 - * @return 该项目下的所有员工列表 - */ - public List findProjectStaffs(UUID projectId) { - return userRepository.findProjectStaffsByProjectId(projectId); - } - - /** - * 创建企业员工 - * - *

创建基础用户信息和企业员工扩展信息,包括工号、部门、职位等。 - * 自动分配该部门的默认角色到用户。

- * - * @param dto 创建企业用户的请求数据 - * @return 创建成功的企业用户对象 - */ - @Transactional - public User createEnterpriseUser(CreateEnterpriseUserDTO dto) { - // 0. 校验密码强度 - passwordService.validateStrength(dto.getPassword()); - - // 1. 创建基础用户 - User user = new User(); - user.setUsername(dto.getUsername()); - user.setPassword(passwordService.encode(dto.getPassword())); - user.setRealName(dto.getRealName()); - user.setPhone(dto.getPhone()); - user.setEmail(dto.getEmail()); - user.setUserType("ENTERPRISE"); - user.setDeptId(dto.getDeptId()); - user.setStatus(User.UserStatus.ACTIVE); - user = userRepository.save(user); - - // 2. 创建企业员工扩展信息 - EnterpriseUser eu = new EnterpriseUser(); - eu.setUser(user); - eu.setEmployeeNo(dto.getEmployeeNo()); - eu.setDeptId(dto.getDeptId()); - eu.setPosition(dto.getPosition()); - eu.setEntryDate(dto.getEntryDate()); - eu.setUserCategory(dto.getUserCategory()); - enterpriseUserRepository.save(eu); - - return user; - } - - /** - * 为用户分配角色 - * - * @param userId 用户ID - * @param roleId 角色ID - */ - @Transactional - public void assignRoleToUser(UUID userId, UUID roleId) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found: " + userId)); - Role role = roleService.findById(roleId); - if (role == null) { - throw new IllegalArgumentException("Role not found: " + roleId); - } - - if (user.getRoles() == null) { - throw new IllegalStateException("User roles collection not initialized"); - } - - boolean hasRole = user.getRoles().stream() - .anyMatch(r -> r.getId().equals(roleId)); - if (!hasRole) { - user.getRoles().add(role); - userRepository.save(user); - } - } - - /** - * 分配员工到项目 - * - *

将用户分配到指定项目,设定员工类型和分配状态,支持多角色分配。

- * - * @param userId 用户唯一标识符 - * @param projectId 项目唯一标识符 - * @param staffType 员工类型 - * @param roleIds 角色ID列表 - * @return 创建的项目员工关联记录 - */ - @Transactional - public ProjectStaff assignStaffToProject(UUID userId, UUID projectId, String staffType, List roleIds) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("User not found: " + userId)); - - // 检查用户是否已经是该项目成员 - ProjectStaff staff = projectStaffRepository.findByUserIdAndProjectId(userId, projectId) - .orElse(null); - - if (staff == null) { - // 不存在,创建新的项目员工记录 - staff = new ProjectStaff(); - staff.setUser(user); - staff.setProjectId(projectId); - staff.setStaffType(staffType != null ? staffType : "PROJECT_STAFF"); - staff.setAssignmentStatus("ASSIGNED"); - staff.setCreatedAt(LocalDateTime.now()); - staff.setUpdatedAt(LocalDateTime.now()); - staff = projectStaffRepository.save(staff); - } else { - // 用户已在项目中,更新员工类型(只有当staffType变化时才更新) - if (staffType != null && !staffType.equals(staff.getStaffType())) { - staff.setStaffType(staffType); - staff.setUpdatedAt(LocalDateTime.now()); - staff = projectStaffRepository.save(staff); - } - } - - // 角色关联:先删除旧角色,再添加新角色(覆盖模式) - if (roleIds != null) { - // 删除该员工在该项目下的所有旧角色关联 - List existingRoles = staff.getStaffRoles(); - if (existingRoles != null && !existingRoles.isEmpty()) { - projectStaffRoleRepository.deleteAll(existingRoles); - staff.getStaffRoles().clear(); - } - // 添加新角色 - if (!roleIds.isEmpty()) { - for (UUID roleId : roleIds) { - Role role = roleService.findById(roleId); - if (role != null) { - ProjectStaffRole staffRole = new ProjectStaffRole(); - staffRole.setStaff(staff); - staffRole.setRole(role); - projectStaffRoleRepository.save(staffRole); - staff.getStaffRoles().add(staffRole); - } - } - } - } - - return staff; - } - - /** - * 查询项目成员 - * - *

根据项目ID查询该项目下的所有成员用户。

- * - * @param projectId 项目唯一标识符 - * @return 该项目下的所有成员用户列表 - */ - public List getProjectMembers(UUID projectId) { - return userRepository.findProjectStaffsByProjectId(projectId); - } - - /** - * 查询项目员工列表(包含角色信息) - * - *

根据项目ID查询该项目下的所有员工信息,包含角色信息。

- * - * @param projectId 项目唯一标识符 - * @return 该项目下的所有员工列表 - */ - public List getProjectStaffsWithRoles(UUID projectId) { - List staffs = projectStaffRepository.findByProjectIdWithRoles(projectId); - for (ProjectStaff staff : staffs) { - Hibernate.initialize(staff.getUser().getRoles()); - } - return staffs; - } - - /** - * 从项目移除员工 - * - *

将用户从指定项目中移除,删除项目员工关联关系。

- * - * @param userId 用户唯一标识符 - * @param projectId 项目唯一标识符 - */ - @Transactional - public void removeStaffFromProject(UUID userId, UUID projectId) { - Optional staff = projectStaffRepository.findByUserIdAndProjectId(userId, projectId); - staff.ifPresent(s -> { - // 级联删除角色关联(通过 orphanRemoval) - projectStaffRepository.delete(s); - }); - } + void removeStaffFromProject(UUID userId, UUID projectId); } diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/UserProjectService.java b/module-auth/src/main/java/com/ether/pms/auth/service/UserProjectService.java index 579dee2..25aaa99 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/service/UserProjectService.java +++ b/module-auth/src/main/java/com/ether/pms/auth/service/UserProjectService.java @@ -1,92 +1,18 @@ package com.ether.pms.auth.service; import com.ether.pms.auth.entity.UserProject; -import com.ether.pms.auth.repository.UserProjectRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.UUID; -/** - * 用户项目关联服务层 - * - *

提供用户与项目关联关系的业务逻辑处理,包括关联查询、添加、移除等功能。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class UserProjectService { +public interface UserProjectService { - /** 用户项目关联数据访问接口 */ - private final UserProjectRepository userProjectRepository; + List getUserProjects(UUID userId); - /** - * 获取用户参与的所有项目 - * - *

根据用户ID查询该用户参与的所有项目关联记录。

getUserProjects(UUID userId) { - return userProjectRepository.findByUserId(userId); - } + List getUserProjectIds(UUID userId); - /** - * 获取用户参与的所有项目ID - * - *

根据用户ID查询该用户参与的所有项目ID列表,用于权限验证等场景。

getUserProjectIds(UUID userId) { - return userProjectRepository.findProjectIdsByUserId(userId); - } + void addUserToProject(UUID userId, UUID projectId, String role); - /** - * 添加用户到项目 - * - *

创建用户与项目之间的关联关系。如果已存在关联则不重复创建。

删除用户与项目之间的关联关系。

判断用户与项目之间是否存在关联关系。

提供用户相关的业务逻辑处理,包括用户CRUD、密码管理、角色分配等功能。

- * - *

所有涉及数据修改的操作均添加事务注解保证数据一致性。

- * - * @author Ether开发团队 - * @version 1.0.0 - * @since 2024-01-01 - */ -@Service -@RequiredArgsConstructor -public class UserService { +public interface UserService { - /** 用户数据访问接口 */ - private final UserRepository userRepository; - /** 角色数据访问接口 */ - private final RoleRepository roleRepository; - /** 密码服务接口 */ - private final PasswordService passwordService; + Page findAll(Pageable pageable); - /** - * 分页查询所有用户 - * - *

返回所有用户及其关联的角色信息,支持分页。

- * - * @param pageable 分页参数 - * @return 用户分页数据 - */ - public Page findAll(Pageable pageable) { - return userRepository.findAllWithRoles(pageable); - } - - /** - * 查询所有用户(不分页,仅用于内部小规模查询) - * - *

返回所有用户及其关联的角色信息。

- *

警告:此方法会加载全表数据,仅在确认数据量较小时使用(如导出、统计场景)。

- * - * @return 所有用户的列表 - * @deprecated 建议使用 {@link #findAll(Pageable)} 分页版本 - */ @Deprecated - public List findAll() { - return userRepository.findAllWithRoles(); - } + List findAll(); - /** - * 根据ID查询用户 - * - *

根据用户唯一标识符查询用户信息。

- * - * @param id 用户唯一标识符 - * @return 用户对象 - * @throws BusinessException 如果用户不存在,抛出业务异常 - */ - public User findById(UUID id) { - return userRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); - } + User findById(UUID id); - /** - * 根据用户名查询用户 - * - *

根据用户名精确查询用户信息,用于登录验证等场景。

- * - * @param username 用户名 - * @return 用户对象 - * @throws BusinessException 如果用户不存在,抛出业务异常 - */ - public User findByUsername(String username) { - return userRepository.findByUsername(username) - .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); - } + User findByUsername(String username); - /** - * 创建新用户 - * - *

创建新用户并对密码进行加密存储,同时验证用户名和手机号的唯一性。

- * - * @param user 待创建的用户对象 - * @return 创建成功的用户对象(包含数据库生成的ID) - * @throws BusinessException 如果用户名已存在、手机号已存在或密码不符合要求 - */ - @Transactional - public User create(User user) { - if (userRepository.existsByUsername(user.getUsername())) { - throw new BusinessException(ErrorCode.USER_001); - } - if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) { - throw new BusinessException(ErrorCode.USER_002); - } + User create(User user); - try { - passwordService.validateStrength(user.getPassword()); - } catch (IllegalArgumentException e) { - throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage()); - } + User update(UUID id, User user); - if (passwordService.isWeakPassword(user.getPassword())) { - throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码"); - } + void updatePassword(UUID id, String oldPassword, String newPassword); - user.setPassword(passwordService.encode(user.getPassword())); + void resetPassword(UUID id, String newPassword); - return userRepository.save(user); - } + void assignRoles(UUID userId, List roleIds); - /** - * 更新用户信息 - * - *

根据用户ID更新用户信息,仅更新非空字段。

- * - * @param id 用户唯一标识符 - * @param user 包含更新信息的用户对象 - * @return 更新后的用户对象 - * @throws BusinessException 如果用户不存在或手机号已被其他用户使用 - */ - @Transactional - public User update(UUID id, User user) { - User existing = findById(id); + void delete(UUID id); - if (user.getRealName() != null) { - existing.setRealName(user.getRealName()); - } - if (user.getPhone() != null) { - if (!user.getPhone().equals(existing.getPhone()) && userRepository.existsByPhone(user.getPhone())) { - throw new BusinessException(ErrorCode.USER_002); - } - existing.setPhone(user.getPhone()); - } - if (user.getEmail() != null) { - existing.setEmail(user.getEmail()); - } - if (user.getAvatar() != null) { - existing.setAvatar(user.getAvatar()); - } - if (user.getStatus() != null) { - existing.setStatus(user.getStatus()); - } - - return userRepository.save(existing); - } - - /** - * 修改用户密码 - * - *

用户主动修改密码,需验证原密码正确性,新密码需符合强度要求。

管理员重置用户密码,新密码需符合强度要求。

为用户分配一个或多个角色,替换用户现有的所有角色。

roleIds) { - User user = findById(userId); - List roles = roleRepository.findAllById(roleIds); - user.setRoles(roles); - userRepository.save(user); - } - - /** - * 删除用户 - * - *

根据用户ID删除用户记录。

记录用户登录的时间戳和IP地址,用于安全审计。

findRecentLogs(Pageable pageable) { + LocalDateTime startTime = LocalDateTime.now().minusDays(30); + return auditLogRepository.findRecentLogs(startTime, pageable); + } + + @Override + public Page searchLogs( + String module, + String action, + String username, + LocalDateTime startDate, + LocalDateTime endDate, + Pageable pageable) { + LocalDateTime maxStartDate = LocalDateTime.now().minusDays(30); + if (startDate == null || startDate.isBefore(maxStartDate)) { + startDate = maxStartDate; + } + if (endDate == null) { + endDate = LocalDateTime.now(); + } + + final LocalDateTime finalStartDate = startDate; + final LocalDateTime finalEndDate = endDate; + + Specification spec = + (root, query, cb) -> { + List predicates = new ArrayList<>(); + + predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), finalStartDate)); + predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), finalEndDate)); + + if (module != null && !module.isEmpty()) { + predicates.add(cb.equal(root.get("module"), module)); + } + + if (action != null && !action.isEmpty()) { + predicates.add( + cb.equal(root.get("action"), AuditLog.ActionType.valueOf(action))); + } + + if (username != null && !username.isEmpty()) { + predicates.add(cb.like(root.get("username"), "%" + username + "%")); + } + + return cb.and(predicates.toArray(new Predicate[0])); + }; + + return auditLogRepository.findAll(spec, pageable); + } + + @Override + @Transactional + public int archiveOldLogs() { + LocalDateTime cutoffTime = LocalDateTime.now().minusDays(90); + + List oldLogs = auditLogRepository.findByCreatedAtBefore(cutoffTime); + + if (oldLogs.isEmpty()) { + log.info("没有需要归档的审计日志"); + return 0; + } + + int deleted = auditLogRepository.deleteByCreatedAtBefore(cutoffTime); + log.info("归档审计日志完成,共归档 {} 条记录", deleted); + + return deleted; + } + + @Override + public long getRecentLogCount() { + LocalDateTime startTime = LocalDateTime.now().minusDays(30); + return auditLogRepository.countRecentLogs(startTime); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/DataScopeServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/DataScopeServiceImpl.java new file mode 100644 index 0000000..5e03d51 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/DataScopeServiceImpl.java @@ -0,0 +1,34 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.repository.UserProjectRepository; +import com.ether.pms.auth.service.DataScopeService; +import java.util.*; +import java.util.Collections; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class DataScopeServiceImpl implements DataScopeService { + + private final UserProjectRepository userProjectRepository; + + @Override + public List getPermittedProjectIds(UUID userId, Set roles, boolean hasAllScope) { + if (hasAllScope) { + return Collections.emptyList(); + } + return userProjectRepository.findProjectIdsByUserId(userId); + } + + @Override + public boolean canAccessAllData(Set roles) { + return roles.stream().anyMatch(r -> r.getDataScope() == Role.DataScope.ALL); + } + + @Override + public boolean isSelfScopeOnly(Set roles) { + return roles.stream().allMatch(r -> r.getDataScope() == Role.DataScope.SELF); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/DeptServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..501c264 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/DeptServiceImpl.java @@ -0,0 +1,95 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.Dept; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.DeptRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.DeptService; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class DeptServiceImpl implements DeptService { + + private final DeptRepository deptRepository; + private final UserRepository userRepository; + + @Override + public List getDeptTree() { + return deptRepository.findAllForTree(); + } + + @Override + public List getActiveDepts() { + return deptRepository.findByStatusOrderBySortOrder("ACTIVE"); + } + + @Override + public Optional getById(UUID id) { + return deptRepository.findById(id); + } + + @Override + public List getDeptEmployees(UUID deptId) { + return userRepository.findByDeptId(deptId); + } + + @Override + @Transactional + public Dept createDept(Dept dept) { + if (dept.getSortOrder() == null) { + dept.setSortOrder(0); + } + if (dept.getStatus() == null) { + dept.setStatus("ACTIVE"); + } + if (dept.getDeptType() == null) { + dept.setDeptType("ADMIN"); + } + return deptRepository.save(dept); + } + + @Override + @Transactional + public Dept updateDept(UUID id, Dept dept) { + Dept existing = + deptRepository + .findById(id) + .orElseThrow(() -> new IllegalArgumentException("部门不存在: " + id)); + + existing.setDeptName(dept.getDeptName()); + existing.setParentId(dept.getParentId()); + existing.setDeptType(dept.getDeptType()); + existing.setLeaderId(dept.getLeaderId()); + existing.setSortOrder(dept.getSortOrder()); + existing.setStatus(dept.getStatus()); + + return deptRepository.save(existing); + } + + @Override + @Transactional + public void deleteDept(UUID id) { + Dept dept = + deptRepository + .findById(id) + .orElseThrow(() -> new IllegalArgumentException("部门不存在: " + id)); + + List children = deptRepository.findByParentIdOrderBySortOrder(id); + if (!children.isEmpty()) { + throw new IllegalStateException("请先删除子部门"); + } + + deptRepository.delete(dept); + } + + @Override + public List getByType(String deptType) { + return deptRepository.findByDeptTypeOrderBySortOrder(deptType); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginAttemptServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginAttemptServiceImpl.java new file mode 100644 index 0000000..0d7e4a8 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginAttemptServiceImpl.java @@ -0,0 +1,88 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.service.LoginAttemptService; +import java.time.Duration; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "login") +@Slf4j +public class LoginAttemptServiceImpl implements LoginAttemptService { + + private int maxAttempts = 5; + private int lockoutDurationMinutes = 10; + + private final StringRedisTemplate redisTemplate; + + private static final String KEY_PREFIX = "login:attempt:"; + + @Override + public void recordFailure(String username) { + String key = KEY_PREFIX + username; + Long attempts = redisTemplate.opsForValue().increment(key); + if (attempts != null && attempts == 1) { + redisTemplate.expire(key, Duration.ofMinutes(lockoutDurationMinutes)); + } + log.warn("用户 {} 登录失败,当前失败次数: {}", username, attempts); + } + + @Override + public void recordSuccess(String username) { + String key = KEY_PREFIX + username; + redisTemplate.delete(key); + log.info("用户 {} 登录成功,已清除登录失败记录", username); + } + + @Override + public boolean isLockedOut(String username) { + String key = KEY_PREFIX + username; + String value = redisTemplate.opsForValue().get(key); + if (value == null) { + return false; + } + int attempts = Integer.parseInt(value); + return attempts >= maxAttempts; + } + + @Override + public int getRemainingAttempts(String username) { + String key = KEY_PREFIX + username; + String value = redisTemplate.opsForValue().get(key); + if (value == null) { + return maxAttempts; + } + int attempts = Integer.parseInt(value); + return Math.max(0, maxAttempts - attempts); + } + + @Override + public void unlock(String username) { + String key = KEY_PREFIX + username; + redisTemplate.delete(key); + } + + @Override + public int getMaxAttempts() { + return maxAttempts; + } + + @Override + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + @Override + public int getLockoutDurationMinutes() { + return lockoutDurationMinutes; + } + + @Override + public void setLockoutDurationMinutes(int lockoutDurationMinutes) { + this.lockoutDurationMinutes = lockoutDurationMinutes; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..1ca5f14 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/LoginServiceImpl.java @@ -0,0 +1,83 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.ProjectStaff; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.ProjectStaffRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.LoginAttemptService; +import com.ether.pms.auth.service.LoginService; +import com.ether.pms.auth.service.PasswordService; +import com.ether.pms.auth.util.JwtTokenProvider; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.util.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class LoginServiceImpl implements LoginService { + + private final UserRepository userRepository; + private final ProjectStaffRepository projectStaffRepository; + private final JwtTokenProvider jwtTokenProvider; + private final PasswordService passwordService; + private final LoginAttemptService loginAttemptService; + + @Override + @Transactional + public Map login(String username, String password, String ip) { + if (loginAttemptService.isLockedOut(username)) { + throw new BusinessException(ErrorCode.AUTH_002); + } + + User user = userRepository.findByUsernameWithRoles(username).orElse(null); + + if (user == null || !passwordService.matches(password, user.getPassword())) { + if (user != null) { + loginAttemptService.recordFailure(username); + } + throw new BusinessException(ErrorCode.AUTH_001); + } + + if (user.getStatus() == User.UserStatus.LOCKED) { + throw new BusinessException(ErrorCode.AUTH_002); + } + if (user.getStatus() == User.UserStatus.DISABLED) { + throw new BusinessException(ErrorCode.AUTH_003); + } + + loginAttemptService.recordSuccess(username); + + Set allRoles = new HashSet<>(); + if (user.getRoles() != null) { + allRoles.addAll(user.getRoles().stream().map(r -> r.getCode()).toList()); + } + + List projectStaffs = projectStaffRepository.findAllByUserId(user.getId()); + for (ProjectStaff staff : projectStaffs) { + if (staff.getStaffRoles() != null) { + allRoles.addAll( + staff.getStaffRoles().stream() + .filter(sr -> sr.getRole() != null) + .map(sr -> sr.getRole().getCode()) + .toList()); + } + } + + Map claims = new HashMap<>(); + claims.put("roles", new ArrayList<>(allRoles)); + + String token = jwtTokenProvider.generateToken(user.getId(), user.getUsername(), claims); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("userId", user.getId()); + result.put("username", user.getUsername()); + result.put("realName", user.getRealName()); + result.put("roles", new ArrayList<>(allRoles)); + + return result; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/PasswordServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/PasswordServiceImpl.java new file mode 100644 index 0000000..c35ee27 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/PasswordServiceImpl.java @@ -0,0 +1,219 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.service.PasswordService; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import com.ether.pms.common.util.LogMaskUtil; +import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@ConfigurationProperties(prefix = "password") +@Slf4j +public class PasswordServiceImpl implements PasswordService { + + private int minLength = 8; + private int maxLength = 20; + private boolean requireUppercase = true; + private boolean requireLowercase = true; + private boolean requireDigit = true; + private boolean requireSpecial = true; + private String specialChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"; + + private final PasswordEncoder passwordEncoder; + + @Autowired + public PasswordServiceImpl(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + log.info("PasswordService 初始化完成,使用 BCrypt 编码器"); + } + + @Override + public String encode(String rawPassword) { + if (rawPassword == null || rawPassword.isBlank()) { + log.warn("尝试加密空密码"); + throw new IllegalArgumentException("密码不能为空"); + } + + String encodedPassword = passwordEncoder.encode(rawPassword); + log.debug("密码加密成功,哈希格式: {}", LogMaskUtil.maskPasswordHash(encodedPassword)); + return encodedPassword; + } + + @Override + public boolean matches(String rawPassword, String encodedPassword) { + if (rawPassword == null || encodedPassword == null) { + log.debug("密码验证失败:输入参数为空"); + return false; + } + + if (!isBcryptFormat(encodedPassword)) { + log.warn( + "检测到非BCrypt格式的密码哈希(前缀: {})," + + "可能使用了不安全的哈希算法(MD5/SHA-1等)," + + "建议用户立即重置密码以升级到 BCrypt", + encodedPassword.length() > 7 ? encodedPassword.substring(0, 7) : "N/A"); + return false; + } + + boolean matches = passwordEncoder.matches(rawPassword, encodedPassword); + + if (!matches) { + log.debug("密码验证失败"); + } + + return matches; + } + + @Override + public void validateStrength(String password) { + if (password == null || password.isBlank()) { + throw new BusinessException(ErrorCode.PASSWORD_001); + } + + if (password.length() < minLength) { + throw new BusinessException(ErrorCode.PASSWORD_002); + } + + if (password.length() > maxLength) { + throw new BusinessException(ErrorCode.PASSWORD_003); + } + + if (requireUppercase && !Pattern.compile("[A-Z]").matcher(password).find()) { + throw new BusinessException(ErrorCode.PASSWORD_004); + } + + if (requireLowercase && !Pattern.compile("[a-z]").matcher(password).find()) { + throw new BusinessException(ErrorCode.PASSWORD_005); + } + + if (requireDigit && !Pattern.compile("[0-9]").matcher(password).find()) { + throw new BusinessException(ErrorCode.PASSWORD_006); + } + + if (requireSpecial && !containsAny(password, specialChars.toCharArray())) { + throw new BusinessException(ErrorCode.PASSWORD_007); + } + + if (isWeakPassword(password)) { + throw new BusinessException(ErrorCode.PASSWORD_008); + } + } + + @Override + public boolean isWeakPassword(String password) { + if (password == null || password.length() < 8) { + return true; + } + + String lower = password.toLowerCase(); + + String[] weakPatterns = { + "password", "123456", "admin", "qwerty", + "letmein", "welcome", "monkey", "dragon" + }; + + for (String pattern : weakPatterns) { + if (lower.contains(pattern)) { + log.debug("检测到弱密码模式: {}", pattern); + return true; + } + } + + return false; + } + + @Override + public boolean isBcryptFormat(String encodedPassword) { + if (encodedPassword == null || encodedPassword.length() < 7) { + return false; + } + + return encodedPassword.startsWith("$2a$") + || encodedPassword.startsWith("$2b$") + || encodedPassword.startsWith("$2y$"); + } + + private boolean containsAny(String str, char... chars) { + for (char c : chars) { + if (str.indexOf(c) >= 0) { + return true; + } + } + return false; + } + + @Override + public int getMinLength() { + return minLength; + } + + @Override + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + @Override + public int getMaxLength() { + return maxLength; + } + + @Override + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + @Override + public boolean isRequireUppercase() { + return requireUppercase; + } + + @Override + public void setRequireUppercase(boolean requireUppercase) { + this.requireUppercase = requireUppercase; + } + + @Override + public boolean isRequireLowercase() { + return requireLowercase; + } + + @Override + public void setRequireLowercase(boolean requireLowercase) { + this.requireLowercase = requireLowercase; + } + + @Override + public boolean isRequireDigit() { + return requireDigit; + } + + @Override + public void setRequireDigit(boolean requireDigit) { + this.requireDigit = requireDigit; + } + + @Override + public boolean isRequireSpecial() { + return requireSpecial; + } + + @Override + public void setRequireSpecial(boolean requireSpecial) { + this.requireSpecial = requireSpecial; + } + + @Override + public String getSpecialChars() { + return specialChars; + } + + @Override + public void setSpecialChars(String specialChars) { + this.specialChars = specialChars; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/PermissionServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/PermissionServiceImpl.java new file mode 100644 index 0000000..c40636b --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/PermissionServiceImpl.java @@ -0,0 +1,282 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.entity.ProjectStaff; +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.PermissionRepository; +import com.ether.pms.auth.repository.ProjectStaffRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.PermissionService; +import com.ether.pms.auth.vo.MenuVO; +import com.ether.pms.auth.vo.PermissionTreeVO; +import com.ether.pms.auth.vo.PermissionVO; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PermissionServiceImpl implements PermissionService { + + private final PermissionRepository permissionRepository; + private final UserRepository userRepository; + private final ProjectStaffRepository projectStaffRepository; + + @Override + public Page findAll(Pageable pageable) { + return permissionRepository.findAll(pageable); + } + + @Override + @Deprecated + public List findAll() { + return permissionRepository.findAll(); + } + + @Override + public Permission findById(UUID id) { + return permissionRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.PERMISSION_002)); + } + + @Override + public List findByType(String type) { + return permissionRepository.findByType(type); + } + + @Override + public List findByParentCode(String parentCode) { + return permissionRepository.findByParentCode(parentCode); + } + + @Override + public List findMenuPermissions() { + return permissionRepository.findByType("MENU"); + } + + @Override + @Transactional + public Permission create(Permission permission) { + if (permissionRepository.existsByCode(permission.getCode())) { + throw new BusinessException(ErrorCode.PERMISSION_001); + } + return permissionRepository.save(permission); + } + + @Override + @Transactional + public Permission update(UUID id, Permission permission) { + Permission existing = findById(id); + + if (permission.getName() != null) { + existing.setName(permission.getName()); + } + if (permission.getType() != null) { + existing.setType(permission.getType()); + } + if (permission.getResource() != null) { + existing.setResource(permission.getResource()); + } + if (permission.getMethod() != null) { + existing.setMethod(permission.getMethod()); + } + if (permission.getDescription() != null) { + existing.setDescription(permission.getDescription()); + } + if (permission.getParentCode() != null) { + existing.setParentCode(permission.getParentCode()); + } + if (permission.getSortOrder() != null) { + existing.setSortOrder(permission.getSortOrder()); + } + + return permissionRepository.save(existing); + } + + @Override + @Transactional + public void delete(UUID id) { + permissionRepository.deleteById(id); + } + + @Override + public List getPermissionTree() { + List allPermissions = permissionRepository.findAll(); + return buildPermissionTree(allPermissions); + } + + private List buildPermissionTree(List permissions) { + Map> parentCodeMap = + permissions.stream() + .collect( + Collectors.groupingBy( + p -> { + if (p.getParentCode() == null + || p.getParentCode().isEmpty()) { + return "ROOT"; + } + return p.getParentCode(); + })); + + List rootNodes = new ArrayList<>(); + for (Permission permission : permissions) { + if (permission.getParentCode() == null || permission.getParentCode().isEmpty()) { + PermissionTreeVO vo = convertToTreeVO(permission); + List children = parentCodeMap.get(permission.getCode()); + if (children != null) { + buildChildren(vo, children, parentCodeMap); + } + rootNodes.add(vo); + } + } + return rootNodes; + } + + private void buildChildren( + PermissionTreeVO parent, + List children, + Map> parentCodeMap) { + for (Permission child : children) { + PermissionTreeVO childVO = convertToTreeVO(child); + List grandchildren = parentCodeMap.get(child.getCode()); + if (grandchildren != null) { + buildChildren(childVO, grandchildren, parentCodeMap); + } + parent.addChild(childVO); + } + } + + private PermissionTreeVO convertToTreeVO(Permission permission) { + PermissionTreeVO vo = new PermissionTreeVO(); + vo.setId(permission.getId()); + vo.setName(permission.getName()); + vo.setCode(permission.getCode()); + vo.setType(permission.getType()); + vo.setPath(permission.getPath()); + vo.setComponent(permission.getComponent()); + vo.setIcon(permission.getIcon()); + vo.setOrderNum(permission.getSortOrder()); + return vo; + } + + @Override + public List getUserMenus(UUID userId, UUID projectId) { + Set permissionCodes = getUserPermissionCodes(userId, projectId); + List allMenus = permissionRepository.findByType("MENU"); + + List userMenus = + allMenus.stream() + .filter(p -> permissionCodes.contains(p.getCode())) + .collect(Collectors.toList()); + + return buildMenuTree(userMenus); + } + + @Override + public PermissionVO getUserPermissions(UUID userId, UUID projectId) { + Set permissionCodes = getUserPermissionCodes(userId, projectId); + return new PermissionVO(new ArrayList<>(permissionCodes)); + } + + private Set getUserPermissionCodes(UUID userId, UUID projectId) { + Set permissionCodes = new HashSet<>(); + + User user = + userRepository + .findByIdWithRoles(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); + + if (user.getRoles() != null) { + for (Role role : user.getRoles()) { + if (role.getPermissions() != null) { + for (Permission permission : role.getPermissions()) { + permissionCodes.add(permission.getCode()); + } + } + } + } + + if (projectId != null) { + ProjectStaff staff = + projectStaffRepository.findByUserIdAndProjectId(userId, projectId).orElse(null); + if (staff != null && staff.getStaffRoles() != null) { + for (var staffRole : staff.getStaffRoles()) { + Role role = staffRole.getRole(); + if (role != null && role.getPermissions() != null) { + for (Permission permission : role.getPermissions()) { + permissionCodes.add(permission.getCode()); + } + } + } + } + } + + return permissionCodes; + } + + private List buildMenuTree(List menus) { + if (menus.isEmpty()) { + return new ArrayList<>(); + } + + Map> parentCodeMap = + menus.stream() + .collect( + Collectors.groupingBy( + p -> { + if (p.getParentCode() == null + || p.getParentCode().isEmpty()) { + return "ROOT"; + } + return p.getParentCode(); + })); + + List rootMenus = new ArrayList<>(); + for (Permission menu : menus) { + if (menu.getParentCode() == null || menu.getParentCode().isEmpty()) { + MenuVO vo = convertToMenuVO(menu); + List children = parentCodeMap.get(menu.getCode()); + if (children != null) { + buildMenuChildren(vo, children, parentCodeMap); + } + rootMenus.add(vo); + } + } + return rootMenus; + } + + private void buildMenuChildren( + MenuVO parent, List children, Map> parentCodeMap) { + for (Permission child : children) { + MenuVO childVO = convertToMenuVO(child); + List grandchildren = parentCodeMap.get(child.getCode()); + if (grandchildren != null) { + buildMenuChildren(childVO, grandchildren, parentCodeMap); + } + parent.addChild(childVO); + } + } + + private MenuVO convertToMenuVO(Permission permission) { + MenuVO vo = new MenuVO(); + vo.setId(permission.getId()); + vo.setName(permission.getName()); + vo.setPath(permission.getPath()); + vo.setComponent(permission.getComponent()); + vo.setIcon(permission.getIcon()); + return vo; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/RoleServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..3a30d53 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/RoleServiceImpl.java @@ -0,0 +1,134 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.PermissionRepository; +import com.ether.pms.auth.repository.RoleRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.RoleService; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class RoleServiceImpl implements RoleService { + + private final RoleRepository roleRepository; + private final PermissionRepository permissionRepository; + private final UserRepository userRepository; + + @Override + public Page findAll(Pageable pageable) { + return roleRepository.findAll(pageable); + } + + @Override + @Deprecated + public List findAll() { + return roleRepository.findAll(); + } + + @Override + public Role findById(UUID id) { + return roleRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); + } + + @Override + public Role findByCode(String code) { + return roleRepository + .findByCode(code) + .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); + } + + @Override + public List findByProjectId(UUID projectId) { + return roleRepository.findByProjectId(projectId); + } + + @Override + @Transactional + public Role create(Role role) { + if (roleRepository.existsByCode(role.getCode())) { + throw new BusinessException(ErrorCode.ROLE_001); + } + return roleRepository.save(role); + } + + @Override + @Transactional + public Role update(UUID id, Role role) { + Role existing = findById(id); + + if (role.getName() != null) { + existing.setName(role.getName()); + } + if (role.getDescription() != null) { + existing.setDescription(role.getDescription()); + } + if (role.getType() != null) { + existing.setType(role.getType()); + } + if (role.getDataScope() != null) { + existing.setDataScope(role.getDataScope()); + } + if (role.getProjectId() != null) { + existing.setProjectId(role.getProjectId()); + } + if (role.getStatus() != null) { + existing.setStatus(role.getStatus()); + } + + return roleRepository.save(existing); + } + + @Override + @Transactional + public void assignPermissions(UUID roleId, List permissionIds) { + Role role = findById(roleId); + List permissions = permissionRepository.findAllById(permissionIds); + role.setPermissions(permissions); + roleRepository.save(role); + } + + @Override + @Transactional + public void delete(UUID id) { + roleRepository.deleteById(id); + } + + @Override + public List getPermissions(UUID roleId) { + Role role = + roleRepository + .findWithPermissionsById(roleId) + .orElseThrow(() -> new BusinessException(ErrorCode.ROLE_002)); + return role.getPermissions(); + } + + @Override + public List getUsersByRoleId(UUID roleId) { + return userRepository.findByRoleId(roleId); + } + + @Override + @Transactional + public void assignRoleToUser(UUID userId, String roleCode) { + Role role = findByCode(roleCode); + User user = + userRepository + .findById(userId) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); + user.getRoles().add(role); + userRepository.save(user); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/SysConfigServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..c9167e7 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,59 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.SysConfig; +import com.ether.pms.auth.repository.SysConfigRepository; +import com.ether.pms.auth.service.SysConfigService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class SysConfigServiceImpl implements SysConfigService { + + private final SysConfigRepository sysConfigRepository; + + @Override + public Map getAllConfigs() { + List configs = sysConfigRepository.findAll(); + Map result = new HashMap<>(); + configs.forEach(config -> result.put(config.getConfigKey(), config.getConfigValue())); + return result; + } + + @Override + public SysConfig getConfig(String configKey) { + return sysConfigRepository.findByConfigKey(configKey).orElse(null); + } + + @Override + @Transactional + public SysConfig updateConfig(String configKey, String configValue) { + SysConfig config = + sysConfigRepository + .findByConfigKey(configKey) + .orElseThrow(() -> new RuntimeException("配置项不存在: " + configKey)); + config.setConfigValue(configValue); + return sysConfigRepository.save(config); + } + + @Override + @Transactional + public Map updateConfigs(Map configs) { + Map result = new HashMap<>(); + configs.forEach( + (key, value) -> { + SysConfig config = + sysConfigRepository + .findByConfigKey(key) + .orElseThrow(() -> new RuntimeException("配置项不存在: " + key)); + config.setConfigValue(value); + sysConfigRepository.save(config); + result.put(key, value); + }); + return result; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserManagementServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserManagementServiceImpl.java new file mode 100644 index 0000000..70532c7 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserManagementServiceImpl.java @@ -0,0 +1,177 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.controller.dto.CreateEnterpriseUserDTO; +import com.ether.pms.auth.entity.EnterpriseUser; +import com.ether.pms.auth.entity.ProjectStaff; +import com.ether.pms.auth.entity.ProjectStaffRole; +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.EnterpriseUserRepository; +import com.ether.pms.auth.repository.ProjectStaffRepository; +import com.ether.pms.auth.repository.ProjectStaffRoleRepository; +import com.ether.pms.auth.repository.ResidentRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.PasswordService; +import com.ether.pms.auth.service.RoleService; +import com.ether.pms.auth.service.UserManagementService; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.hibernate.Hibernate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserManagementServiceImpl implements UserManagementService { + + private final UserRepository userRepository; + private final EnterpriseUserRepository enterpriseUserRepository; + private final ProjectStaffRepository projectStaffRepository; + private final ProjectStaffRoleRepository projectStaffRoleRepository; + private final RoleService roleService; + private final ResidentRepository residentRepository; + private final PasswordService passwordService; + + @Override + public List findEnterpriseUsers() { + return userRepository.findByUserType("ENTERPRISE"); + } + + @Override + public List findProjectStaffs(UUID projectId) { + return userRepository.findProjectStaffsByProjectId(projectId); + } + + @Override + @Transactional + public User createEnterpriseUser(CreateEnterpriseUserDTO dto) { + passwordService.validateStrength(dto.getPassword()); + + User user = new User(); + user.setUsername(dto.getUsername()); + user.setPassword(passwordService.encode(dto.getPassword())); + user.setRealName(dto.getRealName()); + user.setPhone(dto.getPhone()); + user.setEmail(dto.getEmail()); + user.setUserType("ENTERPRISE"); + user.setDeptId(dto.getDeptId()); + user.setStatus(User.UserStatus.ACTIVE); + user = userRepository.save(user); + + EnterpriseUser eu = new EnterpriseUser(); + eu.setUser(user); + eu.setEmployeeNo(dto.getEmployeeNo()); + eu.setDeptId(dto.getDeptId()); + eu.setPosition(dto.getPosition()); + eu.setEntryDate(dto.getEntryDate()); + eu.setUserCategory(dto.getUserCategory()); + enterpriseUserRepository.save(eu); + + return user; + } + + @Override + @Transactional + public void assignRoleToUser(UUID userId, UUID roleId) { + User user = + userRepository + .findById(userId) + .orElseThrow( + () -> new IllegalArgumentException("User not found: " + userId)); + Role role = roleService.findById(roleId); + if (role == null) { + throw new IllegalArgumentException("Role not found: " + roleId); + } + + if (user.getRoles() == null) { + throw new IllegalStateException("User roles collection not initialized"); + } + + boolean hasRole = user.getRoles().stream().anyMatch(r -> r.getId().equals(roleId)); + if (!hasRole) { + user.getRoles().add(role); + userRepository.save(user); + } + } + + @Override + @Transactional + public ProjectStaff assignStaffToProject( + UUID userId, UUID projectId, String staffType, List roleIds) { + User user = + userRepository + .findById(userId) + .orElseThrow( + () -> new IllegalArgumentException("User not found: " + userId)); + + ProjectStaff staff = + projectStaffRepository.findByUserIdAndProjectId(userId, projectId).orElse(null); + + if (staff == null) { + staff = new ProjectStaff(); + staff.setUser(user); + staff.setProjectId(projectId); + staff.setStaffType(staffType != null ? staffType : "PROJECT_STAFF"); + staff.setAssignmentStatus("ASSIGNED"); + staff.setCreatedAt(LocalDateTime.now()); + staff.setUpdatedAt(LocalDateTime.now()); + staff = projectStaffRepository.save(staff); + } else { + if (staffType != null && !staffType.equals(staff.getStaffType())) { + staff.setStaffType(staffType); + staff.setUpdatedAt(LocalDateTime.now()); + staff = projectStaffRepository.save(staff); + } + } + + if (roleIds != null) { + List existingRoles = staff.getStaffRoles(); + if (existingRoles != null && !existingRoles.isEmpty()) { + projectStaffRoleRepository.deleteAll(existingRoles); + staff.getStaffRoles().clear(); + } + if (!roleIds.isEmpty()) { + for (UUID roleId : roleIds) { + Role role = roleService.findById(roleId); + if (role != null) { + ProjectStaffRole staffRole = new ProjectStaffRole(); + staffRole.setStaff(staff); + staffRole.setRole(role); + projectStaffRoleRepository.save(staffRole); + staff.getStaffRoles().add(staffRole); + } + } + } + } + + return staff; + } + + @Override + public List getProjectMembers(UUID projectId) { + return userRepository.findProjectStaffsByProjectId(projectId); + } + + @Override + public List getProjectStaffsWithRoles(UUID projectId) { + List staffs = projectStaffRepository.findByProjectIdWithRoles(projectId); + for (ProjectStaff staff : staffs) { + Hibernate.initialize(staff.getUser().getRoles()); + } + return staffs; + } + + @Override + @Transactional + public void removeStaffFromProject(UUID userId, UUID projectId) { + Optional staff = + projectStaffRepository.findByUserIdAndProjectId(userId, projectId); + staff.ifPresent( + s -> { + projectStaffRepository.delete(s); + }); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserProjectServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserProjectServiceImpl.java new file mode 100644 index 0000000..00bc52b --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserProjectServiceImpl.java @@ -0,0 +1,51 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.UserProject; +import com.ether.pms.auth.repository.UserProjectRepository; +import com.ether.pms.auth.service.UserProjectService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserProjectServiceImpl implements UserProjectService { + + private final UserProjectRepository userProjectRepository; + + @Override + public List getUserProjects(UUID userId) { + return userProjectRepository.findByUserId(userId); + } + + @Override + public List getUserProjectIds(UUID userId) { + return userProjectRepository.findProjectIdsByUserId(userId); + } + + @Override + @Transactional + public void addUserToProject(UUID userId, UUID projectId, String role) { + if (userProjectRepository.existsByUserIdAndProjectId(userId, projectId)) { + return; + } + UserProject up = new UserProject(); + up.setUserId(userId); + up.setProjectId(projectId); + up.setRoleInProject(role != null ? role : "member"); + userProjectRepository.save(up); + } + + @Override + @Transactional + public void removeUserFromProject(UUID userId, UUID projectId) { + userProjectRepository.deleteByUserIdAndProjectId(userId, projectId); + } + + @Override + public boolean isUserInProject(UUID userId, UUID projectId) { + return userProjectRepository.existsByUserIdAndProjectId(userId, projectId); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserServiceImpl.java b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..b47b819 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/impl/UserServiceImpl.java @@ -0,0 +1,170 @@ +package com.ether.pms.auth.service.impl; + +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.RoleRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.PasswordService; +import com.ether.pms.auth.service.UserService; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + private final PasswordService passwordService; + + @Override + public Page findAll(Pageable pageable) { + return userRepository.findAllWithRoles(pageable); + } + + @Override + @Deprecated + public List findAll() { + return userRepository.findAllWithRoles(); + } + + @Override + public User findById(UUID id) { + return userRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); + } + + @Override + public User findByUsername(String username) { + return userRepository + .findByUsername(username) + .orElseThrow(() -> new BusinessException(ErrorCode.USER_003)); + } + + @Override + @Transactional + public User create(User user) { + if (userRepository.existsByUsername(user.getUsername())) { + throw new BusinessException(ErrorCode.USER_001); + } + if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) { + throw new BusinessException(ErrorCode.USER_002); + } + + try { + passwordService.validateStrength(user.getPassword()); + } catch (IllegalArgumentException e) { + throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage()); + } + + if (passwordService.isWeakPassword(user.getPassword())) { + throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码"); + } + + user.setPassword(passwordService.encode(user.getPassword())); + + return userRepository.save(user); + } + + @Override + @Transactional + public User update(UUID id, User user) { + User existing = findById(id); + + if (user.getRealName() != null) { + existing.setRealName(user.getRealName()); + } + if (user.getPhone() != null) { + if (!user.getPhone().equals(existing.getPhone()) + && userRepository.existsByPhone(user.getPhone())) { + throw new BusinessException(ErrorCode.USER_002); + } + existing.setPhone(user.getPhone()); + } + if (user.getEmail() != null) { + existing.setEmail(user.getEmail()); + } + if (user.getAvatar() != null) { + existing.setAvatar(user.getAvatar()); + } + if (user.getStatus() != null) { + existing.setStatus(user.getStatus()); + } + + return userRepository.save(existing); + } + + @Override + @Transactional + public void updatePassword(UUID id, String oldPassword, String newPassword) { + User user = findById(id); + + if (!passwordService.matches(oldPassword, user.getPassword())) { + throw new BusinessException(ErrorCode.USER_004); + } + + try { + passwordService.validateStrength(newPassword); + } catch (IllegalArgumentException e) { + throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage()); + } + + if (passwordService.isWeakPassword(newPassword)) { + throw new BusinessException(ErrorCode.BAD_REQUEST, "新密码太弱,请使用更复杂的密码"); + } + + user.setPassword(passwordService.encode(newPassword)); + userRepository.save(user); + } + + @Override + @Transactional + public void resetPassword(UUID id, String newPassword) { + User user = findById(id); + + try { + passwordService.validateStrength(newPassword); + } catch (IllegalArgumentException e) { + throw new BusinessException(ErrorCode.BAD_REQUEST, e.getMessage()); + } + + if (passwordService.isWeakPassword(newPassword)) { + throw new BusinessException(ErrorCode.BAD_REQUEST, "密码太弱,请使用更复杂的密码"); + } + + user.setPassword(passwordService.encode(newPassword)); + userRepository.save(user); + } + + @Override + @Transactional + public void assignRoles(UUID userId, List roleIds) { + User user = findById(userId); + List roles = roleRepository.findAllById(roleIds); + user.setRoles(roles); + userRepository.save(user); + } + + @Override + @Transactional + public void delete(UUID id) { + userRepository.deleteById(id); + } + + @Override + public void updateLoginInfo(UUID userId, String ip) { + User user = findById(userId); + user.setLastLoginTime(LocalDateTime.now()); + user.setLastLoginIp(ip); + userRepository.save(user); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java b/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java index bdcf046..3872dd6 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java +++ b/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java @@ -2,15 +2,6 @@ package com.ether.pms.auth.util; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Date; @@ -18,6 +9,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import javax.crypto.SecretKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; @Component public class JwtTokenProvider implements InitializingBean { @@ -27,17 +26,17 @@ public class JwtTokenProvider implements InitializingBean { @Value("${jwt.secret:}") private String secret; - + @Value("${jwt.expiration:86400000}") private long expiration; - + @Value("${jwt.issuer:ether-pms}") private String issuer; - + private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); } - + public String generateToken(UUID userId, String username, Map claims) { Map allClaims = new HashMap<>(); allClaims.put("userId", userId.toString()); @@ -45,10 +44,10 @@ public class JwtTokenProvider implements InitializingBean { if (claims != null) { allClaims.putAll(claims); } - + Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); - + return Jwts.builder() .claims(allClaims) .subject(username) @@ -58,11 +57,11 @@ public class JwtTokenProvider implements InitializingBean { .signWith(getSigningKey()) .compact(); } - + public String generateToken(UUID userId, String username) { return generateToken(userId, username, null); } - + public Claims getClaimsFromToken(String token) { return Jwts.parser() .verifyWith(getSigningKey()) @@ -70,12 +69,12 @@ public class JwtTokenProvider implements InitializingBean { .parseSignedClaims(token) .getPayload(); } - + public UUID getUserIdFromToken(String token) { Claims claims = getClaimsFromToken(token); return UUID.fromString(claims.get("userId", String.class)); } - + public String getUsernameFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getSubject(); @@ -97,16 +96,13 @@ public class JwtTokenProvider implements InitializingBean { public boolean validateToken(String token) { try { - Jwts.parser() - .verifyWith(getSigningKey()) - .build() - .parseSignedClaims(token); + Jwts.parser().verifyWith(getSigningKey()).build().parseSignedClaims(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } - + public boolean isTokenExpired(String token) { try { Claims claims = getClaimsFromToken(token); @@ -122,7 +118,8 @@ public class JwtTokenProvider implements InitializingBean { // 1. 检查是否为空 if (secret == null || secret.isBlank()) { - String errorMsg = """ + String errorMsg = + """ ============================================ 致命错误: JWT Secret 未配置! ============================================ @@ -141,22 +138,25 @@ public class JwtTokenProvider implements InitializingBean { 参考 .env.example 文件获取详细说明。 ============================================"""; log.error(errorMsg); - throw new IllegalStateException("JWT Secret 未配置。请设置环境变量 JWT_SECRET。参考 .env.example 文件。"); + throw new IllegalStateException( + "JWT Secret 未配置。请设置环境变量 JWT_SECRET。参考 .env.example 文件。"); } // 2. 检查是否使用了不安全的默认值 String[] insecureDefaults = { - "my-secret-key-2024", - "ether-pms-secret-key-must-be-at-least-256-bits", - "ether-pms-jwt-secret-key-for-development-only-change-in-production-min-256-bits", - "secret", - "password", - "jwt-secret" + "my-secret-key-2024", + "ether-pms-secret-key-must-be-at-least-256-bits", + "ether-pms-jwt-secret-key-for-development-only-change-in-production-min-256-bits", + "secret", + "password", + "jwt-secret" }; for (String insecureDefault : insecureDefaults) { if (secret.equals(insecureDefault)) { - String errorMsg = String.format(""" + String errorMsg = + String.format( + """ ============================================ 安全警告: 检测到不安全的 JWT Secret! ============================================ @@ -172,15 +172,19 @@ public class JwtTokenProvider implements InitializingBean { export JWT_SECRET=<新生成的密钥> 3. 重启应用 - ============================================""", secret); + ============================================""", + secret); log.error(errorMsg); - throw new IllegalStateException("检测到不安全的 JWT Secret: " + secret + "。请生成新的安全密钥并更新配置。"); + throw new IllegalStateException( + "检测到不安全的 JWT Secret: " + secret + "。请生成新的安全密钥并更新配置。"); } } // 3. 检查长度(建议 >= 32 字符) if (secret.length() < MIN_SECRET_LENGTH) { - String warningMsg = String.format(""" + String warningMsg = + String.format( + """ ============================================ 安全警告: JWT Secret 长度不足! ============================================ @@ -191,7 +195,8 @@ public class JwtTokenProvider implements InitializingBean { 生成安全密钥命令: openssl rand -base64 64 - ============================================""", secret.length(), MIN_SECRET_LENGTH); + ============================================""", + secret.length(), MIN_SECRET_LENGTH); log.warn(warningMsg); // 注意:这里只是警告,不阻止启动,但生产环境应该使用足够长的密钥 } diff --git a/module-auth/src/main/java/com/ether/pms/auth/util/SecurityUtils.java b/module-auth/src/main/java/com/ether/pms/auth/util/SecurityUtils.java index aaf1b16..7010528 100644 --- a/module-auth/src/main/java/com/ether/pms/auth/util/SecurityUtils.java +++ b/module-auth/src/main/java/com/ether/pms/auth/util/SecurityUtils.java @@ -1,8 +1,8 @@ package com.ether.pms.auth.util; +import java.util.UUID; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import java.util.UUID; public class SecurityUtils { diff --git a/module-auth/src/main/java/com/ether/pms/auth/vo/MenuVO.java b/module-auth/src/main/java/com/ether/pms/auth/vo/MenuVO.java new file mode 100644 index 0000000..ccf0db2 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/vo/MenuVO.java @@ -0,0 +1,28 @@ +package com.ether.pms.auth.vo; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Data; + +@Data +public class MenuVO { + + private UUID id; + + private String name; + + private String path; + + private String component; + + private String icon; + + private UUID parentId; + + private List children = new ArrayList<>(); + + public void addChild(MenuVO child) { + this.children.add(child); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionTreeVO.java b/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionTreeVO.java new file mode 100644 index 0000000..e8ca9d4 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionTreeVO.java @@ -0,0 +1,34 @@ +package com.ether.pms.auth.vo; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import lombok.Data; + +@Data +public class PermissionTreeVO { + + private UUID id; + + private String name; + + private String code; + + private String type; + + private String path; + + private String component; + + private String icon; + + private UUID parentId; + + private Integer orderNum; + + private List children = new ArrayList<>(); + + public void addChild(PermissionTreeVO child) { + this.children.add(child); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionVO.java b/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionVO.java new file mode 100644 index 0000000..e555572 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/vo/PermissionVO.java @@ -0,0 +1,17 @@ +package com.ether.pms.auth.vo; + +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data +public class PermissionVO { + + private List codes = new ArrayList<>(); + + public PermissionVO() {} + + public PermissionVO(List codes) { + this.codes = codes; + } +} diff --git a/module-auth/src/test/java/com/ether/pms/auth/PasswordEncoderTest.java b/module-auth/src/test/java/com/ether/pms/auth/PasswordEncoderTest.java index 38838b1..326f55b 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/PasswordEncoderTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/PasswordEncoderTest.java @@ -1,10 +1,10 @@ package com.ether.pms.auth; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.Test; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import static org.junit.jupiter.api.Assertions.*; - class PasswordEncoderTest { @Test diff --git a/module-auth/src/test/java/com/ether/pms/auth/TestApplication.java b/module-auth/src/test/java/com/ether/pms/auth/TestApplication.java new file mode 100644 index 0000000..4a0a447 --- /dev/null +++ b/module-auth/src/test/java/com/ether/pms/auth/TestApplication.java @@ -0,0 +1,6 @@ +package com.ether.pms.auth; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestApplication {} diff --git a/module-auth/src/test/java/com/ether/pms/auth/controller/dto/ProjectMemberVOTest.java b/module-auth/src/test/java/com/ether/pms/auth/controller/dto/ProjectMemberVOTest.java index 9e423c5..488f9e1 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/controller/dto/ProjectMemberVOTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/controller/dto/ProjectMemberVOTest.java @@ -5,17 +5,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import org.junit.jupiter.api.Test; - import com.ether.pms.auth.entity.ProjectStaff; import com.ether.pms.auth.entity.ProjectStaffRole; import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.entity.User; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Test; class ProjectMemberVOTest { diff --git a/module-auth/src/test/java/com/ether/pms/auth/service/PasswordServiceTest.java b/module-auth/src/test/java/com/ether/pms/auth/service/PasswordServiceTest.java index 2874a19..bf7e9e3 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/service/PasswordServiceTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/service/PasswordServiceTest.java @@ -1,20 +1,26 @@ package com.ether.pms.auth.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.ether.pms.auth.service.impl.PasswordServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.*; +import org.springframework.security.crypto.password.PasswordEncoder; @ExtendWith(MockitoExtension.class) class PasswordServiceTest { - @InjectMocks - private PasswordService passwordService; + @Mock private PasswordEncoder passwordEncoder; + + @InjectMocks private PasswordServiceImpl passwordService; @Test void encode_shouldReturnNonNullValue() { + when(passwordEncoder.encode("Test123!")).thenReturn("$2a$10$encodedpassword"); String encoded = passwordService.encode("Test123!"); assertNotNull(encoded); assertNotEquals("Test123!", encoded); @@ -22,12 +28,16 @@ class PasswordServiceTest { @Test void matches_shouldReturnTrue_forCorrectPassword() { + when(passwordEncoder.encode("Test123!")).thenReturn("$2a$10$encodedpassword"); + when(passwordEncoder.matches("Test123!", "$2a$10$encodedpassword")).thenReturn(true); String encoded = passwordService.encode("Test123!"); assertTrue(passwordService.matches("Test123!", encoded)); } @Test void matches_shouldReturnFalse_forWrongPassword() { + when(passwordEncoder.encode("Test123!")).thenReturn("$2a$10$encodedpassword"); + when(passwordEncoder.matches("Wrong123!", "$2a$10$encodedpassword")).thenReturn(false); String encoded = passwordService.encode("Test123!"); assertFalse(passwordService.matches("Wrong123!", encoded)); } diff --git a/module-auth/src/test/java/com/ether/pms/auth/service/PermissionServiceTest.java b/module-auth/src/test/java/com/ether/pms/auth/service/PermissionServiceTest.java index b807347..030e7d2 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/service/PermissionServiceTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/service/PermissionServiceTest.java @@ -1,30 +1,28 @@ package com.ether.pms.auth.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.auth.entity.Permission; import com.ether.pms.auth.repository.PermissionRepository; +import com.ether.pms.auth.service.impl.PermissionServiceImpl; import com.ether.pms.common.BusinessException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class PermissionServiceTest { - @Mock - private PermissionRepository permissionRepository; + @Mock private PermissionRepository permissionRepository; - @InjectMocks - private PermissionService permissionService; + @InjectMocks private PermissionServiceImpl permissionService; @Test void findById_shouldReturnPermission_whenExists() { @@ -152,7 +150,8 @@ class PermissionServiceTest { updateData.setDescription("新描述"); when(permissionRepository.findById(permId)).thenReturn(Optional.of(existingPerm)); - when(permissionRepository.save(any(Permission.class))).thenAnswer(inv -> inv.getArgument(0)); + when(permissionRepository.save(any(Permission.class))) + .thenAnswer(inv -> inv.getArgument(0)); Permission result = permissionService.update(permId, updateData); @@ -174,7 +173,8 @@ class PermissionServiceTest { updateData.setMethod("POST"); when(permissionRepository.findById(permId)).thenReturn(Optional.of(existingPerm)); - when(permissionRepository.save(any(Permission.class))).thenAnswer(inv -> inv.getArgument(0)); + when(permissionRepository.save(any(Permission.class))) + .thenAnswer(inv -> inv.getArgument(0)); Permission result = permissionService.update(permId, updateData); @@ -187,7 +187,8 @@ class PermissionServiceTest { UUID permId = UUID.randomUUID(); when(permissionRepository.findById(permId)).thenReturn(Optional.empty()); - assertThrows(BusinessException.class, () -> permissionService.update(permId, new Permission())); + assertThrows( + BusinessException.class, () -> permissionService.update(permId, new Permission())); } @Test diff --git a/module-auth/src/test/java/com/ether/pms/auth/service/RoleServiceTest.java b/module-auth/src/test/java/com/ether/pms/auth/service/RoleServiceTest.java index 0d72be3..c6b7eae 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/service/RoleServiceTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/service/RoleServiceTest.java @@ -1,35 +1,35 @@ package com.ether.pms.auth.service; -import com.ether.pms.auth.entity.Role; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.auth.entity.Permission; -import com.ether.pms.auth.repository.RoleRepository; +import com.ether.pms.auth.entity.Role; import com.ether.pms.auth.repository.PermissionRepository; +import com.ether.pms.auth.repository.RoleRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.impl.RoleServiceImpl; import com.ether.pms.common.BusinessException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class RoleServiceTest { - @Mock - private RoleRepository roleRepository; + @Mock private RoleRepository roleRepository; - @Mock - private PermissionRepository permissionRepository; + @Mock private PermissionRepository permissionRepository; - @InjectMocks - private RoleService roleService; + @Mock private UserRepository userRepository; + + @InjectMocks private RoleServiceImpl roleService; @Test void findById_shouldReturnRole_whenExists() { @@ -176,7 +176,8 @@ class RoleServiceTest { perm2.setId(permId2); when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); - when(permissionRepository.findAllById(List.of(permId1, permId2))).thenReturn(List.of(perm1, perm2)); + when(permissionRepository.findAllById(List.of(permId1, permId2))) + .thenReturn(List.of(perm1, perm2)); when(roleRepository.save(any(Role.class))).thenAnswer(inv -> inv.getArgument(0)); roleService.assignPermissions(roleId, List.of(permId1, permId2)); diff --git a/module-auth/src/test/java/com/ether/pms/auth/service/UserManagementServiceTest.java b/module-auth/src/test/java/com/ether/pms/auth/service/UserManagementServiceTest.java index b2baa20..0a2eb46 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/service/UserManagementServiceTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/service/UserManagementServiceTest.java @@ -1,53 +1,51 @@ package com.ether.pms.auth.service; -import com.ether.pms.auth.entity.ProjectStaff; -import com.ether.pms.auth.entity.ProjectStaffRole; -import com.ether.pms.auth.entity.Role; -import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.repository.ProjectStaffRepository; -import com.ether.pms.auth.repository.ProjectStaffRoleRepository; -import com.ether.pms.auth.repository.UserRepository; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.*; +import com.ether.pms.auth.entity.ProjectStaff; +import com.ether.pms.auth.entity.ProjectStaffRole; +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.EnterpriseUserRepository; +import com.ether.pms.auth.repository.ProjectStaffRepository; +import com.ether.pms.auth.repository.ProjectStaffRoleRepository; +import com.ether.pms.auth.repository.ResidentRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.impl.UserManagementServiceImpl; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + @ExtendWith(MockitoExtension.class) class UserManagementServiceTest { - @Mock - private UserRepository userRepository; + @Mock private UserRepository userRepository; - @Mock - private ProjectStaffRepository projectStaffRepository; + @Mock private EnterpriseUserRepository enterpriseUserRepository; - @Mock - private ProjectStaffRoleRepository projectStaffRoleRepository; + @Mock private ProjectStaffRepository projectStaffRepository; - @Mock - private RoleService roleService; + @Mock private ProjectStaffRoleRepository projectStaffRoleRepository; - @Mock - private PasswordEncoder passwordEncoder; + @Mock private RoleService roleService; - @InjectMocks - private UserManagementService userManagementService; + @Mock private ResidentRepository residentRepository; + + @Mock private PasswordService passwordService; + + @InjectMocks private UserManagementServiceImpl userManagementService; @Test void assignStaffToProject_shouldCreateNewStaff_whenUserNotInProject() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); UUID roleId = UUID.randomUUID(); @@ -71,15 +69,16 @@ class UserManagementServiceTest { savedStaff.setStaffRoles(new ArrayList<>()); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.empty()); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.empty()); when(projectStaffRepository.save(any(ProjectStaff.class))).thenReturn(savedStaff); when(roleService.findById(roleId)).thenReturn(mockRole); - when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); + ProjectStaff result = + userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); - // Then assertNotNull(result); assertEquals(mockUser, result.getUser()); assertEquals(projectId, result.getProjectId()); @@ -89,8 +88,8 @@ class UserManagementServiceTest { } @Test - void assignStaffToProject_shouldReuseExistingStaffAndUpdateStaffType_whenUserAlreadyInProject() { - // Given + void + assignStaffToProject_shouldReuseExistingStaffAndUpdateStaffType_whenUserAlreadyInProject() { UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); UUID roleId = UUID.randomUUID(); @@ -114,15 +113,17 @@ class UserManagementServiceTest { existingStaff.setStaffRoles(new ArrayList<>()); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.of(existingStaff)); - when(projectStaffRepository.save(any(ProjectStaff.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.of(existingStaff)); + when(projectStaffRepository.save(any(ProjectStaff.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); when(roleService.findById(roleId)).thenReturn(mockRole); - when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); + ProjectStaff result = + userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); - // Then assertNotNull(result); assertEquals(existingStaff.getId(), result.getId()); assertEquals("SECURITY", result.getStaffType()); @@ -132,7 +133,6 @@ class UserManagementServiceTest { @Test void assignStaffToProject_shouldClearRolesAndAddNewRoles_whenRoleIdsProvided() { - // Given: 用户已有一个角色 UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); UUID newRoleId = UUID.randomUUID(); @@ -150,42 +150,43 @@ class UserManagementServiceTest { existingStaff.setId(UUID.randomUUID()); existingStaff.setUser(mockUser); existingStaff.setProjectId(projectId); - existingStaff.setStaffType("GENERAL"); // staffType相同,不会触发staff.save - existingStaff.setStaffRoles(new ArrayList<>()); // 空的,不需要删除 + existingStaff.setStaffType("GENERAL"); + existingStaff.setStaffRoles(new ArrayList<>()); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.of(existingStaff)); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.of(existingStaff)); when(roleService.findById(newRoleId)).thenReturn(newRole); - when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, newRoleIds); + ProjectStaff result = + userManagementService.assignStaffToProject( + userId, projectId, staffType, newRoleIds); - // Then assertNotNull(result); - verify(projectStaffRoleRepository, never()).deleteAll(anyList()); // 没有旧角色,不需要删除 + verify(projectStaffRoleRepository, never()).deleteAll(anyList()); verify(projectStaffRoleRepository, times(1)).save(any(ProjectStaffRole.class)); assertEquals(1, result.getStaffRoles().size()); } @Test void assignStaffToProject_shouldThrowException_whenUserNotFound() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); List roleIds = List.of(); when(userRepository.findById(userId)).thenReturn(Optional.empty()); - // Then - assertThrows(IllegalArgumentException.class, () -> - userManagementService.assignStaffToProject(userId, projectId, "GENERAL", roleIds) - ); + assertThrows( + IllegalArgumentException.class, + () -> + userManagementService.assignStaffToProject( + userId, projectId, "GENERAL", roleIds)); } @Test void assignStaffToProject_shouldHandleMultipleRoles() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); UUID roleId1 = UUID.randomUUID(); @@ -213,24 +214,23 @@ class UserManagementServiceTest { savedStaff.setStaffRoles(new ArrayList<>()); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.empty()); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.empty()); when(projectStaffRepository.save(any(ProjectStaff.class))).thenReturn(savedStaff); when(roleService.findById(roleId1)).thenReturn(mockRole1); when(roleService.findById(roleId2)).thenReturn(mockRole2); - when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); + ProjectStaff result = + userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); - // Then assertNotNull(result); - // 应该添加两个角色 verify(projectStaffRoleRepository, times(2)).save(any(ProjectStaffRole.class)); } @Test void assignStaffToProject_shouldHandleEmptyRoleIds() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); String staffType = "GENERAL"; @@ -246,21 +246,19 @@ class UserManagementServiceTest { savedStaff.setStaffRoles(new ArrayList<>()); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.empty()); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.empty()); when(projectStaffRepository.save(any(ProjectStaff.class))).thenReturn(savedStaff); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); + ProjectStaff result = + userManagementService.assignStaffToProject(userId, projectId, staffType, roleIds); - // Then assertNotNull(result); - // 不应该添加任何角色 verify(projectStaffRoleRepository, never()).save(any(ProjectStaffRole.class)); } @Test void assignStaffToProject_shouldUseDefaultStaffType_whenStaffTypeIsNull() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); List roleIds = List.of(); @@ -274,24 +272,25 @@ class UserManagementServiceTest { savedStaff.setProjectId(projectId); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.empty()); - when(projectStaffRepository.save(any(ProjectStaff.class))).thenAnswer(invocation -> { - ProjectStaff staff = invocation.getArgument(0); - staff.setId(UUID.randomUUID()); - return staff; - }); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.empty()); + when(projectStaffRepository.save(any(ProjectStaff.class))) + .thenAnswer( + invocation -> { + ProjectStaff staff = invocation.getArgument(0); + staff.setId(UUID.randomUUID()); + return staff; + }); - // When - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, null, roleIds); + ProjectStaff result = + userManagementService.assignStaffToProject(userId, projectId, null, roleIds); - // Then assertNotNull(result); assertEquals("PROJECT_STAFF", result.getStaffType()); } @Test void assignStaffToProject_shouldReplaceRoles_whenCalledAgain() { - // Given: 用户已有一个角色 UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); UUID oldRoleId = UUID.randomUUID(); @@ -317,18 +316,20 @@ class UserManagementServiceTest { existingStaff.setId(UUID.randomUUID()); existingStaff.setUser(mockUser); existingStaff.setProjectId(projectId); - existingStaff.setStaffType("GENERAL"); // staffType相同,不会触发staff.save + existingStaff.setStaffType("GENERAL"); existingStaff.setStaffRoles(new ArrayList<>(List.of(existingStaffRole))); when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.of(existingStaff)); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.of(existingStaff)); when(roleService.findById(newRoleId)).thenReturn(newRole); - when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(projectStaffRoleRepository.save(any(ProjectStaffRole.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - // When: 重新传入新的角色列表 - ProjectStaff result = userManagementService.assignStaffToProject(userId, projectId, staffType, newRoleIds); + ProjectStaff result = + userManagementService.assignStaffToProject( + userId, projectId, staffType, newRoleIds); - // Then: 旧角色应该被删除,新角色应该被添加 assertNotNull(result); verify(projectStaffRoleRepository, times(1)).deleteAll(anyList()); verify(projectStaffRoleRepository, times(1)).save(any(ProjectStaffRole.class)); @@ -338,7 +339,6 @@ class UserManagementServiceTest { @Test void getProjectStaffsWithRoles_shouldReturnStaffList_withRoles() { - // Given UUID projectId = UUID.randomUUID(); UUID userId = UUID.randomUUID(); UUID roleId = UUID.randomUUID(); @@ -364,10 +364,8 @@ class UserManagementServiceTest { when(projectStaffRepository.findByProjectIdWithRoles(projectId)).thenReturn(List.of(staff)); - // When List result = userManagementService.getProjectStaffsWithRoles(projectId); - // Then assertNotNull(result); assertEquals(1, result.size()); assertEquals(mockUser, result.get(0).getUser()); @@ -377,22 +375,18 @@ class UserManagementServiceTest { @Test void getProjectStaffsWithRoles_shouldReturnEmptyList_whenNoStaffInProject() { - // Given UUID projectId = UUID.randomUUID(); when(projectStaffRepository.findByProjectIdWithRoles(projectId)).thenReturn(List.of()); - // When List result = userManagementService.getProjectStaffsWithRoles(projectId); - // Then assertNotNull(result); assertTrue(result.isEmpty()); } @Test void removeStaffFromProject_shouldDeleteStaff_whenExists() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); @@ -400,28 +394,25 @@ class UserManagementServiceTest { existingStaff.setId(UUID.randomUUID()); existingStaff.setProjectId(projectId); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.of(existingStaff)); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.of(existingStaff)); doNothing().when(projectStaffRepository).delete(existingStaff); - // When userManagementService.removeStaffFromProject(userId, projectId); - // Then verify(projectStaffRepository).delete(existingStaff); } @Test void removeStaffFromProject_shouldDoNothing_whenStaffNotExists() { - // Given UUID userId = UUID.randomUUID(); UUID projectId = UUID.randomUUID(); - when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)).thenReturn(Optional.empty()); + when(projectStaffRepository.findByUserIdAndProjectId(userId, projectId)) + .thenReturn(Optional.empty()); - // When userManagementService.removeStaffFromProject(userId, projectId); - // Then verify(projectStaffRepository, never()).delete(any()); } } diff --git a/module-auth/src/test/java/com/ether/pms/auth/service/UserServiceTest.java b/module-auth/src/test/java/com/ether/pms/auth/service/UserServiceTest.java index db29350..7261ab8 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/service/UserServiceTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/service/UserServiceTest.java @@ -1,37 +1,33 @@ package com.ether.pms.auth.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.auth.entity.User; import com.ether.pms.auth.repository.RoleRepository; import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.service.impl.UserServiceImpl; import com.ether.pms.common.BusinessException; import com.ether.pms.common.ErrorCode; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class UserServiceTest { - @Mock - private UserRepository userRepository; + @Mock private UserRepository userRepository; - @Mock - private RoleRepository roleRepository; + @Mock private RoleRepository roleRepository; - @Mock - private PasswordService passwordService; + @Mock private PasswordService passwordService; - @InjectMocks - private UserService userService; + @InjectMocks private UserServiceImpl userService; @Test void findById_shouldReturnUser_whenExists() { @@ -39,11 +35,11 @@ class UserServiceTest { User mockUser = new User(); mockUser.setId(userId); mockUser.setUsername("testuser"); - + when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); - + User result = userService.findById(userId); - + assertNotNull(result); assertEquals("testuser", result.getUsername()); } @@ -52,7 +48,7 @@ class UserServiceTest { void findById_shouldThrowException_whenNotExists() { UUID userId = UUID.randomUUID(); when(userRepository.findById(userId)).thenReturn(Optional.empty()); - + assertThrows(BusinessException.class, () -> userService.findById(userId)); } @@ -60,11 +56,11 @@ class UserServiceTest { void findByUsername_shouldReturnUser_whenExists() { User mockUser = new User(); mockUser.setUsername("testuser"); - + when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(mockUser)); - + User result = userService.findByUsername("testuser"); - + assertNotNull(result); assertEquals("testuser", result.getUsername()); } @@ -74,9 +70,9 @@ class UserServiceTest { User newUser = new User(); newUser.setUsername("existinguser"); newUser.setPassword("Test123!"); - + when(userRepository.existsByUsername("existinguser")).thenReturn(true); - + assertThrows(BusinessException.class, () -> userService.create(newUser)); } @@ -85,11 +81,12 @@ class UserServiceTest { User newUser = new User(); newUser.setUsername("newuser"); newUser.setPassword("weak"); - + when(userRepository.existsByUsername("newuser")).thenReturn(false); doThrow(new BusinessException(ErrorCode.PASSWORD_001)) - .when(passwordService).validateStrength("weak"); - + .when(passwordService) + .validateStrength("weak"); + assertThrows(BusinessException.class, () -> userService.create(newUser)); } @@ -98,15 +95,15 @@ class UserServiceTest { User newUser = new User(); newUser.setUsername("newuser"); newUser.setPassword("Valid123!"); - + when(userRepository.existsByUsername("newuser")).thenReturn(false); doNothing().when(passwordService).validateStrength("Valid123!"); when(passwordService.isWeakPassword("Valid123!")).thenReturn(false); when(passwordService.encode("Valid123!")).thenReturn("encodedPassword"); when(userRepository.save(any(User.class))).thenReturn(newUser); - + User result = userService.create(newUser); - + assertNotNull(result); verify(passwordService).encode("Valid123!"); } @@ -117,21 +114,22 @@ class UserServiceTest { User existingUser = new User(); existingUser.setId(userId); existingUser.setPassword("encodedOld"); - + when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser)); when(passwordService.matches("wrongOld", "encodedOld")).thenReturn(false); - - assertThrows(BusinessException.class, - () -> userService.updatePassword(userId, "wrongOld", "New123!")); + + assertThrows( + BusinessException.class, + () -> userService.updatePassword(userId, "wrongOld", "New123!")); } @Test void delete_shouldCallRepository() { UUID userId = UUID.randomUUID(); doNothing().when(userRepository).deleteById(userId); - + userService.delete(userId); - + verify(userRepository).deleteById(userId); } } diff --git a/module-auth/src/test/java/com/ether/pms/auth/util/JwtTokenProviderTest.java b/module-auth/src/test/java/com/ether/pms/auth/util/JwtTokenProviderTest.java index cc9de82..01a2966 100644 --- a/module-auth/src/test/java/com/ether/pms/auth/util/JwtTokenProviderTest.java +++ b/module-auth/src/test/java/com/ether/pms/auth/util/JwtTokenProviderTest.java @@ -1,15 +1,12 @@ package com.ether.pms.auth.util; +import static org.assertj.core.api.Assertions.*; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.*; - -/** - * JWT Token Provider 单元测试 - * 验证启动时 Secret 校验逻辑 - */ +/** JWT Token Provider 单元测试 验证启动时 Secret 校验逻辑 */ class JwtTokenProviderTest { private JwtTokenProvider jwtTokenProvider; @@ -70,8 +67,7 @@ class JwtTokenProviderTest { setSecretValue(shortSecret); // 不应该抛出异常,只是警告 - assertThatCode(() -> jwtTokenProvider.afterPropertiesSet()) - .doesNotThrowAnyException(); + assertThatCode(() -> jwtTokenProvider.afterPropertiesSet()).doesNotThrowAnyException(); } @Test @@ -81,13 +77,10 @@ class JwtTokenProviderTest { setSecretValue(secureSecret); // 应该正常通过 - assertThatCode(() -> jwtTokenProvider.afterPropertiesSet()) - .doesNotThrowAnyException(); + assertThatCode(() -> jwtTokenProvider.afterPropertiesSet()).doesNotThrowAnyException(); } - /** - * 使用反射设置 private 字段 secret 的值 - */ + /** 使用反射设置 private 字段 secret 的值 */ private void setSecretValue(String value) { try { var field = JwtTokenProvider.class.getDeclaredField("secret"); diff --git a/module-auth/src/test/resources/application.yml b/module-auth/src/test/resources/application.yml new file mode 100644 index 0000000..c6ca621 --- /dev/null +++ b/module-auth/src/test/resources/application.yml @@ -0,0 +1,23 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL + username: sa + password: + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create-drop + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + + h2: + console: + enabled: false + +jwt: + secret: test-secret-key-for-unit-testing-at-least-32-characters-long + expiration: 86400000 + issuer: ether-pms-test diff --git a/module-common/src/main/java/com/ether/pms/common/ApiResponse.java b/module-common/src/main/java/com/ether/pms/common/ApiResponse.java index 5229493..091cb85 100644 --- a/module-common/src/main/java/com/ether/pms/common/ApiResponse.java +++ b/module-common/src/main/java/com/ether/pms/common/ApiResponse.java @@ -4,41 +4,40 @@ import lombok.Data; @Data public class ApiResponse { - + private int code; private String message; private T data; - - public ApiResponse() { - } - + + public ApiResponse() {} + public ApiResponse(int code, String message) { this.code = code; this.message = message; } - + public ApiResponse(int code, String message, T data) { this.code = code; this.message = message; this.data = data; } - + public static ApiResponse success() { return new ApiResponse<>(200, "success"); } - + public static ApiResponse success(T data) { return new ApiResponse<>(200, "success", data); } - + public static ApiResponse success(String message, T data) { return new ApiResponse<>(200, message, data); } - + public static ApiResponse error(int code, String message) { return new ApiResponse<>(code, message); } - + public static ApiResponse error(String message) { return new ApiResponse<>(500, message); } diff --git a/module-common/src/main/java/com/ether/pms/common/BusinessException.java b/module-common/src/main/java/com/ether/pms/common/BusinessException.java index 804dc7d..761bc0c 100644 --- a/module-common/src/main/java/com/ether/pms/common/BusinessException.java +++ b/module-common/src/main/java/com/ether/pms/common/BusinessException.java @@ -4,22 +4,22 @@ import lombok.Getter; @Getter public class BusinessException extends RuntimeException { - + private final int code; private final String message; - + public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage()); this.code = errorCode.getCode(); this.message = errorCode.getMessage(); } - + public BusinessException(ErrorCode errorCode, String customMessage) { super(customMessage); this.code = errorCode.getCode(); this.message = customMessage; } - + public BusinessException(int code, String message) { super(message); this.code = code; diff --git a/module-common/src/main/java/com/ether/pms/common/ErrorCode.java b/module-common/src/main/java/com/ether/pms/common/ErrorCode.java index a90cc7e..082a9ed 100644 --- a/module-common/src/main/java/com/ether/pms/common/ErrorCode.java +++ b/module-common/src/main/java/com/ether/pms/common/ErrorCode.java @@ -1,20 +1,19 @@ package com.ether.pms.common; public enum ErrorCode { - SUCCESS(200, "success"), - + BAD_REQUEST(400, "请求参数错误"), UNAUTHORIZED(401, "未授权"), FORBIDDEN(403, "禁止访问"), NOT_FOUND(404, "资源不存在"), - + AUTH_001(1001, "用户名或密码错误"), AUTH_002(1002, "账号已被锁定"), AUTH_003(1003, "账号已被禁用"), AUTH_004(1004, "Token已过期"), AUTH_005(1005, "Token无效"), - + USER_001(2001, "用户名已存在"), USER_002(2002, "手机号已存在"), USER_003(2003, "用户不存在"), @@ -28,20 +27,20 @@ public enum ErrorCode { PASSWORD_006(2016, "密码必须包含数字"), PASSWORD_007(2017, "密码必须包含特殊字符"), PASSWORD_008(2018, "密码强度不足,请使用更复杂的密码"), - + ROLE_001(3001, "角色编码已存在"), ROLE_002(3002, "角色不存在"), - + PERMISSION_001(4001, "权限编码已存在"), PERMISSION_002(4002, "权限不存在"), - + PROJECT_001(5001, "项目编码已存在"), PROJECT_002(5002, "项目不存在"), PROJECT_003(5003, "项目状态转换无效"), PROJECT_004(5004, "项目成员已存在"), PROJECT_005(5005, "项目成员不存在"), PROJECT_006(5006, "项目配置不存在"), - + SPACE_001(6001, "空间节点编码已存在"), SPACE_002(6002, "空间节点不存在"), SPACE_003(6003, "空间节点存在子节点,无法删除"), @@ -53,19 +52,19 @@ public enum ErrorCode { FILE_004(7004, "Excel 行数超出限制"), SYSTEM_ERROR(9999, "系统错误"); - + private final int code; private final String message; - + ErrorCode(int code, String message) { this.code = code; this.message = message; } - + public int getCode() { return code; } - + public String getMessage() { return message; } diff --git a/module-common/src/main/java/com/ether/pms/common/GlobalExceptionHandler.java b/module-common/src/main/java/com/ether/pms/common/GlobalExceptionHandler.java index 6649089..e694e51 100644 --- a/module-common/src/main/java/com/ether/pms/common/GlobalExceptionHandler.java +++ b/module-common/src/main/java/com/ether/pms/common/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ package com.ether.pms.common; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; @@ -19,16 +20,11 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; -import java.util.stream.Collectors; - /** * 全局异常处理器 - *

- * 设计原则: - * 1. 统一响应格式:所有异常都返回 ApiResponse - * 2. 安全性:不向客户端暴露技术细节(堆栈、SQL、类名) - * 3. 可观测性:服务端记录完整异常信息供运维排查 - * 4. 用户友好:返回中文友好提示信息 + * + *

设计原则: 1. 统一响应格式:所有异常都返回 ApiResponse 2. 安全性:不向客户端暴露技术细节(堆栈、SQL、类名) 3. 可观测性:服务端记录完整异常信息供运维排查 4. + * 用户友好:返回中文友好提示信息 */ @RestControllerAdvice @Slf4j @@ -36,208 +32,182 @@ public class GlobalExceptionHandler { // ==================== 业务异常 (400-499) ==================== - /** - * 处理业务自定义异常 - */ + /** 处理业务自定义异常 */ @ExceptionHandler(BusinessException.class) - public ResponseEntity> handleBusinessException(BusinessException e, HttpServletRequest request) { - log.warn("业务异常 [{} {}]: code={}, message={}", - request.getMethod(), request.getRequestURI(), - e.getCode(), e.getMessage()); + public ResponseEntity> handleBusinessException( + BusinessException e, HttpServletRequest request) { + log.warn( + "业务异常 [{} {}]: code={}, message={}", + request.getMethod(), + request.getRequestURI(), + e.getCode(), + e.getMessage()); HttpStatus status = mapErrorCodeToHttpStatus(e.getCode()); - return ResponseEntity - .status(status) - .body(ApiResponse.error(e.getCode(), e.getMessage())); + return ResponseEntity.status(status).body(ApiResponse.error(e.getCode(), e.getMessage())); } - /** - * 处理 @Valid 校验失败(请求体参数校验) - */ + /** 处理 @Valid 校验失败(请求体参数校验) */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentNotValidException( MethodArgumentNotValidException e, HttpServletRequest request) { - log.warn("参数校验失败 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); + log.warn( + "参数校验失败 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - String message = e.getBindingResult().getFieldErrors().stream() - .map(error -> error.getField() + ": " + error.getDefaultMessage()) - .collect(Collectors.joining("; ")); + String message = + e.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining("; ")); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "参数校验失败: " + message)); } - /** - * 处理参数校验失败(@Validated 方法级别校验) - */ + /** 处理参数校验失败(@Validated 方法级别校验) */ @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity> handleConstraintViolationException( ConstraintViolationException e, HttpServletRequest request) { - log.warn("约束校验失败 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); + log.warn( + "约束校验失败 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); - String message = e.getConstraintViolations().stream() - .map(ConstraintViolation::getMessage) - .collect(Collectors.joining("; ")); + String message = + e.getConstraintViolations().stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining("; ")); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "参数校验失败: " + message)); } - /** - * 处理请求体解析错误(JSON 格式错误等) - */ + /** 处理请求体解析错误(JSON 格式错误等) */ @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity> handleHttpMessageNotReadableException( HttpMessageNotReadableException e, HttpServletRequest request) { - log.warn("请求体解析错误 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) + log.warn( + "请求体解析错误 [{} {}]: {}", + request.getMethod(), + request.getRequestURI(), + e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "请求数据格式错误,请检查请求参数")); } - /** - * 处理缺少必须的请求参数 - */ + /** 处理缺少必须的请求参数 */ @ExceptionHandler(MissingServletRequestParameterException.class) public ResponseEntity> handleMissingServletRequestParameterException( MissingServletRequestParameterException e, HttpServletRequest request) { - log.warn("缺少请求参数 [{} {}]: parameter={}", - request.getMethod(), request.getRequestURI(), e.getParameterName()); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), - "缺少必需参数: " + e.getParameterName())); + log.warn( + "缺少请求参数 [{} {}]: parameter={}", + request.getMethod(), + request.getRequestURI(), + e.getParameterName()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body( + ApiResponse.error( + ErrorCode.BAD_REQUEST.getCode(), + "缺少必需参数: " + e.getParameterName())); } // ==================== HTTP 协议异常 ==================== - /** - * 处理 HTTP 方法不支持 - */ + /** 处理 HTTP 方法不支持 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity> handleHttpRequestMethodNotSupportedException( HttpRequestMethodNotSupportedException e, HttpServletRequest request) { - log.warn("不支持的HTTP方法 [{} {}]: method={}", - request.getMethod(), request.getRequestURI(), e.getMethod()); - return ResponseEntity - .status(HttpStatus.METHOD_NOT_ALLOWED) + log.warn( + "不支持的HTTP方法 [{} {}]: method={}", + request.getMethod(), + request.getRequestURI(), + e.getMethod()); + return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED) .body(ApiResponse.error(405, "不支持的请求方式: " + e.getMethod())); } - /** - * 处理媒体类型不接受 - */ + /** 处理媒体类型不接受 */ @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) public ResponseEntity> handleHttpMediaTypeNotAcceptableException( HttpMediaTypeNotAcceptableException e, HttpServletRequest request) { - log.warn("不支持的媒体类型 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); - return ResponseEntity - .status(HttpStatus.NOT_ACCEPTABLE) + log.warn( + "不支持的媒体类型 [{} {}]: {}", + request.getMethod(), + request.getRequestURI(), + e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE) .body(ApiResponse.error(406, "不支持的响应格式")); } - /** - * 处理无处理器找到(404) - */ + /** 处理无处理器找到(404) */ @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity> handleNoHandlerFoundException( NoHandlerFoundException e, HttpServletRequest request) { - log.warn("资源不存在 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getHttpMethod() + " " + e.getRequestURL()); - return ResponseEntity - .status(HttpStatus.NOT_FOUND) + log.warn( + "资源不存在 [{} {}]: {}", + request.getMethod(), + request.getRequestURI(), + e.getHttpMethod() + " " + e.getRequestURL()); + return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(ApiResponse.error(ErrorCode.NOT_FOUND.getCode(), "请求的资源不存在")); } // ==================== 安全相关异常 ==================== - /** - * 处理认证失败(401) - */ + /** 处理认证失败(401) */ @ExceptionHandler(AuthenticationException.class) public ResponseEntity> handleAuthenticationException( AuthenticationException e, HttpServletRequest request) { - log.warn("认证失败 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); - return ResponseEntity - .status(HttpStatus.UNAUTHORIZED) + log.warn("认证失败 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(ApiResponse.error(ErrorCode.UNAUTHORIZED.getCode(), "认证失败,请重新登录")); } - /** - * 处理权限不足(403) - */ + /** 处理权限不足(403) */ @ExceptionHandler(AccessDeniedException.class) public ResponseEntity> handleAccessDeniedException( AccessDeniedException e, HttpServletRequest request) { - log.warn("权限不足 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); - return ResponseEntity - .status(HttpStatus.FORBIDDEN) + log.warn("权限不足 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(ApiResponse.error(ErrorCode.FORBIDDEN.getCode(), "权限不足,无法访问该资源")); } // ==================== 数据库异常 ==================== - /** - * 处理数据完整性违反异常(唯一约束、外键约束等) - */ + /** 处理数据完整性违反异常(唯一约束、外键约束等) */ @ExceptionHandler(DataIntegrityViolationException.class) public ResponseEntity> handleDataIntegrityViolationException( DataIntegrityViolationException e, HttpServletRequest request) { - log.error("数据完整性违规 [{} {}]", - request.getMethod(), request.getRequestURI(), e); + log.error("数据完整性违规 [{} {}]", request.getMethod(), request.getRequestURI(), e); // 不暴露具体的数据库错误信息,防止SQL注入线索泄露 - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(ErrorCode.SYSTEM_ERROR.getCode(), "数据操作失败,请联系管理员")); } - /** - * 处理通用数据库访问异常 - */ + /** 处理通用数据库访问异常 */ @ExceptionHandler(DataAccessException.class) public ResponseEntity> handleDataAccessException( DataAccessException e, HttpServletRequest request) { - log.error("数据库访问异常 [{} {}]", - request.getMethod(), request.getRequestURI(), e); + log.error("数据库访问异常 [{} {}]", request.getMethod(), request.getRequestURI(), e); // 不暴露SQL语句和数据库细节 - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(ErrorCode.SYSTEM_ERROR.getCode(), "数据操作失败,请联系管理员")); } // ==================== 参数异常(通用) ==================== - /** - * 处理非法参数异常(包括批量操作限制等) - */ + /** 处理非法参数异常(包括批量操作限制等) */ @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity> handleIllegalArgumentException( IllegalArgumentException e, HttpServletRequest request) { - log.warn("参数错误 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); + log.warn("参数错误 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); // 返回批量操作限制的具体消息给用户 - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) + return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), e.getMessage())); } - /** - * 处理非法状态异常 - */ + /** 处理非法状态异常 */ @ExceptionHandler(IllegalStateException.class) public ResponseEntity> handleIllegalStateException( IllegalStateException e, HttpServletRequest request) { - log.warn("状态异常 [{} {}]: {}", - request.getMethod(), request.getRequestURI(), e.getMessage()); - return ResponseEntity - .status(HttpStatus.BAD_REQUEST) + log.warn("状态异常 [{} {}]: {}", request.getMethod(), request.getRequestURI(), e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(ErrorCode.BAD_REQUEST.getCode(), "操作状态异常,请刷新后重试")); } @@ -245,28 +215,21 @@ public class GlobalExceptionHandler { /** * 兜底处理所有未捕获的异常 - *

- * 安全要求: - * - 绝对不能向客户端暴露堆栈信息、类名、SQL等技术细节 - * - 必须在服务端记录完整的异常日志供运维排查 - * - 返回用户友好的通用错误消息 + * + *

安全要求: - 绝对不能向客户端暴露堆栈信息、类名、SQL等技术细节 - 必须在服务端记录完整的异常日志供运维排查 - 返回用户友好的通用错误消息 */ @ExceptionHandler(Exception.class) public ResponseEntity> handleException( Exception e, HttpServletRequest request) { - log.error("系统内部错误 [{} {}]", - request.getMethod(), request.getRequestURI(), e); + log.error("系统内部错误 [{} {}]", request.getMethod(), request.getRequestURI(), e); // 生产环境绝对不能暴露任何技术细节 - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(ErrorCode.SYSTEM_ERROR.getCode(), "服务器内部错误,请稍后重试")); } // ==================== 工具方法 ==================== - /** - * 将业务错误码映射到 HTTP 状态码 - */ + /** 将业务错误码映射到 HTTP 状态码 */ private HttpStatus mapErrorCodeToHttpStatus(int errorCode) { if (errorCode >= 400 && errorCode < 600) { try { diff --git a/module-common/src/main/java/com/ether/pms/common/MaintenanceTaskRepository.java b/module-common/src/main/java/com/ether/pms/common/MaintenanceTaskRepository.java new file mode 100644 index 0000000..256994d --- /dev/null +++ b/module-common/src/main/java/com/ether/pms/common/MaintenanceTaskRepository.java @@ -0,0 +1,11 @@ +package com.ether.pms.common; + +import java.util.List; +import java.util.UUID; + +public interface MaintenanceTaskRepository { + + List findByEquipmentId(UUID equipmentId); + + record MaintenanceTaskInfo(UUID id, String status) {} +} diff --git a/module-common/src/main/java/com/ether/pms/common/util/BatchOperationValidator.java b/module-common/src/main/java/com/ether/pms/common/util/BatchOperationValidator.java index 888ab38..49d69cb 100644 --- a/module-common/src/main/java/com/ether/pms/common/util/BatchOperationValidator.java +++ b/module-common/src/main/java/com/ether/pms/common/util/BatchOperationValidator.java @@ -3,14 +3,15 @@ package com.ether.pms.common.util; /** * 批量操作校验工具类 * - *

用于限制批量操作的数据量,防止 DoS 攻击和服务器资源耗尽。

+ *

用于限制批量操作的数据量,防止 DoS 攻击和服务器资源耗尽。 + * + *

支持以下场景的校验: * - *

支持以下场景的校验:

*
    - *
  • 批量删除操作
  • - *
  • 批量导入操作
  • - *
  • 批量创建/更新操作
  • - *
  • 通用自定义限制
  • + *
  • 批量删除操作 + *
  • 批量导入操作 + *
  • 批量创建/更新操作 + *
  • 通用自定义限制 *
* * @author Ether开发团队 @@ -40,7 +41,7 @@ public class BatchOperationValidator { public static void validateDeleteSize(int size) { if (size > MAX_BATCH_DELETE_SIZE) { throw new IllegalArgumentException( - String.format("单次删除数量不能超过 %d 条,当前:%d", MAX_BATCH_DELETE_SIZE, size)); + String.format("单次删除数量不能超过 %d 条,当前:%d", MAX_BATCH_DELETE_SIZE, size)); } } @@ -53,7 +54,7 @@ public class BatchOperationValidator { public static void validateImportSize(int size) { if (size > MAX_BATCH_IMPORT_SIZE) { throw new IllegalArgumentException( - String.format("单次导入数量不能超过 %d 条,当前:%d", MAX_BATCH_IMPORT_SIZE, size)); + String.format("单次导入数量不能超过 %d 条,当前:%d", MAX_BATCH_IMPORT_SIZE, size)); } } @@ -66,7 +67,7 @@ public class BatchOperationValidator { public static void validateUpdateSize(int size) { if (size > MAX_BATCH_UPDATE_SIZE) { throw new IllegalArgumentException( - String.format("单次更新数量不能超过 %d 条,当前:%d", MAX_BATCH_UPDATE_SIZE, size)); + String.format("单次更新数量不能超过 %d 条,当前:%d", MAX_BATCH_UPDATE_SIZE, size)); } } @@ -79,22 +80,22 @@ public class BatchOperationValidator { public static void validateAssignmentSize(int size) { if (size > MAX_BATCH_ASSIGNMENT_SIZE) { throw new IllegalArgumentException( - String.format("单次分配数量不能超过 %d 条,当前:%d", MAX_BATCH_ASSIGNMENT_SIZE, size)); + String.format("单次分配数量不能超过 %d 条,当前:%d", MAX_BATCH_ASSIGNMENT_SIZE, size)); } } /** * 通用校验方法 - 自定义操作类型和限制 * - * @param size 当前操作的记录数 - * @param maxSize 最大允许数量 + * @param size 当前操作的记录数 + * @param maxSize 最大允许数量 * @param operation 操作名称(用于错误消息) * @throws IllegalArgumentException 如果超过最大限制 */ public static void validateSize(int size, int maxSize, String operation) { if (size > maxSize) { throw new IllegalArgumentException( - String.format("%s数量不能超过 %d 条,当前:%d", operation, maxSize, size)); + String.format("%s数量不能超过 %d 条,当前:%d", operation, maxSize, size)); } } diff --git a/module-common/src/main/java/com/ether/pms/common/util/LogMaskUtil.java b/module-common/src/main/java/com/ether/pms/common/util/LogMaskUtil.java index d661096..f92a452 100644 --- a/module-common/src/main/java/com/ether/pms/common/util/LogMaskUtil.java +++ b/module-common/src/main/java/com/ether/pms/common/util/LogMaskUtil.java @@ -3,19 +3,21 @@ package com.ether.pms.common.util; /** * 日志敏感信息脱敏工具类 * - *

用于对日志中的敏感信息进行脱敏处理,防止敏感数据泄露到日志文件中。

+ *

用于对日志中的敏感信息进行脱敏处理,防止敏感数据泄露到日志文件中。 * *

支持的脱敏类型:

+ * *
    - *
  • 手机号:138****1234
  • - *
  • 身份证号:110***********1234
  • - *
  • 邮箱:a***@email.com
  • - *
  • Token/JWT:eyJhbGciOi...
  • - *
  • 密码哈希:$2a$10$*************
  • - *
  • 用户名:a**n(保留首尾字符)
  • + *
  • 手机号:138****1234 + *
  • 身份证号:110***********1234 + *
  • 邮箱:a***@email.com + *
  • Token/JWT:eyJhbGciOi... + *
  • 密码哈希:$2a$10$************* + *
  • 用户名:a**n(保留首尾字符) *
* *

使用示例:

+ * *
{@code
  * // 手机号脱敏
  * String masked = LogMaskUtil.maskPhone("13812345678");
@@ -37,15 +39,13 @@ public class LogMaskUtil {
 
     private static final String MASK = "****";
 
-    /**
-     * 默认脱敏符数量
-     */
+    /** 默认脱敏符数量 */
     private static final int DEFAULT_MASK_LENGTH = 4;
 
     /**
      * 手机号脱敏:保留前3位和后4位,中间用 **** 替换
      *
-     * 

示例:13812345678 → 138****5678

+ *

示例:13812345678 → 138****5678 * * @param phone 原始手机号 * @return 脱敏后的手机号,如果输入为 null 或格式不正确返回 "[INVALID]" @@ -60,7 +60,7 @@ public class LogMaskUtil { /** * 身份证号脱敏:保留前3位和后4位,中间用 **** 替换 * - *

示例:110105199001011234 → 110***********1234

+ *

示例:110105199001011234 → 110***********1234 * * @param idCard 原始身份证号 * @return 脱敏后的身份证号,如果输入为 null 或长度不足返回 "[INVALID]" @@ -69,13 +69,16 @@ public class LogMaskUtil { if (idCard == null || idCard.length() < 8) { return "[INVALID]"; } - return idCard.substring(0, 3) + MASK + "*".repeat(Math.max(0, idCard.length() - 7)) + idCard.substring(idCard.length() - 4); + return idCard.substring(0, 3) + + MASK + + "*".repeat(Math.max(0, idCard.length() - 7)) + + idCard.substring(idCard.length() - 4); } /** * 邮箱脱敏:保留首字符和 @ 域名部分,中间用 *** 替换 * - *

示例:admin@email.com → a***@email.com

+ *

示例:admin@email.com → a***@email.com * * @param email 原始邮箱地址 * @return 脱敏后的邮箱,如果输入为 null 或不含 @ 返回 "[INVALID]" @@ -94,7 +97,7 @@ public class LogMaskUtil { /** * Token/JWT 脱敏:只显示前 10 个字符,后面用 ... 替换 * - *

示例:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0... → eyJhbGciOi...

+ *

示例:eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0... → eyJhbGciOi... * * @param token 原始 Token * @return 脱敏后的 Token,如果输入为 null 或太短返回 "***" @@ -109,7 +112,7 @@ public class LogMaskUtil { /** * 密码哈希脱敏:只显示前缀(如 $2a$10$),后面用 **** 替换 * - *

示例:$2a$10$N9qo8uLOickg2ZAmZqzKM... → $2a$10$****

+ *

示例:$2a$10$N9qo8uLOickg2ZAmZqzKM... → $2a$10$**** * * @param passwordHash 原始密码哈希 * @return 脱敏后的密码哈希,如果输入为 null 返回 "[NULL]" @@ -128,7 +131,7 @@ public class LogMaskUtil { /** * 用户名脱敏:保留首尾各1个字符,中间用 ** 替换 * - *

示例:admin → a**n

+ *

示例:admin → a**n * * @param username 原始用户名 * @return 脱敏后的用户名,如果输入为 null 或太短返回 "***" @@ -143,9 +146,9 @@ public class LogMaskUtil { /** * 通用字符串脱敏:保留前 prefixLen 和后 suffixLen 个字符,中间用 **** 替换 * - * @param value 原始字符串 - * @param prefixLen 保留的前缀长度 - * @param suffixLen 保留的后缀长度 + * @param value 原始字符串 + * @param prefixLen 保留的前缀长度 + * @param suffixLen 保留的后缀长度 * @return 脱敏后的字符串 */ public static String mask(String value, int prefixLen, int suffixLen) { diff --git a/module-common/src/main/java/com/ether/pms/common/util/PaginationValidator.java b/module-common/src/main/java/com/ether/pms/common/util/PaginationValidator.java index f3bf83b..bcec97e 100644 --- a/module-common/src/main/java/com/ether/pms/common/util/PaginationValidator.java +++ b/module-common/src/main/java/com/ether/pms/common/util/PaginationValidator.java @@ -6,9 +6,10 @@ import org.springframework.data.domain.Pageable; /** * 分页参数校验工具类 * - *

用于校验和规范化分页参数,防止恶意传入超大值导致 OOM 或数据库过载。

+ *

用于校验和规范化分页参数,防止恶意传入超大值导致 OOM 或数据库过载。 * *

使用示例:

+ * *
{@code
  * @GetMapping
  * public ResponseEntity>> list(
@@ -25,28 +26,23 @@ import org.springframework.data.domain.Pageable;
  */
 public class PaginationValidator {
 
-    /**
-     * 最大每页记录数(防止 OOM 和数据库过载)
-     */
+    /** 最大每页记录数(防止 OOM 和数据库过载) */
     public static final int MAX_PAGE_SIZE = 100;
 
-    /**
-     * 默认每页记录数
-     */
+    /** 默认每页记录数 */
     public static final int DEFAULT_PAGE_SIZE = 10;
 
-    /**
-     * 最小每页记录数
-     */
+    /** 最小每页记录数 */
     private static final int MIN_PAGE_SIZE = 1;
 
     /**
      * 校验并创建安全的 Pageable 对象
      *
-     * 

对传入的分页参数进行安全校验:

+ *

对传入的分页参数进行安全校验: + * *

    - *
  • page: 确保不小于 0
  • - *
  • size: 限制在 [1, {@link #MAX_PAGE_SIZE}] 范围内
  • + *
  • page: 确保不小于 0 + *
  • size: 限制在 [1, {@link #MAX_PAGE_SIZE}] 范围内 *
* * @param page 页码(从 0 开始) diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java index d50f1fc..15ca59c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/EnergyController.java @@ -7,17 +7,16 @@ import com.ether.pms.mdm.service.EnergyConsumptionService; import com.ether.pms.mdm.service.EnergyMeterService; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/ops/energy") @@ -54,7 +53,8 @@ public class EnergyController { } @PutMapping("/meters/{id}") - public ApiResponse updateMeter(@PathVariable UUID id, @Valid @RequestBody EnergyMeter meter) { + public ApiResponse updateMeter( + @PathVariable UUID id, @Valid @RequestBody EnergyMeter meter) { return ApiResponse.success(energyMeterService.updateMeter(id, meter)); } @@ -67,20 +67,26 @@ public class EnergyController { // ==================== 能耗记录 ==================== @PostMapping("/consumption") - public ApiResponse recordConsumption(@Valid @RequestBody RecordConsumptionRequest request) { - EnergyConsumption consumption = energyConsumptionService.recordConsumption( - request.getMeterId(), request.getCurrentReading(), request.getRecordedBy()); + public ApiResponse recordConsumption( + @Valid @RequestBody RecordConsumptionRequest request) { + EnergyConsumption consumption = + energyConsumptionService.recordConsumption( + request.getMeterId(), request.getCurrentReading(), request.getRecordedBy()); return ApiResponse.success(consumption); } @GetMapping("/consumption/{meterId}") public ApiResponse> getConsumption( @PathVariable UUID meterId, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + LocalDate endDate) { List consumptions; if (startDate != null && endDate != null) { - consumptions = energyConsumptionService.getConsumptionByMeterAndDateRange(meterId, startDate, endDate); + consumptions = + energyConsumptionService.getConsumptionByMeterAndDateRange( + meterId, startDate, endDate); } else { consumptions = energyConsumptionService.getConsumptionByMeter(meterId); } @@ -113,4 +119,4 @@ public class EnergyController { private UUID recordedBy; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionItemController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionItemController.java index 637a215..57fcbf5 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionItemController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionItemController.java @@ -4,16 +4,13 @@ import com.ether.pms.common.ApiResponse; import com.ether.pms.mdm.entity.InspectionItem; import com.ether.pms.mdm.service.InspectionItemService; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - -/** - * 巡检标准项控制器 - */ +/** 巡检标准项控制器 */ @RestController @RequestMapping("/api/mdm/inspection-items") @RequiredArgsConstructor @@ -36,7 +33,9 @@ public class InspectionItemController { if (activeOnly != null && activeOnly) { items = inspectionItemService.getActiveItems(); } else if (equipmentType != null && systemType != null) { - items = inspectionItemService.getItemsByEquipmentTypeAndSystemType(equipmentType, systemType); + items = + inspectionItemService.getItemsByEquipmentTypeAndSystemType( + equipmentType, systemType); } else if (equipmentType != null) { items = inspectionItemService.getItemsByEquipmentType(equipmentType); } else if (systemType != null) { @@ -53,7 +52,8 @@ public class InspectionItemController { } @PutMapping("/{id}") - public ApiResponse updateItem(@PathVariable UUID id, @Valid @RequestBody InspectionItem item) { + public ApiResponse updateItem( + @PathVariable UUID id, @Valid @RequestBody InspectionItem item) { return ApiResponse.success(inspectionItemService.updateItem(id, item)); } @@ -62,4 +62,4 @@ public class InspectionItemController { inspectionItemService.deleteItem(id); return ApiResponse.success(null); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionRecordController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionRecordController.java index a0991b2..6471fe4 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionRecordController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionRecordController.java @@ -4,17 +4,14 @@ import com.ether.pms.common.ApiResponse; import com.ether.pms.mdm.entity.InspectionRecord; import com.ether.pms.mdm.service.InspectionRecordService; import jakarta.validation.Valid; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; -import java.util.List; -import java.util.UUID; - -/** - * 巡检记录控制器 - */ +/** 巡检记录控制器 */ @RestController @RequestMapping("/api/mdm/inspection-records") @RequiredArgsConstructor @@ -38,7 +35,9 @@ public class InspectionRecordController { @RequestParam(required = false) LocalDate endDate) { List records; if (equipmentId != null && startDate != null && endDate != null) { - records = inspectionRecordService.getRecordsByEquipmentAndDateRange(equipmentId, startDate, endDate); + records = + inspectionRecordService.getRecordsByEquipmentAndDateRange( + equipmentId, startDate, endDate); } else if (equipmentId != null) { records = inspectionRecordService.getRecordsByEquipment(equipmentId); } else if (planId != null) { @@ -61,7 +60,8 @@ public class InspectionRecordController { } @PutMapping("/{id}") - public ApiResponse updateRecord(@PathVariable UUID id, @Valid @RequestBody InspectionRecord record) { + public ApiResponse updateRecord( + @PathVariable UUID id, @Valid @RequestBody InspectionRecord record) { return ApiResponse.success(inspectionRecordService.updateRecord(id, record)); } @@ -75,4 +75,4 @@ public class InspectionRecordController { public ApiResponse completeRecord(@PathVariable UUID id) { return ApiResponse.success(inspectionRecordService.completeRecord(id)); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java index 8f357c3..08d1a8f 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/InspectionTemplateController.java @@ -4,13 +4,12 @@ import com.ether.pms.common.ApiResponse; import com.ether.pms.mdm.entity.InspectionTemplate; import com.ether.pms.mdm.service.InspectionTemplateService; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - @RestController @RequestMapping("/api/ops/inspection-templates") @RequiredArgsConstructor @@ -21,12 +20,14 @@ public class InspectionTemplateController { @GetMapping public ApiResponse> getTemplates(@RequestParam UUID projectId) { - List templates = inspectionTemplateService.getTemplatesByProject(projectId); + List templates = + inspectionTemplateService.getTemplatesByProject(projectId); return ApiResponse.success(templates); } @PostMapping - public ApiResponse createTemplate(@Valid @RequestBody InspectionTemplate template) { + public ApiResponse createTemplate( + @Valid @RequestBody InspectionTemplate template) { InspectionTemplate created = inspectionTemplateService.createTemplate(template); return ApiResponse.success(created); } @@ -37,19 +38,20 @@ public class InspectionTemplateController { } @PutMapping("/{id}") - public ApiResponse updateTemplate(@PathVariable UUID id, - @Valid @RequestBody InspectionTemplate template) { + public ApiResponse updateTemplate( + @PathVariable UUID id, @Valid @RequestBody InspectionTemplate template) { return ApiResponse.success(inspectionTemplateService.updateTemplate(id, template)); } @PostMapping("/{id}/copy") - public ApiResponse copyTemplate(@PathVariable UUID id, - @RequestParam String newName) { + public ApiResponse copyTemplate( + @PathVariable UUID id, @RequestParam String newName) { return ApiResponse.success(inspectionTemplateService.copyTemplate(id, newName)); } @GetMapping("/by-type/{equipmentType}") - public ApiResponse> getTemplatesByType(@PathVariable String equipmentType) { + public ApiResponse> getTemplatesByType( + @PathVariable String equipmentType) { return ApiResponse.success(inspectionTemplateService.getTemplatesByType(equipmentType)); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java index b31399a..f36da0b 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java @@ -1,5 +1,6 @@ package com.ether.pms.mdm.controller; +import com.ether.pms.common.ApiResponse; import com.ether.pms.mdm.dto.AddMemberRequest; import com.ether.pms.mdm.dto.ChangeStatusRequest; import com.ether.pms.mdm.dto.PageResponse; @@ -14,18 +15,15 @@ import com.ether.pms.mdm.service.ProjectConfigService; import com.ether.pms.mdm.service.ProjectMemberService; import com.ether.pms.mdm.service.ProjectService; import com.ether.pms.mdm.service.ProjectStatisticsService; -import com.ether.pms.common.ApiResponse; -import com.ether.pms.common.util.PaginationValidator; import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - @RestController @RequestMapping("/api/mdm/projects") @RequiredArgsConstructor @@ -37,25 +35,20 @@ public class ProjectController { private final ProjectConfigService projectConfigService; private final ProjectStatisticsService projectStatisticsService; - /** - * PM-001: 分页查询项目列表 - */ + /** PM-001: 分页查询项目列表 */ @GetMapping - public ResponseEntity>> queryProjects(ProjectQueryRequest request) { + public ResponseEntity>> queryProjects( + ProjectQueryRequest request) { return ResponseEntity.ok(ApiResponse.success(projectService.queryProjects(request))); } - /** - * PM-010: 获取项目选择器列表 - */ + /** PM-010: 获取项目选择器列表 */ @GetMapping("/selector") public ResponseEntity>> getSelectorList() { return ResponseEntity.ok(ApiResponse.success(projectService.getSelectorList())); } - /** - * PM-005: 生成项目编码 - */ + /** PM-005: 生成项目编码 */ @GetMapping("/generate-code") public ResponseEntity> generateCode() { return ResponseEntity.ok(ApiResponse.success(projectService.generateCode())); @@ -77,7 +70,8 @@ public class ProjectController { } @PutMapping("/{id}") - public ResponseEntity> update(@PathVariable UUID id, @Valid @RequestBody Project project) { + public ResponseEntity> update( + @PathVariable UUID id, @Valid @RequestBody Project project) { return ResponseEntity.ok(ApiResponse.success(projectService.update(id, project))); } @@ -91,37 +85,29 @@ public class ProjectController { // PM-003: 成员管理 // ======================================== - /** - * PM-003: 获取项目成员列表 - */ + /** PM-003: 获取项目成员列表 */ @GetMapping("/{id}/members") public ResponseEntity>> getMembers( @PathVariable("id") UUID projectId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { - return ResponseEntity.ok(ApiResponse.success( - projectMemberService.getMembers(projectId, PageRequest.of(page, size)) - )); + return ResponseEntity.ok( + ApiResponse.success( + projectMemberService.getMembers(projectId, PageRequest.of(page, size)))); } - /** - * PM-003: 添加项目成员 - */ + /** PM-003: 添加项目成员 */ @PostMapping("/{id}/members") public ResponseEntity> addMembers( - @PathVariable("id") UUID projectId, - @Valid @RequestBody AddMemberRequest request) { + @PathVariable("id") UUID projectId, @Valid @RequestBody AddMemberRequest request) { projectMemberService.addMembers(projectId, request); return ResponseEntity.ok(ApiResponse.success()); } - /** - * PM-003: 移除项目成员 - */ + /** PM-003: 移除项目成员 */ @DeleteMapping("/{id}/members/{memberId}") public ResponseEntity> removeMember( - @PathVariable("id") UUID projectId, - @PathVariable UUID memberId) { + @PathVariable("id") UUID projectId, @PathVariable UUID memberId) { projectMemberService.removeMember(projectId, memberId); return ResponseEntity.ok(ApiResponse.success()); } @@ -130,25 +116,22 @@ public class ProjectController { // PM-002: 统计数据 // ======================================== - /** - * PM-002: 获取项目统计数据 - */ + /** PM-002: 获取项目统计数据 */ @GetMapping("/{id}/statistics") - public ResponseEntity> getStatistics(@PathVariable("id") UUID projectId) { - return ResponseEntity.ok(ApiResponse.success(projectStatisticsService.getStatistics(projectId))); + public ResponseEntity> getStatistics( + @PathVariable("id") UUID projectId) { + return ResponseEntity.ok( + ApiResponse.success(projectStatisticsService.getStatistics(projectId))); } // ======================================== // PM-006: 状态管理 // ======================================== - /** - * PM-006: 变更项目状态 - */ + /** PM-006: 变更项目状态 */ @PutMapping("/{id}/status") public ResponseEntity> changeStatus( - @PathVariable UUID id, - @Valid @RequestBody ChangeStatusRequest request) { + @PathVariable UUID id, @Valid @RequestBody ChangeStatusRequest request) { projectService.changeStatus(id, request.getStatus(), request.getReason()); return ResponseEntity.ok(ApiResponse.success()); } @@ -157,32 +140,26 @@ public class ProjectController { // PM-008: 配置管理 // ======================================== - /** - * PM-008: 获取项目配置 - */ + /** PM-008: 获取项目配置 */ @GetMapping("/{id}/config") - public ResponseEntity> getConfig(@PathVariable("id") UUID projectId) { + public ResponseEntity> getConfig( + @PathVariable("id") UUID projectId) { return ResponseEntity.ok(ApiResponse.success(projectConfigService.getConfig(projectId))); } - /** - * PM-008: 更新项目配置 - */ + /** PM-008: 更新项目配置 */ @PutMapping("/{id}/config") public ResponseEntity> updateConfig( - @PathVariable("id") UUID projectId, - @Valid @RequestBody ProjectConfigDTO dto) { - return ResponseEntity.ok(ApiResponse.success(projectConfigService.updateConfig(projectId, dto))); + @PathVariable("id") UUID projectId, @Valid @RequestBody ProjectConfigDTO dto) { + return ResponseEntity.ok( + ApiResponse.success(projectConfigService.updateConfig(projectId, dto))); } // ======================================== // PM-009: 删除前检查 // ======================================== - /** - * PM-009: 项目删除前检查 - * 检查项目的关联数据情况,如果存在应收未收费用则无法删除 - */ + /** PM-009: 项目删除前检查 检查项目的关联数据情况,如果存在应收未收费用则无法删除 */ @GetMapping("/{projectId}/delete-check") public ResponseEntity> checkProjectDelete( @PathVariable UUID projectId) { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java index e2aa8e4..4e86f58 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java @@ -3,22 +3,17 @@ package com.ether.pms.mdm.controller; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.BusinessException; import com.ether.pms.common.ErrorCode; +import com.ether.pms.common.util.BatchOperationValidator; +import com.ether.pms.mdm.dto.EquipmentCreateDTO; +import com.ether.pms.mdm.dto.FloorInfoVO; import com.ether.pms.mdm.dto.SpaceNodeCreateDTO; import com.ether.pms.mdm.dto.SpaceNodeDeleteCheckDTO; import com.ether.pms.mdm.dto.SpaceNodeEquipmentDTO; import com.ether.pms.mdm.dto.SpaceNodeTreeDTO; import com.ether.pms.mdm.dto.SpaceNodeUpdateDTO; -import com.ether.pms.mdm.dto.EquipmentCreateDTO; -import com.ether.pms.mdm.dto.FloorInfoVO; import com.ether.pms.mdm.entity.SpaceNode; import com.ether.pms.mdm.service.SpaceNodeService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -27,12 +22,13 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; - +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import com.ether.pms.common.util.PaginationValidator; -import com.ether.pms.common.util.BatchOperationValidator; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api/mdm/space-nodes") @@ -42,10 +38,10 @@ public class SpaceNodeController { private final SpaceNodeService spaceNodeService; - private static final Set ALLOWED_CONTENT_TYPES = Set.of( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel" - ); + private static final Set ALLOWED_CONTENT_TYPES = + Set.of( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel"); private static final Set ALLOWED_EXTENSIONS = Set.of(".xlsx", ".xls"); @@ -55,7 +51,8 @@ public class SpaceNodeController { public ResponseEntity>> findAll( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.findAll(PageRequest.of(page, size)))); + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.findAll(PageRequest.of(page, size)))); } @GetMapping("/{id}") @@ -64,25 +61,30 @@ public class SpaceNodeController { } @GetMapping("/project/{projectId}") - public ResponseEntity>> findByProject(@PathVariable UUID projectId) { + public ResponseEntity>> findByProject( + @PathVariable UUID projectId) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.findByProjectId(projectId))); } @GetMapping("/project/{projectId}/tree") - public ResponseEntity>> getTree(@PathVariable UUID projectId) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getTreeByProjectId(projectId))); + public ResponseEntity>> getTree( + @PathVariable UUID projectId) { + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.getTreeByProjectId(projectId))); } @GetMapping("/project/{projectId}/roots") public ResponseEntity>> getRoots(@PathVariable UUID projectId) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.findRootsByProjectId(projectId))); + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.findRootsByProjectId(projectId))); } @GetMapping("/project/{projectId}/type/{nodeType}") public ResponseEntity>> findByProjectAndType( - @PathVariable UUID projectId, - @PathVariable SpaceNode.NodeType nodeType) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.findByProjectIdAndNodeType(projectId, nodeType))); + @PathVariable UUID projectId, @PathVariable SpaceNode.NodeType nodeType) { + return ResponseEntity.ok( + ApiResponse.success( + spaceNodeService.findByProjectIdAndNodeType(projectId, nodeType))); } @GetMapping("/parent/{parentId}/children") @@ -91,18 +93,21 @@ public class SpaceNodeController { } @PostMapping - public ResponseEntity> create(@Valid @RequestBody SpaceNodeCreateDTO dto) { + public ResponseEntity> create( + @Valid @RequestBody SpaceNodeCreateDTO dto) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.create(dto))); } @PostMapping("/batch") - public ResponseEntity>> batchCreate(@Valid @RequestBody List dtoList) { + public ResponseEntity>> batchCreate( + @Valid @RequestBody List dtoList) { BatchOperationValidator.validateUpdateSize(dtoList.size()); return ResponseEntity.ok(ApiResponse.success(spaceNodeService.batchCreate(dtoList))); } @PutMapping("/{id}") - public ResponseEntity> update(@PathVariable UUID id, @Valid @RequestBody SpaceNodeUpdateDTO dto) { + public ResponseEntity> update( + @PathVariable UUID id, @Valid @RequestBody SpaceNodeUpdateDTO dto) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.update(id, dto))); } @@ -113,7 +118,8 @@ public class SpaceNodeController { } @GetMapping("/{id}/delete-check") - public ResponseEntity> checkDeleteInfo(@PathVariable UUID id) { + public ResponseEntity> checkDeleteInfo( + @PathVariable UUID id) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.checkDeleteInfo(id))); } @@ -125,6 +131,7 @@ public class SpaceNodeController { /** * 获取设备详情 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @@ -135,6 +142,7 @@ public class SpaceNodeController { /** * 获取设备列表 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @@ -146,56 +154,65 @@ public class SpaceNodeController { /** * 获取特种设备列表 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @GetMapping("/special-equipment") public ResponseEntity>> getSpecialEquipment( @RequestParam UUID projectId) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getSpecialEquipmentList(projectId))); + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.getSpecialEquipmentList(projectId))); } /** * 获取即将年检设备 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @GetMapping("/expiring-inspection") public ResponseEntity>> getExpiringInspection( - @RequestParam UUID projectId, - @RequestParam(defaultValue = "90") Integer daysAhead) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getExpiringInspectionEquipment(projectId, daysAhead))); + @RequestParam UUID projectId, @RequestParam(defaultValue = "90") Integer daysAhead) { + return ResponseEntity.ok( + ApiResponse.success( + spaceNodeService.getExpiringInspectionEquipment(projectId, daysAhead))); } /** * 创建设备 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @PostMapping("/equipment") - public ResponseEntity> createEquipment(@Valid @RequestBody EquipmentCreateDTO dto) { + public ResponseEntity> createEquipment( + @Valid @RequestBody EquipmentCreateDTO dto) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.createEquipment(dto))); } /** * 批量创建设备 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @PostMapping("/equipment/batch") - public ResponseEntity>> batchCreateEquipment(@Valid @RequestBody List dtoList) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.batchCreateEquipment(dtoList))); + public ResponseEntity>> batchCreateEquipment( + @Valid @RequestBody List dtoList) { + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.batchCreateEquipment(dtoList))); } /** * Excel导入设备 + * * @deprecated 请使用 /api/asset/equipment 替代 */ @Deprecated @PostMapping("/equipment/import") public ResponseEntity> importEquipment( - @RequestParam("file") MultipartFile file, - @RequestParam UUID projectId) { + @RequestParam("file") MultipartFile file, @RequestParam UUID projectId) { validateExcelFile(file); return ResponseEntity.ok(spaceNodeService.importEquipmentFromExcel(file, projectId)); } @@ -228,17 +245,16 @@ public class SpaceNodeController { // Excel 行数预检 try (InputStream is = file.getInputStream(); - org.apache.poi.ss.usermodel.Workbook workbook = - org.apache.poi.ss.usermodel.WorkbookFactory.create(is)) { + org.apache.poi.ss.usermodel.Workbook workbook = + org.apache.poi.ss.usermodel.WorkbookFactory.create(is)) { org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(0); if (sheet != null) { int rowCount = sheet.getLastRowNum() + 1; if (rowCount > MAX_EXCEL_ROWS) { throw new BusinessException( - ErrorCode.FILE_004, - String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount) - ); + ErrorCode.FILE_004, + String.format("Excel 行数不能超过 %d 行,当前 %d 行", MAX_EXCEL_ROWS, rowCount)); } } } catch (IOException e) { @@ -246,27 +262,26 @@ public class SpaceNodeController { } } - /** - * 获取楼栋楼层信息 - */ + /** 获取楼栋楼层信息 */ @GetMapping("/{buildingId}/floor-info") - public ResponseEntity> getBuildingFloorInfo(@PathVariable String buildingId) { - return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getBuildingFloorInfo(buildingId))); + public ResponseEntity> getBuildingFloorInfo( + @PathVariable String buildingId) { + return ResponseEntity.ok( + ApiResponse.success(spaceNodeService.getBuildingFloorInfo(buildingId))); } - /** - * 调试端点:检查房间的楼层号数据 - */ + /** 调试端点:检查房间的楼层号数据 */ @GetMapping("/debug/floor-numbers") - public ResponseEntity>> debugFloorNumbers(@RequestParam UUID projectId) { + public ResponseEntity>> debugFloorNumbers( + @RequestParam UUID projectId) { Map result = new HashMap<>(); List allNodes = spaceNodeService.findByProjectId(projectId); - + // 统计房间楼层信息 List> roomInfos = new ArrayList<>(); long withFloor = 0; long withoutFloor = 0; - + for (SpaceNode node : allNodes) { if (node.getNodeType() == SpaceNode.NodeType.ROOM) { Map info = new HashMap<>(); @@ -275,7 +290,7 @@ public class SpaceNodeController { info.put("floorNumber", node.getFloorNumber()); info.put("parentId", node.getParentId()); roomInfos.add(info); - + if (node.getFloorNumber() != null) { withFloor++; } else { @@ -283,12 +298,12 @@ public class SpaceNodeController { } } } - + result.put("totalRooms", roomInfos.size()); result.put("withFloorNumber", withFloor); result.put("withoutFloorNumber", withoutFloor); result.put("rooms", roomInfos.subList(0, Math.min(20, roomInfos.size()))); - + return ResponseEntity.ok(ApiResponse.success(result)); } } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java index 63f7775..136851f 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SparePartController.java @@ -7,14 +7,13 @@ import com.ether.pms.mdm.entity.SparePartRecord; import com.ether.pms.mdm.service.SparePartService; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.UUID; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.UUID; - @RestController @RequestMapping("/api/ops/spare-parts") @RequiredArgsConstructor @@ -31,7 +30,8 @@ public class SparePartController { } @PostMapping("/categories") - public ApiResponse createCategory(@Valid @RequestBody SparePartCategory category) { + public ApiResponse createCategory( + @Valid @RequestBody SparePartCategory category) { return ApiResponse.success(sparePartService.createCategory(category)); } @@ -39,8 +39,7 @@ public class SparePartController { @GetMapping public ApiResponse> getSpareParts( - @RequestParam UUID projectId, - @RequestParam(required = false) UUID categoryId) { + @RequestParam UUID projectId, @RequestParam(required = false) UUID categoryId) { List spareParts; if (categoryId != null) { spareParts = sparePartService.getSparePartsByCategory(categoryId); @@ -61,7 +60,8 @@ public class SparePartController { } @PutMapping("/{id}") - public ApiResponse updateSparePart(@PathVariable UUID id, @Valid @RequestBody SparePart sparePart) { + public ApiResponse updateSparePart( + @PathVariable UUID id, @Valid @RequestBody SparePart sparePart) { return ApiResponse.success(sparePartService.updateSparePart(id, sparePart)); } @@ -80,16 +80,24 @@ public class SparePartController { @PostMapping("/in-stock") public ApiResponse inStock(@Valid @RequestBody StockRequest request) { - SparePartRecord record = sparePartService.inStock( - request.getSparePartId(), request.getQuantity(), request.getRecordedBy(), request.getRemarks()); + SparePartRecord record = + sparePartService.inStock( + request.getSparePartId(), + request.getQuantity(), + request.getRecordedBy(), + request.getRemarks()); return ApiResponse.success(record); } @PostMapping("/out-stock") public ApiResponse outStock(@Valid @RequestBody OutStockRequest request) { - SparePartRecord record = sparePartService.outStock( - request.getSparePartId(), request.getQuantity(), - request.getRelatedOrderId(), request.getRecordedBy(), request.getRemarks()); + SparePartRecord record = + sparePartService.outStock( + request.getSparePartId(), + request.getQuantity(), + request.getRelatedOrderId(), + request.getRecordedBy(), + request.getRemarks()); return ApiResponse.success(record); } @@ -122,4 +130,4 @@ public class SparePartController { private UUID recordedBy; private String remarks; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/AddMemberRequest.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/AddMemberRequest.java index 58dd987..dcd08c5 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/AddMemberRequest.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/AddMemberRequest.java @@ -2,30 +2,23 @@ package com.ether.pms.mdm.dto; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; -import java.util.UUID; - -/** - * 添加项目成员请求 - */ +/** 添加项目成员请求 */ @Data @NoArgsConstructor @AllArgsConstructor public class AddMemberRequest { - /** - * 用户ID列表 - */ + /** 用户ID列表 */ @NotEmpty(message = "用户ID列表不能为空") private List userIds; - /** - * 项目中的角色 - */ + /** 项目中的角色 */ @NotNull(message = "项目角色不能为空") private String roleInProject; } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ChangeStatusRequest.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ChangeStatusRequest.java index 73669c6..22e0406 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ChangeStatusRequest.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ChangeStatusRequest.java @@ -1,27 +1,20 @@ package com.ether.pms.mdm.dto; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -/** - * 项目状态变更请求 - */ +/** 项目状态变更请求 */ @Data @NoArgsConstructor @AllArgsConstructor public class ChangeStatusRequest { - /** - * 新状态 - */ + /** 新状态 */ @NotBlank(message = "状态不能为空") private String status; - /** - * 变更原因 - */ + /** 变更原因 */ private String reason; } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/EquipmentCreateDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/EquipmentCreateDTO.java index 994950b..d2ebdbe 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/EquipmentCreateDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/EquipmentCreateDTO.java @@ -2,10 +2,10 @@ package com.ether.pms.mdm.dto; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.util.UUID; +import lombok.Data; @Data public class EquipmentCreateDTO { @@ -57,4 +57,4 @@ public class EquipmentCreateDTO { private String installationEnvironment; private String protectionLevel; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorDetailVO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorDetailVO.java index 9193bec..e4977c8 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorDetailVO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorDetailVO.java @@ -2,33 +2,21 @@ package com.ether.pms.mdm.dto; import lombok.Data; -/** - * 楼层详情VO - */ +/** 楼层详情VO */ @Data public class FloorDetailVO { - /** - * 楼层号(1=1楼,-1=地下一层) - */ + /** 楼层号(1=1楼,-1=地下一层) */ private Integer floorNumber; - /** - * 是否有房间 - */ + /** 是否有房间 */ private Boolean hasRooms; - /** - * 是否有商铺 - */ + /** 是否有商铺 */ private Boolean hasShop; - /** - * 房间数量 - */ + /** 房间数量 */ private Integer roomCount; - /** - * 备注(如"大堂") - */ + /** 备注(如"大堂") */ private String remark; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorInfoVO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorInfoVO.java index 7fd05b2..f5a2af7 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorInfoVO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/FloorInfoVO.java @@ -1,35 +1,23 @@ package com.ether.pms.mdm.dto; -import lombok.Data; import java.util.List; +import lombok.Data; -/** - * 楼栋楼层信息VO - */ +/** 楼栋楼层信息VO */ @Data public class FloorInfoVO { - /** - * 楼栋ID - */ + /** 楼栋ID */ private String buildingId; - /** - * 楼栋名称 - */ + /** 楼栋名称 */ private String buildingName; - /** - * 总楼层数(地上) - */ + /** 总楼层数(地上) */ private Integer totalFloors; - /** - * 地下楼层数 - */ + /** 地下楼层数 */ private Integer undergroundFloors; - /** - * 楼层详情列表 - */ + /** 楼层详情列表 */ private List floors; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/PageResponse.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/PageResponse.java index 95c7b06..459e5d4 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/PageResponse.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/PageResponse.java @@ -1,14 +1,11 @@ package com.ether.pms.mdm.dto; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; - -/** - * 分页响应DTO - */ +/** 分页响应DTO */ @Data @NoArgsConstructor @AllArgsConstructor @@ -31,13 +28,6 @@ public class PageResponse { public static PageResponse of(List content, int page, int size, long totalElements) { int totalPages = (int) Math.ceil((double) totalElements / size); return new PageResponse<>( - content, - page, - size, - totalElements, - totalPages, - page == 0, - page >= totalPages - 1 - ); + content, page, size, totalElements, totalPages, page == 0, page >= totalPages - 1); } } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectConfigDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectConfigDTO.java index 72396c5..545b5bb 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectConfigDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectConfigDTO.java @@ -1,14 +1,11 @@ package com.ether.pms.mdm.dto; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.UUID; - -/** - * 项目配置DTO - */ +/** 项目配置DTO */ @Data @NoArgsConstructor @AllArgsConstructor @@ -18,53 +15,33 @@ public class ProjectConfigDTO { private UUID projectId; - /** - * 启用预约功能 - */ + /** 启用预约功能 */ private Boolean enableReservation; - /** - * 启用访客功能 - */ + /** 启用访客功能 */ private Boolean enableVisitor; - /** - * 启用投诉功能 - */ + /** 启用投诉功能 */ private Boolean enableComplaint; - /** - * 启用缴费功能 - */ + /** 启用缴费功能 */ private Boolean enablePayment; - /** - * 启用公告功能 - */ + /** 启用公告功能 */ private Boolean enableAnnouncement; - /** - * 启用问卷功能 - */ + /** 启用问卷功能 */ private Boolean enableSurvey; - /** - * 启用投票功能 - */ + /** 启用投票功能 */ private Boolean enableVote; - /** - * 启用报修功能 - */ + /** 启用报修功能 */ private Boolean enableMaintenance; - /** - * 启用资产功能 - */ + /** 启用资产功能 */ private Boolean enableAsset; - /** - * 自定义配置(JSON格式) - */ + /** 自定义配置(JSON格式) */ private String customConfig; } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteCheckVO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteCheckVO.java index 92146d6..40cb5d5 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteCheckVO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteCheckVO.java @@ -2,25 +2,15 @@ package com.ether.pms.mdm.dto; import lombok.Data; -import java.math.BigDecimal; - -/** - * 项目删除检查返回对象 - */ +/** 项目删除检查返回对象 */ @Data public class ProjectDeleteCheckVO { - /** - * 是否可以删除 - */ + /** 是否可以删除 */ private Boolean canDelete; - /** - * 无法删除的原因 - */ + /** 无法删除的原因 */ private String reason; - /** - * 项目关联数据统计 - */ + /** 项目关联数据统计 */ private ProjectDeleteStatistics statistics; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteStatistics.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteStatistics.java index 92ccfeb..f6dccb1 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteStatistics.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectDeleteStatistics.java @@ -1,41 +1,26 @@ package com.ether.pms.mdm.dto; +import java.math.BigDecimal; import lombok.Data; -import java.math.BigDecimal; - -/** - * 项目删除检查统计数据 - */ +/** 项目删除检查统计数据 */ @Data public class ProjectDeleteStatistics { - /** - * 空间数量 - */ + /** 空间数量 */ private Integer spaceCount; - /** - * 成员数量 - */ + /** 成员数量 */ private Integer memberCount; - /** - * 设施设备数量 - */ + /** 设施设备数量 */ private Integer equipmentCount; - /** - * 账单数量 - */ + /** 账单数量 */ private Integer billCount; - /** - * 应收总额 - */ + /** 应收总额 */ private BigDecimal totalReceivable; - /** - * 未收金额 - */ + /** 未收金额 */ private BigDecimal unpaidAmount; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectMemberDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectMemberDTO.java index bcb61d5..d63b514 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectMemberDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectMemberDTO.java @@ -1,57 +1,38 @@ package com.ether.pms.mdm.dto; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; -import java.util.UUID; - -/** - * 项目成员DTO - */ +/** 项目成员DTO */ @Data @NoArgsConstructor @AllArgsConstructor public class ProjectMemberDTO { - /** - * 成员关联ID - */ + /** 成员关联ID */ private UUID id; - /** - * 用户ID - */ + /** 用户ID */ private UUID userId; - /** - * 用户名 - */ + /** 用户名 */ private String username; - /** - * 真实姓名 - */ + /** 真实姓名 */ private String realName; - /** - * 手机号 - */ + /** 手机号 */ private String phone; - /** - * 头像 - */ + /** 头像 */ private String avatar; - /** - * 项目中的角色 - */ + /** 项目中的角色 */ private String roleInProject; - /** - * 加入时间 - */ + /** 加入时间 */ private LocalDateTime joinedAt; } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectQueryRequest.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectQueryRequest.java index 06c2a43..56d1173 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectQueryRequest.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectQueryRequest.java @@ -2,9 +2,7 @@ package com.ether.pms.mdm.dto; import lombok.Data; -/** - * 项目查询请求DTO - */ +/** 项目查询请求DTO */ @Data public class ProjectQueryRequest { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectSelectorItem.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectSelectorItem.java index c98d252..d5e0c30 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectSelectorItem.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/ProjectSelectorItem.java @@ -2,9 +2,7 @@ package com.ether.pms.mdm.dto; import lombok.Data; -/** - * 项目选择器项DTO - */ +/** 项目选择器项DTO */ @Data public class ProjectSelectorItem { @@ -16,8 +14,7 @@ public class ProjectSelectorItem { private String status; - public ProjectSelectorItem() { - } + public ProjectSelectorItem() {} public ProjectSelectorItem(String id, String code, String name, String status) { this.id = id; diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeCreateDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeCreateDTO.java index 15b2821..6300f6e 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeCreateDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeCreateDTO.java @@ -3,9 +3,9 @@ package com.ether.pms.mdm.dto; import com.ether.pms.mdm.entity.SpaceNode; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Data; import java.math.BigDecimal; import java.util.UUID; +import lombok.Data; @Data public class SpaceNodeCreateDTO { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDTO.java index 38557ce..a21eb41 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDTO.java @@ -1,9 +1,9 @@ package com.ether.pms.mdm.dto; import com.ether.pms.mdm.entity.SpaceNode; -import lombok.Data; import java.math.BigDecimal; import java.util.UUID; +import lombok.Data; @Data public class SpaceNodeDTO { @@ -38,4 +38,4 @@ public class SpaceNodeDTO { private String street; private String address; private String attributes; -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDeleteCheckDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDeleteCheckDTO.java index ad7637f..85fcb3c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDeleteCheckDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeDeleteCheckDTO.java @@ -12,7 +12,12 @@ public class SpaceNodeDeleteCheckDTO { public SpaceNodeDeleteCheckDTO() {} - public SpaceNodeDeleteCheckDTO(UUID nodeId, String nodeName, int childCount, Map childTypeCount, int totalDescendantCount) { + public SpaceNodeDeleteCheckDTO( + UUID nodeId, + String nodeName, + int childCount, + Map childTypeCount, + int totalDescendantCount) { this.nodeId = nodeId; this.nodeName = nodeName; this.childCount = childCount; @@ -20,14 +25,43 @@ public class SpaceNodeDeleteCheckDTO { this.totalDescendantCount = totalDescendantCount; } - public UUID getNodeId() { return nodeId; } - public void setNodeId(UUID nodeId) { this.nodeId = nodeId; } - public String getNodeName() { return nodeName; } - public void setNodeName(String nodeName) { this.nodeName = nodeName; } - public int getChildCount() { return childCount; } - public void setChildCount(int childCount) { this.childCount = childCount; } - public Map getChildTypeCount() { return childTypeCount; } - public void setChildTypeCount(Map childTypeCount) { this.childTypeCount = childTypeCount; } - public int getTotalDescendantCount() { return totalDescendantCount; } - public void setTotalDescendantCount(int totalDescendantCount) { this.totalDescendantCount = totalDescendantCount; } + public UUID getNodeId() { + return nodeId; + } + + public void setNodeId(UUID nodeId) { + this.nodeId = nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public int getChildCount() { + return childCount; + } + + public void setChildCount(int childCount) { + this.childCount = childCount; + } + + public Map getChildTypeCount() { + return childTypeCount; + } + + public void setChildTypeCount(Map childTypeCount) { + this.childTypeCount = childTypeCount; + } + + public int getTotalDescendantCount() { + return totalDescendantCount; + } + + public void setTotalDescendantCount(int totalDescendantCount) { + this.totalDescendantCount = totalDescendantCount; + } } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java index 2f53abc..00d2e93 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeEquipmentDTO.java @@ -1,11 +1,15 @@ package com.ether.pms.mdm.dto; -import lombok.Data; -import lombok.EqualsAndHashCode; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +/** + * @deprecated 此DTO属于设备管理,将在后续版本迁移到 module-asset 请使用 module-asset 模块中的 EquipmentDTO + */ +@Deprecated @Data @EqualsAndHashCode(callSuper = true) public class SpaceNodeEquipmentDTO extends SpaceNodeDTO { @@ -38,4 +42,4 @@ public class SpaceNodeEquipmentDTO extends SpaceNodeDTO { private String model; private Integer quantity; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeProjection.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeProjection.java index 8606426..8b5ef9c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeProjection.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeProjection.java @@ -7,61 +7,118 @@ import java.util.UUID; public interface SpaceNodeProjection { UUID getId(); + String getProjectId(); + String getName(); + String getFullName(); + String getShortName(); + String getNodeCategory(); + String getNodeType(); + String getUsageType(); + UUID getParentId(); + String getTreePath(); + String getTreePathName(); + Integer getLevel(); + Integer getSortOrder(); + String getStatus(); + String getDeliveryStatus(); + String getDecorationStatus(); + BigDecimal getBuildingArea(); + BigDecimal getUsableArea(); + BigDecimal getSharedArea(); + BigDecimal getLandArea(); + BigDecimal getLongitude(); + BigDecimal getLatitude(); + BigDecimal getAltitude(); + Integer getFloorNumber(); + String getProvince(); + String getCity(); + String getDistrict(); + String getStreet(); + String getAddress(); + Boolean getIsEquipment(); + String getSpecialEquipmentType(); + String getSpecialEquipmentCert(); + LocalDate getLastInspectionDate(); + String getLastInspectionResult(); + LocalDate getNextInspectionDate(); + Integer getInspectionCycle(); + String getProtectionLevel(); + String getMaintenanceVendor(); + String getMaintenanceVendorContact(); + String getMaintenanceVendorPhone(); + LocalDate getMaintenanceContractStart(); + LocalDate getMaintenanceContractEnd(); + String getMaintenanceContractNo(); + String getRatedVoltage(); + String getRatedCurrent(); + String getRatedPower(); + String getEnergyConsumptionStandard(); + String getInstallationEnvironment(); + String getDesignLifeYears(); + String getCommonSpareParts(); + String getAttributes(); + String getNodeCategoryName(); + String getNodeTypeName(); + LocalDateTime getCreatedAt(); + LocalDateTime getUpdatedAt(); + String getCreatedBy(); + String getUpdatedBy(); + Boolean getIsDeleted(); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeTreeDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeTreeDTO.java index 3b690a7..85ae95f 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeTreeDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeTreeDTO.java @@ -1,11 +1,11 @@ package com.ether.pms.mdm.dto; import com.ether.pms.mdm.entity.SpaceNode; -import lombok.Data; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import lombok.Data; @Data public class SpaceNodeTreeDTO { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeUpdateDTO.java b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeUpdateDTO.java index 9a8306b..6726f73 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeUpdateDTO.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/dto/SpaceNodeUpdateDTO.java @@ -1,8 +1,8 @@ package com.ether.pms.mdm.dto; import com.ether.pms.mdm.entity.SpaceNode; -import lombok.Data; import java.math.BigDecimal; +import lombok.Data; @Data public class SpaceNodeUpdateDTO { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java index e5320f5..5a79abf 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyConsumption.java @@ -1,17 +1,19 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity -@Table(name = "ops_energy_consumption", indexes = { - @Index(name = "idx_ec_meter_date", columnList = "meter_id, consumption_date"), - @Index(name = "idx_ec_project_date", columnList = "project_id, consumption_date") -}) +@Table( + name = "ops_energy_consumption", + indexes = { + @Index(name = "idx_ec_meter_date", columnList = "meter_id, consumption_date"), + @Index(name = "idx_ec_project_date", columnList = "project_id, consumption_date") + }) @Data public class EnergyConsumption { @@ -48,8 +50,8 @@ public class EnergyConsumption { private RecordMethod recordMethod = RecordMethod.MANUAL; public enum RecordMethod { - MANUAL, // 手动录入 - IOT // IoT自动采集 + MANUAL, // 手动录入 + IOT // IoT自动采集 } @Column(length = 1000) @@ -62,4 +64,4 @@ public class EnergyConsumption { public void prePersist() { createdAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java index 86137da..a83c8ea 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/EnergyMeter.java @@ -1,10 +1,10 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_energy_meter") @@ -37,8 +37,14 @@ public class EnergyMeter { GAS("燃气"); private final String desc; - EnergyType(String desc) { this.desc = desc; } - public String getDesc() { return desc; } + + EnergyType(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } } @Column(name = "space_node_id") @@ -58,7 +64,8 @@ public class EnergyMeter { private Status status = Status.ACTIVE; public enum Status { - ACTIVE, INACTIVE + ACTIVE, + INACTIVE } @Column(name = "created_at") @@ -77,4 +84,4 @@ public class EnergyMeter { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionItem.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionItem.java index f534e79..c0d83a6 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionItem.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionItem.java @@ -1,14 +1,11 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; - import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; -/** - * 巡检标准项实体 - */ +/** 巡检标准项实体 */ @Entity @Table(name = "mdm_inspection_item") @Data @@ -47,7 +44,8 @@ public class InspectionItem { private Status status = Status.ACTIVE; public enum Status { - ACTIVE, INACTIVE + ACTIVE, + INACTIVE } @Column(name = "created_at") @@ -66,4 +64,4 @@ public class InspectionItem { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionRecord.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionRecord.java index f9cfba3..bdaa01e 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionRecord.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionRecord.java @@ -1,24 +1,23 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.Data; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; -/** - * 巡检记录实体 - */ +/** 巡检记录实体 */ @Entity -@Table(name = "mdm_inspection_record", indexes = { - @Index(name = "idx_ir_equipment_date", columnList = "equipment_id, inspection_date"), - @Index(name = "idx_ir_inspectiondate", columnList = "inspection_date") -}) +@Table( + name = "mdm_inspection_record", + indexes = { + @Index(name = "idx_ir_equipment_date", columnList = "equipment_id, inspection_date"), + @Index(name = "idx_ir_inspectiondate", columnList = "inspection_date") + }) @Data public class InspectionRecord { @@ -43,7 +42,9 @@ public class InspectionRecord { private CheckStatus status = CheckStatus.NORMAL; public enum CheckStatus { - NORMAL, WARNING, ABNORMAL + NORMAL, + WARNING, + ABNORMAL } @Column(name = "check_in_time") @@ -63,8 +64,7 @@ public class InspectionRecord { @Column(name = "problems", columnDefinition = "jsonb") private List> problems; - @Column - private Boolean completed = false; + @Column private Boolean completed = false; @Column(name = "completed_time") private LocalDateTime completedTime; @@ -82,4 +82,4 @@ public class InspectionRecord { completed = false; } } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java index cb5b1be..6c8b532 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/InspectionTemplate.java @@ -1,9 +1,9 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_inspection_template") @@ -37,7 +37,8 @@ public class InspectionTemplate { private Status status = Status.ACTIVE; public enum Status { - ACTIVE, INACTIVE + ACTIVE, + INACTIVE } @Column(nullable = false) @@ -62,4 +63,4 @@ public class InspectionTemplate { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java index 47063ff..c418d99 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java @@ -4,38 +4,38 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "mdm_project") @Data public class Project { - + @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - + @NotNull(message = "项目代码不能为空") @Size(min = 2, max = 50, message = "项目代码长度必须在2-50位之间") @Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "项目代码只能包含字母、数字、连字符和下划线") @Column(unique = true, nullable = false, length = 50) private String code; - + @NotNull(message = "项目名称不能为空") @Size(min = 2, max = 100, message = "项目名称长度必须在2-100位之间") @Column(nullable = false, length = 100) private String name; - + @Size(max = 500, message = "项目描述长度不能超过500位") @Column(length = 500) private String description; - + @Size(max = 100, message = "项目地址长度不能超过100位") @Column(length = 100) private String address; - + @Column(name = "project_type", length = 20) @Enumerated(EnumType.STRING) private ProjectType projectType = ProjectType.RESIDENTIAL; @@ -55,47 +55,47 @@ public class Project { return desc; } } - + @Column(length = 50) private String province; - + @Column(length = 50) private String city; - + @Column(length = 50) private String district; - + private Double longitude; - + private Double latitude; - + @Column(length = 20, nullable = false) private String status; - + private Integer buildingCount; - + private Integer unitCount; - + private Integer roomCount; - + private Integer floorCount; - + @Column(length = 200) private String logo; - + @Column(length = 200) private String contact; - + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系电话格式不正确") @Column(length = 20) private String contactPhone; - + @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; @Column(name = "updated_at", nullable = false) private LocalDateTime updatedAt; - + @PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); @@ -104,7 +104,7 @@ public class Project { this.status = "ACTIVE"; } } - + @PreUpdate public void preUpdate() { this.updatedAt = LocalDateTime.now(); diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectConfig.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectConfig.java index 0ba503f..8d4e5c0 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectConfig.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectConfig.java @@ -1,13 +1,11 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; -/** - * 项目配置实体 - */ +/** 项目配置实体 */ @Entity @Table(name = "mdm_project_config") @Data diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatistics.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatistics.java index b7671f3..face320 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatistics.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatistics.java @@ -1,13 +1,11 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; -/** - * 项目统计信息实体 - */ +/** 项目统计信息实体 */ @Entity @Table(name = "mdm_project_statistics") @Data diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatusHistory.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatusHistory.java index f62392a..53d3218 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatusHistory.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/ProjectStatusHistory.java @@ -1,13 +1,11 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; -/** - * 项目状态变更历史实体 - */ +/** 项目状态变更历史实体 */ @Entity @Table(name = "mdm_project_status_history") @Data diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java index 3512ff8..63467be 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java @@ -3,25 +3,34 @@ package com.ether.pms.mdm.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; +/** + * SpaceNode 实体类 - 空间节点 + * + *

TODO: 设备相关字段迁移计划(2026-Q3) 1. 将设备管理相关方法迁移到 module-asset 2. 创建独立的 Equipment 实体替代 SpaceNode + * 中的设备字段 3. 将 SpaceNodeEquipmentDTO 迁移到 module-asset 4. 更新相关 Controller 使用 module-asset 的服务 + */ @Entity -@Table(name = "mdm_space_node", indexes = { - @Index(name = "idx_space_node_project", columnList = "project_id"), - @Index(name = "idx_space_node_parent", columnList = "parent_id"), - @Index(name = "idx_space_node_type", columnList = "node_type"), - @Index(name = "idx_space_node_tree_path", columnList = "tree_path"), - @Index(name = "idx_sn_project_parent", columnList = "project_id, parent_id"), - @Index(name = "idx_sn_project_type", columnList = "project_id, node_type"), - @Index(name = "idx_sn_project_isequipment", columnList = "project_id, is_equipment"), - @Index(name = "idx_sn_project_nextinspection", columnList = "project_id, next_inspection_date") -}) +@Table( + name = "mdm_space_node", + indexes = { + @Index(name = "idx_space_node_project", columnList = "project_id"), + @Index(name = "idx_space_node_parent", columnList = "parent_id"), + @Index(name = "idx_space_node_type", columnList = "node_type"), + @Index(name = "idx_space_node_tree_path", columnList = "tree_path"), + @Index(name = "idx_sn_project_parent", columnList = "project_id, parent_id"), + @Index(name = "idx_sn_project_type", columnList = "project_id, node_type"), + @Index(name = "idx_sn_project_isequipment", columnList = "project_id, is_equipment"), + @Index( + name = "idx_sn_project_nextinspection", + columnList = "project_id, next_inspection_date") + }) @Data public class SpaceNode { @@ -142,90 +151,195 @@ public class SpaceNode { @Column(name = "is_deleted") private Boolean isDeleted = false; - // ========== 设备扩展字段(@Deprecated:请使用 module-asset 的 Equipment 实体) ========== + /** + * 设备扩展字段 - TODO: 迁移到 module-asset(2026-Q3) + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "is_equipment") private Boolean isEquipment = false; + /** + * 设计使用寿命(年) + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "design_life_years") private Integer designLifeYears; + /** + * 额定功率 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "rated_power", precision = 10, scale = 2) private BigDecimal ratedPower; + /** + * 额定电压 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "rated_voltage", precision = 10, scale = 2) private BigDecimal ratedVoltage; + /** + * 额定电流 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "rated_current", precision = 10, scale = 2) private BigDecimal ratedCurrent; + /** + * 维保单位 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_vendor", length = 100) private String maintenanceVendor; + /** + * 维保单位联系人 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_vendor_contact", length = 50) private String maintenanceVendorContact; + /** + * 维保单位联系电话 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_vendor_phone", length = 20) private String maintenanceVendorPhone; + /** + * 维保合同编号 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_contract_no", length = 50) private String maintenanceContractNo; + /** + * 维保合同开始日期 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_contract_start") private LocalDate maintenanceContractStart; + /** + * 维保合同结束日期 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "maintenance_contract_end") private LocalDate maintenanceContractEnd; + /** + * 特种设备类型 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "special_equipment_type", length = 50) private String specialEquipmentType; + /** + * 特种设备证书编号 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "special_equipment_cert", length = 100) private String specialEquipmentCert; + /** + * 检验周期(天) + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "inspection_cycle") private Integer inspectionCycle; + /** + * 下次检验日期 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "next_inspection_date") private LocalDate nextInspectionDate; + /** + * 上次检验日期 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "last_inspection_date") private LocalDate lastInspectionDate; + /** + * 上次检验结果 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "last_inspection_result", length = 20) private String lastInspectionResult; + /** + * 常用备件列表(JSON格式) + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "common_spare_parts", length = 2000) private String commonSpareParts; + /** + * 能耗标准 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "energy_consumption_standard", precision = 12, scale = 2) private BigDecimal energyConsumptionStandard; + /** + * 安装环境 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "installation_environment", length = 50) private String installationEnvironment; + /** + * 防护等级 + * + * @deprecated 请使用 module-asset 模块中的 Equipment 实体 + */ @Deprecated @Column(name = "protection_level", length = 20) private String protectionLevel; + // ========== 设备扩展字段结束 ========== @PrePersist diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java index 0635fd0..684116a 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePart.java @@ -1,15 +1,15 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity -@Table(name = "ops_spare_part", indexes = { - @Index(name = "idx_sp_project_status", columnList = "project_id, status") -}) +@Table( + name = "ops_spare_part", + indexes = {@Index(name = "idx_sp_project_status", columnList = "project_id, status")}) @Data public class SparePart { @@ -61,7 +61,8 @@ public class SparePart { private Status status = Status.ACTIVE; public enum Status { - ACTIVE, INACTIVE + ACTIVE, + INACTIVE } @Column(name = "created_at") @@ -82,4 +83,4 @@ public class SparePart { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartCategory.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartCategory.java index baed70b..0aa285a 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartCategory.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartCategory.java @@ -1,9 +1,9 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_spare_part_category") @@ -36,4 +36,4 @@ public class SparePartCategory { public void prePersist() { createdAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java index 0c09c84..6016bb9 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SparePartRecord.java @@ -1,9 +1,9 @@ package com.ether.pms.mdm.entity; import jakarta.persistence.*; -import lombok.Data; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_spare_part_record") @@ -28,8 +28,14 @@ public class SparePartRecord { ADJUST("调整"); private final String desc; - RecordType(String desc) { this.desc = desc; } - public String getDesc() { return desc; } + + RecordType(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } } @Column(name = "spare_part_id", nullable = false) @@ -59,4 +65,4 @@ public class SparePartRecord { recordDate = LocalDateTime.now(); } } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/enums/ProjectStatus.java b/module-mdm/src/main/java/com/ether/pms/mdm/enums/ProjectStatus.java index 8aa3357..61a239f 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/enums/ProjectStatus.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/enums/ProjectStatus.java @@ -2,12 +2,9 @@ package com.ether.pms.mdm.enums; import lombok.Getter; -/** - * 项目状态枚举 - */ +/** 项目状态枚举 */ @Getter public enum ProjectStatus { - DRAFT("DRAFT", "草稿"), ACTIVE("ACTIVE", "启用"), INACTIVE("INACTIVE", "停用"), @@ -22,18 +19,14 @@ public enum ProjectStatus { } /** - * 判断是否可以转换到目标状态 - * 状态转换规则: - * - DRAFT -> ACTIVE - * - ACTIVE -> INACTIVE, ARCHIVED - * - INACTIVE -> ACTIVE, ARCHIVED - * - ARCHIVED -> ACTIVE (需要特殊权限) + * 判断是否可以转换到目标状态 状态转换规则: - DRAFT -> ACTIVE - ACTIVE -> INACTIVE, ARCHIVED - INACTIVE -> ACTIVE, + * ARCHIVED - ARCHIVED -> ACTIVE (需要特殊权限) */ public boolean canTransitionTo(ProjectStatus target) { if (target == null) { return false; } - + return switch (this) { case DRAFT -> target == ACTIVE; case ACTIVE -> target == INACTIVE || target == ARCHIVED; @@ -42,9 +35,7 @@ public enum ProjectStatus { }; } - /** - * 根据code获取枚举 - */ + /** 根据code获取枚举 */ public static ProjectStatus fromCode(String code) { if (code == null) { return null; diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/enums/SystemType.java b/module-mdm/src/main/java/com/ether/pms/mdm/enums/SystemType.java index fa51d91..8ad2360 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/enums/SystemType.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/enums/SystemType.java @@ -1,8 +1,6 @@ package com.ether.pms.mdm.enums; -/** - * 商业地产8大系统分类枚举 - */ +/** 商业地产8大系统分类枚举 */ public enum SystemType { HVAC("暖通空调"), FIRE("消防系统"), @@ -13,14 +11,14 @@ public enum SystemType { KITCHEN("餐饮厨房"), LANDSCAPE("景观"), OTHER("其他"); - + private final String description; - + SystemType(String description) { this.description = description; } - + public String getDescription() { return description; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyConsumptionRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyConsumptionRepository.java index 208fc68..113014f 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyConsumptionRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyConsumptionRepository.java @@ -1,28 +1,36 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.EnergyConsumption; -import com.ether.pms.mdm.entity.EnergyMeter; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; @Repository public interface EnergyConsumptionRepository extends JpaRepository { List findByMeterIdOrderByConsumptionDateDesc(UUID meterId); - List findByMeterIdAndConsumptionDateBetween(UUID meterId, LocalDate startDate, LocalDate endDate); + List findByMeterIdAndConsumptionDateBetween( + UUID meterId, LocalDate startDate, LocalDate endDate); - @Query("SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.meterId = :meterId AND e.consumptionDate BETWEEN :startDate AND :endDate") - BigDecimal sumConsumptionByMeterIdAndDateRange(@Param("meterId") UUID meterId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); + @Query( + "SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.meterId = :meterId AND e.consumptionDate BETWEEN :startDate AND :endDate") + BigDecimal sumConsumptionByMeterIdAndDateRange( + @Param("meterId") UUID meterId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); - @Query("SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.projectId = :projectId AND e.consumptionDate BETWEEN :startDate AND :endDate") - BigDecimal sumConsumptionByProjectAndDateRange(@Param("projectId") UUID projectId, @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); + @Query( + "SELECT SUM(e.consumption) FROM EnergyConsumption e WHERE e.projectId = :projectId AND e.consumptionDate BETWEEN :startDate AND :endDate") + BigDecimal sumConsumptionByProjectAndDateRange( + @Param("projectId") UUID projectId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); Optional findTopByMeterIdOrderByConsumptionDateDesc(UUID meterId); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyMeterRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyMeterRepository.java index b78f304..c82cf82 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyMeterRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/EnergyMeterRepository.java @@ -1,16 +1,20 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.EnergyMeter; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface EnergyMeterRepository extends JpaRepository { List findByProjectIdAndStatus(UUID projectId, EnergyMeter.Status status); - List findByProjectIdAndEnergyType(UUID projectId, EnergyMeter.EnergyType energyType); + + List findByProjectIdAndEnergyType( + UUID projectId, EnergyMeter.EnergyType energyType); + Optional findByMeterCode(String meterCode); + boolean existsByMeterCode(String meterCode); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionItemRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionItemRepository.java index cbee07e..0599e75 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionItemRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionItemRepository.java @@ -1,17 +1,14 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.InspectionItem; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.UUID; - -/** - * 巡检标准项Repository - */ +/** 巡检标准项Repository */ @Repository public interface InspectionItemRepository extends JpaRepository { @@ -21,13 +18,16 @@ public interface InspectionItemRepository extends JpaRepository findByStatus(InspectionItem.Status status); - List findByEquipmentTypeAndStatus(String equipmentType, InspectionItem.Status status); + List findByEquipmentTypeAndStatus( + String equipmentType, InspectionItem.Status status); List findBySystemTypeAndStatus(String systemType, InspectionItem.Status status); - @Query("SELECT i FROM InspectionItem i WHERE i.equipmentType = :equipmentType AND i.systemType = :systemType AND i.status = 'ACTIVE' ORDER BY i.sortOrder") - List findByEquipmentTypeAndSystemType(@Param("equipmentType") String equipmentType, @Param("systemType") String systemType); + @Query( + "SELECT i FROM InspectionItem i WHERE i.equipmentType = :equipmentType AND i.systemType = :systemType AND i.status = 'ACTIVE' ORDER BY i.sortOrder") + List findByEquipmentTypeAndSystemType( + @Param("equipmentType") String equipmentType, @Param("systemType") String systemType); @Query("SELECT i FROM InspectionItem i WHERE i.status = 'ACTIVE' ORDER BY i.sortOrder") List findAllActiveOrderBySortOrder(); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionRecordRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionRecordRepository.java index 7aa3450..a5e5e6a 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionRecordRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionRecordRepository.java @@ -1,18 +1,15 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.InspectionRecord; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.util.List; -import java.util.UUID; - -/** - * 巡检记录Repository - */ +/** 巡检记录Repository */ @Repository public interface InspectionRecordRepository extends JpaRepository { @@ -26,11 +23,16 @@ public interface InspectionRecordRepository extends JpaRepository findByInspectionDate(LocalDate inspectionDate); - List findByEquipmentIdAndInspectionDateBetween(UUID equipmentId, LocalDate startDate, LocalDate endDate); + List findByEquipmentIdAndInspectionDateBetween( + UUID equipmentId, LocalDate startDate, LocalDate endDate); - @Query("SELECT r FROM InspectionRecord r WHERE r.equipmentId = :equipmentId ORDER BY r.inspectionDate DESC") - List findByEquipmentIdOrderByInspectionDateDesc(@Param("equipmentId") UUID equipmentId); + @Query( + "SELECT r FROM InspectionRecord r WHERE r.equipmentId = :equipmentId ORDER BY r.inspectionDate DESC") + List findByEquipmentIdOrderByInspectionDateDesc( + @Param("equipmentId") UUID equipmentId); - @Query("SELECT r FROM InspectionRecord r WHERE r.inspectionDate BETWEEN :startDate AND :endDate ORDER BY r.inspectionDate") - List findByDateRange(@Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); -} \ No newline at end of file + @Query( + "SELECT r FROM InspectionRecord r WHERE r.inspectionDate BETWEEN :startDate AND :endDate ORDER BY r.inspectionDate") + List findByDateRange( + @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionTemplateRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionTemplateRepository.java index 3a118c2..af95144 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionTemplateRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/InspectionTemplateRepository.java @@ -1,16 +1,19 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.InspectionTemplate; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface InspectionTemplateRepository extends JpaRepository { List findByProjectId(UUID projectId); + List findByEquipmentType(String equipmentType); + Optional findByTemplateCode(String templateCode); + boolean existsByTemplateCode(String templateCode); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectConfigRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectConfigRepository.java index d7cd7af..2afef99 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectConfigRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectConfigRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.ProjectConfig; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface ProjectConfigRepository extends JpaRepository { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java index fd387a8..05d9dce 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java @@ -1,6 +1,9 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.Project; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,12 +12,9 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - @Repository -public interface ProjectRepository extends JpaRepository, JpaSpecificationExecutor { +public interface ProjectRepository + extends JpaRepository, JpaSpecificationExecutor { Optional findByCode(String code); @@ -22,13 +22,13 @@ public interface ProjectRepository extends JpaRepository, JpaSpec Page findByStatus(String status, Pageable pageable); - @Query("SELECT p FROM Project p WHERE " + - "(:keyword IS NULL OR p.name LIKE %:keyword% " + - "OR p.code LIKE %:keyword%) " + - "AND (:status IS NULL OR p.status = :status)") - Page searchProjects(@Param("keyword") String keyword, - @Param("status") String status, - Pageable pageable); + @Query( + "SELECT p FROM Project p WHERE " + + "(:keyword IS NULL OR p.name LIKE %:keyword% " + + "OR p.code LIKE %:keyword%) " + + "AND (:status IS NULL OR p.status = :status)") + Page searchProjects( + @Param("keyword") String keyword, @Param("status") String status, Pageable pageable); @Query("SELECT p FROM Project p WHERE p.status IN ('ACTIVE', 'INACTIVE') ORDER BY p.name") List findActiveProjectsForSelector(); @@ -38,6 +38,9 @@ public interface ProjectRepository extends JpaRepository, JpaSpec boolean existsByCodeAndIdNot(String code, UUID id); - @Query(value = "SELECT COUNT(*) FROM equipment WHERE project_id = :projectId AND is_deleted = false", nativeQuery = true) + @Query( + value = + "SELECT COUNT(*) FROM equipment WHERE project_id = :projectId AND is_deleted = false", + nativeQuery = true) long countEquipmentsByProjectId(@Param("projectId") UUID projectId); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatisticsRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatisticsRepository.java index 23dbc16..ee7ac97 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatisticsRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatisticsRepository.java @@ -1,11 +1,10 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.ProjectStatistics; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface ProjectStatisticsRepository extends JpaRepository { diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatusHistoryRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatusHistoryRepository.java index 65999df..3e06197 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatusHistoryRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectStatusHistoryRepository.java @@ -1,6 +1,9 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.ProjectStatusHistory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,18 +11,16 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - @Repository public interface ProjectStatusHistoryRepository extends JpaRepository { List findByProjectIdOrderByCreatedAtDesc(UUID projectId); - Page findByProjectIdOrderByCreatedAtDesc(UUID projectId, Pageable pageable); + Page findByProjectIdOrderByCreatedAtDesc( + UUID projectId, Pageable pageable); - @Query("SELECT h FROM ProjectStatusHistory h WHERE h.projectId = :projectId ORDER BY h.createdAt DESC LIMIT 1") + @Query( + "SELECT h FROM ProjectStatusHistory h WHERE h.projectId = :projectId ORDER BY h.createdAt DESC LIMIT 1") Optional findLatestByProjectId(@Param("projectId") UUID projectId); long countByProjectId(UUID projectId); diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java index 5fcda96..642f563 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java @@ -1,6 +1,10 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.SpaceNode; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,18 +12,13 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - @Repository public interface SpaceNodeRepository extends JpaRepository { /** * 查询所有未删除的空间节点(不分页) * - *

警告:此方法会加载全表数据,建议使用分页版本。

+ *

警告:此方法会加载全表数据,建议使用分页版本。 * * @return 所有未删除的空间节点列表 * @deprecated 建议使用 {@link #findByIsDeletedFalse(Pageable)} 分页版本 @@ -39,7 +38,8 @@ public interface SpaceNodeRepository extends JpaRepository { List findByProjectIdAndIsDeletedFalseOrderBySortOrderAsc(UUID projectId); - List findByProjectIdAndNodeTypeAndIsDeletedFalse(UUID projectId, SpaceNode.NodeType nodeType); + List findByProjectIdAndNodeTypeAndIsDeletedFalse( + UUID projectId, SpaceNode.NodeType nodeType); List findByProjectIdAndParentIdIsNullAndIsDeletedFalse(UUID projectId); @@ -47,25 +47,26 @@ public interface SpaceNodeRepository extends JpaRepository { List findByProjectIdAndIsEquipment(UUID projectId, Boolean isEquipment); - List findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull(UUID projectId, Boolean isEquipment); + List findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull( + UUID projectId, Boolean isEquipment); - List findByProjectIdAndIsEquipmentAndNextInspectionDateBefore(UUID projectId, Boolean isEquipment, LocalDate date); + List findByProjectIdAndIsEquipmentAndNextInspectionDateBefore( + UUID projectId, Boolean isEquipment, LocalDate date); - /** - * 查询楼栋下所有房间和商铺(按楼层号分组) - */ - List findByParentIdAndNodeTypeInAndIsDeletedFalse(UUID parentId, List nodeTypes); + /** 查询楼栋下所有房间和商铺(按楼层号分组) */ + List findByParentIdAndNodeTypeInAndIsDeletedFalse( + UUID parentId, List nodeTypes); - /** - * 统计项目下的空间数量 - */ + /** 统计项目下的空间数量 */ long countByProjectIdAndIsDeletedFalse(UUID projectId); - /** - * 统计项目下指定类型空间数量 - */ + /** 统计项目下指定类型空间数量 */ long countByProjectIdAndNodeTypeAndIsDeletedFalse(UUID projectId, SpaceNode.NodeType nodeType); - @Query("SELECT MAX(sn.code) FROM SpaceNode sn WHERE sn.projectId = :projectId AND sn.nodeType = :nodeType AND sn.code LIKE CONCAT(:prefix, '%') AND sn.isDeleted = false") - Optional findMaxCodeByProjectAndTypeAndPrefix(@Param("projectId") UUID projectId, @Param("nodeType") SpaceNode.NodeType nodeType, @Param("prefix") String prefix); + @Query( + "SELECT MAX(sn.code) FROM SpaceNode sn WHERE sn.projectId = :projectId AND sn.nodeType = :nodeType AND sn.code LIKE CONCAT(:prefix, '%') AND sn.isDeleted = false") + Optional findMaxCodeByProjectAndTypeAndPrefix( + @Param("projectId") UUID projectId, + @Param("nodeType") SpaceNode.NodeType nodeType, + @Param("prefix") String prefix); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartCategoryRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartCategoryRepository.java index 3cacd91..79d479c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartCategoryRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartCategoryRepository.java @@ -1,15 +1,17 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.SparePartCategory; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface SparePartCategoryRepository extends JpaRepository { List findByParentIdIsNull(); + List findByParentId(UUID parentId); + Optional findByCategoryCode(String categoryCode); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRecordRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRecordRepository.java index 90f3f31..210448e 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRecordRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRecordRepository.java @@ -1,15 +1,17 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.SparePartRecord; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface SparePartRecordRepository extends JpaRepository { List findBySparePartIdOrderByRecordDateDesc(UUID sparePartId); + Optional findTopBySparePartIdOrderByRecordDateDesc(UUID sparePartId); + List findByRelatedOrderId(UUID relatedOrderId); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRepository.java index beee68b..0ab8e87 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRepository.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SparePartRepository.java @@ -1,21 +1,25 @@ package com.ether.pms.mdm.repository; import com.ether.pms.mdm.entity.SparePart; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.Optional; -import java.util.UUID; @Repository public interface SparePartRepository extends JpaRepository { List findByProjectIdAndStatus(UUID projectId, SparePart.Status status); + List findByCategoryId(UUID categoryId); + Optional findBySparePartCode(String sparePartCode); + boolean existsBySparePartCode(String sparePartCode); - @Query("SELECT s FROM SparePart s WHERE s.projectId = :projectId AND s.currentStock <= s.safeStock") + @Query( + "SELECT s FROM SparePart s WHERE s.projectId = :projectId AND s.currentStock <= s.safeStock") List findLowStockParts(@Param("projectId") UUID projectId); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java index 1fca4d2..d86fbcb 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyConsumptionService.java @@ -10,8 +10,13 @@ import java.util.UUID; public interface EnergyConsumptionService { EnergyConsumption recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy); + List getConsumptionByMeter(UUID meterId); - List getConsumptionByMeterAndDateRange(UUID meterId, LocalDate startDate, LocalDate endDate); + + List getConsumptionByMeterAndDateRange( + UUID meterId, LocalDate startDate, LocalDate endDate); + Map getConsumptionByType(UUID projectId, LocalDate month); + BigDecimal getUnitConsumption(UUID projectId, LocalDate month); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java index 509e4a2..65a255e 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/EnergyMeterService.java @@ -6,9 +6,14 @@ import java.util.UUID; public interface EnergyMeterService { EnergyMeter createMeter(EnergyMeter meter); + EnergyMeter updateMeter(UUID id, EnergyMeter meter); + void deleteMeter(UUID id); + EnergyMeter getMeterById(UUID id); + List getMetersByProject(UUID projectId); + List getMetersByType(UUID projectId, EnergyMeter.EnergyType energyType); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionItemService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionItemService.java index 7f155fa..e912c45 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionItemService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionItemService.java @@ -1,13 +1,10 @@ package com.ether.pms.mdm.service; import com.ether.pms.mdm.entity.InspectionItem; - import java.util.List; import java.util.UUID; -/** - * 巡检标准项服务接口 - */ +/** 巡检标准项服务接口 */ public interface InspectionItemService { InspectionItem createItem(InspectionItem item); @@ -26,5 +23,6 @@ public interface InspectionItemService { List getActiveItems(); - List getItemsByEquipmentTypeAndSystemType(String equipmentType, String systemType); -} \ No newline at end of file + List getItemsByEquipmentTypeAndSystemType( + String equipmentType, String systemType); +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionRecordService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionRecordService.java index 9601f8f..408f156 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionRecordService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionRecordService.java @@ -1,14 +1,11 @@ package com.ether.pms.mdm.service; import com.ether.pms.mdm.entity.InspectionRecord; - import java.time.LocalDate; import java.util.List; import java.util.UUID; -/** - * 巡检记录服务接口 - */ +/** 巡检记录服务接口 */ public interface InspectionRecordService { InspectionRecord createRecord(InspectionRecord record); @@ -31,7 +28,8 @@ public interface InspectionRecordService { List getRecordsByDateRange(LocalDate startDate, LocalDate endDate); - List getRecordsByEquipmentAndDateRange(UUID equipmentId, LocalDate startDate, LocalDate endDate); + List getRecordsByEquipmentAndDateRange( + UUID equipmentId, LocalDate startDate, LocalDate endDate); InspectionRecord completeRecord(UUID id); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java index c6c937a..2575c05 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/InspectionTemplateService.java @@ -6,9 +6,14 @@ import java.util.UUID; public interface InspectionTemplateService { InspectionTemplate createTemplate(InspectionTemplate template); + InspectionTemplate updateTemplate(UUID id, InspectionTemplate template); + InspectionTemplate copyTemplate(UUID templateId, String newName); + List getTemplatesByType(String equipmentType); + List getTemplatesByProject(UUID projectId); + InspectionTemplate getTemplateById(UUID id); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectConfigService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectConfigService.java index 0d9f928..32321f9 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectConfigService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectConfigService.java @@ -1,130 +1,11 @@ package com.ether.pms.mdm.service; import com.ether.pms.mdm.dto.ProjectConfigDTO; -import com.ether.pms.mdm.entity.ProjectConfig; -import com.ether.pms.mdm.repository.ProjectConfigRepository; -import com.ether.pms.mdm.repository.ProjectRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.UUID; -/** - * 项目配置管理服务 - */ -@Service -@RequiredArgsConstructor -public class ProjectConfigService { +public interface ProjectConfigService { - private final ProjectConfigRepository projectConfigRepository; - private final ProjectRepository projectRepository; + ProjectConfigDTO getConfig(UUID projectId); - /** - * PM-008: 获取项目配置 - */ - public ProjectConfigDTO getConfig(UUID projectId) { - // 验证项目存在 - if (!projectRepository.existsById(projectId)) { - throw new BusinessException(ErrorCode.PROJECT_002); - } - - ProjectConfig config = projectConfigRepository.findByProjectId(projectId) - .orElseGet(() -> createDefaultConfig(projectId)); - - return toDTO(config); - } - - /** - * PM-008: 更新项目配置 - */ - @Transactional - public ProjectConfigDTO updateConfig(UUID projectId, ProjectConfigDTO dto) { - // 验证项目存在 - if (!projectRepository.existsById(projectId)) { - throw new BusinessException(ErrorCode.PROJECT_002); - } - - ProjectConfig config = projectConfigRepository.findByProjectId(projectId) - .orElseGet(() -> { - ProjectConfig newConfig = new ProjectConfig(); - newConfig.setProjectId(projectId); - return newConfig; - }); - - // 更新配置 - if (dto.getEnableReservation() != null) { - config.setEnableReservation(dto.getEnableReservation()); - } - if (dto.getEnableVisitor() != null) { - config.setEnableVisitor(dto.getEnableVisitor()); - } - if (dto.getEnableComplaint() != null) { - config.setEnableComplaint(dto.getEnableComplaint()); - } - if (dto.getEnablePayment() != null) { - config.setEnablePayment(dto.getEnablePayment()); - } - if (dto.getEnableAnnouncement() != null) { - config.setEnableAnnouncement(dto.getEnableAnnouncement()); - } - if (dto.getEnableSurvey() != null) { - config.setEnableSurvey(dto.getEnableSurvey()); - } - if (dto.getEnableVote() != null) { - config.setEnableVote(dto.getEnableVote()); - } - if (dto.getEnableMaintenance() != null) { - config.setEnableMaintenance(dto.getEnableMaintenance()); - } - if (dto.getEnableAsset() != null) { - config.setEnableAsset(dto.getEnableAsset()); - } - if (dto.getCustomConfig() != null) { - config.setCustomConfig(dto.getCustomConfig()); - } - - ProjectConfig saved = projectConfigRepository.save(config); - return toDTO(saved); - } - - /** - * 创建默认配置 - */ - private ProjectConfig createDefaultConfig(UUID projectId) { - ProjectConfig config = new ProjectConfig(); - config.setProjectId(projectId); - config.setEnableReservation(false); - config.setEnableVisitor(false); - config.setEnableComplaint(true); - config.setEnablePayment(false); - config.setEnableAnnouncement(true); - config.setEnableSurvey(false); - config.setEnableVote(false); - config.setEnableMaintenance(true); - config.setEnableAsset(false); - return projectConfigRepository.save(config); - } - - /** - * 转换为DTO - */ - private ProjectConfigDTO toDTO(ProjectConfig config) { - ProjectConfigDTO dto = new ProjectConfigDTO(); - dto.setId(config.getId()); - dto.setProjectId(config.getProjectId()); - dto.setEnableReservation(config.getEnableReservation()); - dto.setEnableVisitor(config.getEnableVisitor()); - dto.setEnableComplaint(config.getEnableComplaint()); - dto.setEnablePayment(config.getEnablePayment()); - dto.setEnableAnnouncement(config.getEnableAnnouncement()); - dto.setEnableSurvey(config.getEnableSurvey()); - dto.setEnableVote(config.getEnableVote()); - dto.setEnableMaintenance(config.getEnableMaintenance()); - dto.setEnableAsset(config.getEnableAsset()); - dto.setCustomConfig(config.getCustomConfig()); - return dto; - } + ProjectConfigDTO updateConfig(UUID projectId, ProjectConfigDTO dto); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectMemberService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectMemberService.java index ea61fce..f9ec40c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectMemberService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectMemberService.java @@ -1,138 +1,16 @@ package com.ether.pms.mdm.service; -import com.ether.pms.auth.entity.User; -import com.ether.pms.auth.entity.UserProject; -import com.ether.pms.auth.repository.UserProjectRepository; -import com.ether.pms.auth.repository.UserRepository; import com.ether.pms.mdm.dto.AddMemberRequest; import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.mdm.dto.ProjectMemberDTO; -import com.ether.pms.mdm.repository.ProjectRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; +import java.util.UUID; import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; +public interface ProjectMemberService { -/** - * 项目成员管理服务 - */ -@Service -@RequiredArgsConstructor -public class ProjectMemberService { + PageResponse getMembers(UUID projectId, Pageable pageable); - private final UserProjectRepository userProjectRepository; - private final UserRepository userRepository; - private final ProjectRepository projectRepository; + void addMembers(UUID projectId, AddMemberRequest request); - /** - * PM-003: 获取项目成员列表 - */ - public PageResponse getMembers(UUID projectId, Pageable pageable) { - // 验证项目存在 - if (!projectRepository.existsById(projectId)) { - throw new BusinessException(ErrorCode.PROJECT_002); - } - - // 分页查询用户项目关联 - Page userProjectPage = userProjectRepository.findByProjectId(projectId, pageable); - - // 获取用户ID列表 - List userIds = userProjectPage.getContent().stream() - .map(UserProject::getUserId) - .collect(Collectors.toList()); - - // 批量查询用户信息 - Map userMap; - if (!userIds.isEmpty()) { - List users = userRepository.findAllById(userIds); - userMap = users.stream() - .collect(Collectors.toMap(User::getId, Function.identity())); - } else { - userMap = Collections.emptyMap(); - } - - // 使用 final 变量在 lambda 中 - final Map finalUserMap = userMap; - - // 构建DTO - List members = userProjectPage.getContent().stream() - .map(up -> { - User user = finalUserMap.get(up.getUserId()); - ProjectMemberDTO dto = new ProjectMemberDTO(); - dto.setId(up.getId()); - dto.setUserId(up.getUserId()); - dto.setRoleInProject(up.getRoleInProject()); - dto.setJoinedAt(up.getJoinedAt()); - if (user != null) { - dto.setUsername(user.getUsername()); - dto.setRealName(user.getRealName()); - dto.setPhone(user.getPhone()); - dto.setAvatar(user.getAvatar()); - } - return dto; - }) - .collect(Collectors.toList()); - - return PageResponse.of( - members, - userProjectPage.getNumber(), - userProjectPage.getSize(), - userProjectPage.getTotalElements() - ); - } - - /** - * PM-003: 添加项目成员 - */ - @Transactional - public void addMembers(UUID projectId, AddMemberRequest request) { - // 验证项目存在 - if (!projectRepository.existsById(projectId)) { - throw new BusinessException(ErrorCode.PROJECT_002); - } - - List userProjects = new ArrayList<>(); - - for (UUID userId : request.getUserIds()) { - // 验证用户存在 - if (!userRepository.existsById(userId)) { - throw new BusinessException(ErrorCode.USER_003); - } - - // 检查是否已是成员 - if (userProjectRepository.existsByUserIdAndProjectId(userId, projectId)) { - throw new BusinessException(ErrorCode.PROJECT_004); - } - - UserProject userProject = new UserProject(); - userProject.setUserId(userId); - userProject.setProjectId(projectId); - userProject.setRoleInProject(request.getRoleInProject()); - userProject.setJoinedAt(LocalDateTime.now()); - userProjects.add(userProject); - } - - userProjectRepository.saveAll(userProjects); - } - - /** - * PM-003: 移除项目成员 - */ - @Transactional - public void removeMember(UUID projectId, UUID memberId) { - // 验证成员关联存在 - if (!userProjectRepository.existsById(memberId)) { - throw new BusinessException(ErrorCode.PROJECT_005); - } - - userProjectRepository.deleteById(memberId); - } + void removeMember(UUID projectId, UUID memberId); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java index 91249b9..41357a9 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java @@ -1,294 +1,34 @@ package com.ether.pms.mdm.service; -import com.ether.pms.auth.repository.UserProjectRepository; import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.mdm.dto.ProjectDeleteCheckVO; -import com.ether.pms.mdm.dto.ProjectDeleteStatistics; import com.ether.pms.mdm.dto.ProjectQueryRequest; import com.ether.pms.mdm.dto.ProjectSelectorItem; import com.ether.pms.mdm.entity.Project; -import com.ether.pms.mdm.repository.ProjectRepository; -import com.ether.pms.mdm.repository.SpaceNodeRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.time.Year; import java.util.List; import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Collectors; -@Service -@RequiredArgsConstructor -public class ProjectService { +public interface ProjectService { - private final ProjectRepository projectRepository; - private final SpaceNodeRepository spaceNodeRepository; - private final UserProjectRepository userProjectRepository; + List findAll(); - /** - * 查询所有项目(不分页) - * - *

性能说明:此方法用于获取全部项目列表,通常用于项目选择器等场景。 - * 项目数量一般有限(<1000个),使用 findAll 是可接受的。 - * 如果项目数量增长,建议使用 {@link #queryProjects(ProjectQueryRequest)} 分页查询方法。

- * - * @return 所有项目的列表 - */ - public List findAll() { - // TODO: 如果项目数量超过 1000 个,应改用分页查询 - return projectRepository.findAll(); - } + Project findById(UUID id); - public Project findById(UUID id) { - return projectRepository.findById(id) - .orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_002)); - } + Project findByCode(String code); - public Project findByCode(String code) { - return projectRepository.findByCode(code) - .orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_002)); - } + PageResponse queryProjects(ProjectQueryRequest request); - /** - * PM-001: 分页查询项目 - */ - public PageResponse queryProjects(ProjectQueryRequest request) { - Sort sort = Sort.by( - "DESC".equalsIgnoreCase(request.getSortDirection()) - ? Sort.Direction.DESC - : Sort.Direction.ASC, - request.getSortBy() - ); - Pageable pageable = PageRequest.of(request.getPage(), request.getSize(), sort); + List getSelectorList(); - Page projectPage = projectRepository.searchProjects( - request.getKeyword(), - request.getStatus(), - pageable - ); + String generateCode(); - return PageResponse.of( - projectPage.getContent(), - projectPage.getNumber(), - projectPage.getSize(), - projectPage.getTotalElements() - ); - } + Project create(Project project); - /** - * PM-010: 获取项目选择器列表 - */ - public List getSelectorList() { - List projects = projectRepository.findActiveProjectsForSelector(); - return projects.stream() - .map(p -> new ProjectSelectorItem( - p.getId().toString(), - p.getCode(), - p.getName(), - p.getStatus() - )) - .collect(Collectors.toList()); - } + Project update(UUID id, Project project); - /** - * PM-005: 生成项目编码 - * 格式: PRJ-YYYYNNNN (年份+4位序号) - */ - public String generateCode() { - int year = Year.now().getValue(); - int sequence = 1; + void delete(UUID id); - // 尝试生成唯一编码 - String code; - do { - code = String.format("PRJ-%d%04d", year, sequence); - sequence++; - // 防止无限循环 - if (sequence > 9999) { - // 使用随机数作为后备方案 - code = String.format("PRJ-%d%04d", year, - ThreadLocalRandom.current().nextInt(1, 10000)); - break; - } - } while (projectRepository.existsByCode(code)); + void changeStatus(UUID projectId, String newStatus, String reason); - return code; - } - - @Transactional - public Project create(Project project) { - if (projectRepository.existsByCode(project.getCode())) { - throw new BusinessException(ErrorCode.PROJECT_001); - } - return projectRepository.save(project); - } - - @Transactional - public Project update(UUID id, Project project) { - Project existing = findById(id); - - // 检查编码是否被其他项目使用 - if (project.getCode() != null && !project.getCode().equals(existing.getCode())) { - if (projectRepository.existsByCodeAndIdNot(project.getCode(), id)) { - throw new BusinessException(ErrorCode.PROJECT_001); - } - existing.setCode(project.getCode()); - } - - if (project.getName() != null) { - existing.setName(project.getName()); - } - if (project.getDescription() != null) { - existing.setDescription(project.getDescription()); - } - if (project.getAddress() != null) { - existing.setAddress(project.getAddress()); - } - if (project.getProjectType() != null) { - existing.setProjectType(project.getProjectType()); - } - if (project.getProvince() != null) { - existing.setProvince(project.getProvince()); - } - if (project.getCity() != null) { - existing.setCity(project.getCity()); - } - if (project.getDistrict() != null) { - existing.setDistrict(project.getDistrict()); - } - if (project.getLongitude() != null) { - existing.setLongitude(project.getLongitude()); - } - if (project.getLatitude() != null) { - existing.setLatitude(project.getLatitude()); - } - if (project.getStatus() != null) { - existing.setStatus(project.getStatus()); - } - if (project.getBuildingCount() != null) { - existing.setBuildingCount(project.getBuildingCount()); - } - if (project.getUnitCount() != null) { - existing.setUnitCount(project.getUnitCount()); - } - if (project.getRoomCount() != null) { - existing.setRoomCount(project.getRoomCount()); - } - if (project.getFloorCount() != null) { - existing.setFloorCount(project.getFloorCount()); - } - if (project.getLogo() != null) { - existing.setLogo(project.getLogo()); - } - if (project.getContact() != null) { - existing.setContact(project.getContact()); - } - if (project.getContactPhone() != null) { - existing.setContactPhone(project.getContactPhone()); - } - - return projectRepository.save(existing); - } - - @Transactional - public void delete(UUID id) { - projectRepository.deleteById(id); - } - - /** - * PM-006: 变更项目状态 - * 状态转换规则: - * - ACTIVE -> INACTIVE (停用) - * - INACTIVE -> ACTIVE (启用) - * - ACTIVE -> ARCHIVED (归档) - * - INACTIVE -> ARCHIVED (归档) - */ - @Transactional - public void changeStatus(UUID projectId, String newStatus, String reason) { - Project project = findById(projectId); - String currentStatus = project.getStatus(); - - // 验证状态转换是否合法 - if (!isValidStatusTransition(currentStatus, newStatus)) { - throw new BusinessException(ErrorCode.PROJECT_003); - } - - project.setStatus(newStatus); - projectRepository.save(project); - - // TODO: 保存状态变更历史记录(如需要) - } - - /** - * 验证状态转换是否合法 - */ - private boolean isValidStatusTransition(String currentStatus, String newStatus) { - if (currentStatus == null || newStatus == null) { - return false; - } - - // 相同状态不需要转换 - if (currentStatus.equals(newStatus)) { - return true; - } - - // 定义允许的状态转换 - return switch (currentStatus) { - case "ACTIVE" -> "INACTIVE".equals(newStatus) || "ARCHIVED".equals(newStatus); - case "INACTIVE" -> "ACTIVE".equals(newStatus) || "ARCHIVED".equals(newStatus); - case "ARCHIVED" -> false; // 归档状态不能转换到其他状态 - default -> false; - }; - } - - /** - * PM-009: 项目删除前检查 - * 检查项目的关联数据情况,如果存在应收未收费用则无法删除 - */ - public ProjectDeleteCheckVO checkProjectDelete(UUID projectId) { - // 验证项目存在 - findById(projectId); - - // 统计各项数据 - long spaceCount = spaceNodeRepository.countByProjectIdAndIsDeletedFalse(projectId); - long memberCount = userProjectRepository.countByProjectId(projectId); - long equipmentCount = projectRepository.countEquipmentsByProjectId(projectId); - - // 账单模块暂未开发,使用占位值 - int billCount = 0; - BigDecimal totalReceivable = BigDecimal.ZERO; - BigDecimal unpaidAmount = BigDecimal.ZERO; - - // 构建统计数据 - ProjectDeleteStatistics statistics = new ProjectDeleteStatistics(); - statistics.setSpaceCount((int) spaceCount); - statistics.setMemberCount((int) memberCount); - statistics.setEquipmentCount((int) equipmentCount); - statistics.setBillCount(billCount); - statistics.setTotalReceivable(totalReceivable); - statistics.setUnpaidAmount(unpaidAmount); - - // 构建返回结果 - ProjectDeleteCheckVO result = new ProjectDeleteCheckVO(); - result.setStatistics(statistics); - - // 如果存在未收金额,则不能删除 - if (unpaidAmount.compareTo(BigDecimal.ZERO) > 0) { - result.setCanDelete(false); - result.setReason(String.format("该项目存在 %d 笔应收未收费用,共计 ¥%s 元", billCount, unpaidAmount.toString())); - } else { - result.setCanDelete(true); - } - - return result; - } + ProjectDeleteCheckVO checkProjectDelete(UUID projectId); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectStatisticsService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectStatisticsService.java index dd98b70..711763a 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectStatisticsService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectStatisticsService.java @@ -1,62 +1,11 @@ package com.ether.pms.mdm.service; -import com.ether.pms.auth.repository.ProjectStaffRepository; import com.ether.pms.mdm.entity.ProjectStatistics; -import com.ether.pms.mdm.entity.SpaceNode; -import com.ether.pms.mdm.repository.ProjectStatisticsRepository; -import com.ether.pms.mdm.repository.SpaceNodeRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; import java.util.UUID; -/** - * 项目统计服务 - */ -@Service -@RequiredArgsConstructor -public class ProjectStatisticsService { +public interface ProjectStatisticsService { - private final ProjectStatisticsRepository statisticsRepository; - private final SpaceNodeRepository spaceNodeRepository; - private final ProjectStaffRepository projectStaffRepository; + ProjectStatistics getStatistics(UUID projectId); - /** - * 获取项目统计信息(实时计算) - */ - public ProjectStatistics getStatistics(UUID projectId) { - ProjectStatistics stats = statisticsRepository.findByProjectId(projectId) - .orElseGet(() -> { - ProjectStatistics newStats = new ProjectStatistics(); - newStats.setProjectId(projectId); - return newStats; - }); - - // 实时计算各项统计数据 - stats.setMemberCount((int) projectStaffRepository.countByProjectId(projectId)); - stats.setBuildingCount((int) spaceNodeRepository.countByProjectIdAndNodeTypeAndIsDeletedFalse(projectId, SpaceNode.NodeType.BUILDING)); - stats.setRoomCount((int) spaceNodeRepository.countByProjectIdAndNodeTypeAndIsDeletedFalse(projectId, SpaceNode.NodeType.ROOM)); - - // TODO: 业主数和租户数需要从业主表/租户表统计,暂时设为0 - stats.setOwnerCount(0); - stats.setTenantCount(0); - - stats.setLastSyncedAt(LocalDateTime.now()); - return stats; - } - - /** - * 初始化项目统计 - */ - @Transactional - public ProjectStatistics initializeStatistics(UUID projectId) { - if (statisticsRepository.existsByProjectId(projectId)) { - return getStatistics(projectId); - } - ProjectStatistics stats = new ProjectStatistics(); - stats.setProjectId(projectId); - return statisticsRepository.save(stats); - } + ProjectStatistics initializeStatistics(UUID projectId); } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java index 0774d87..9cbc7f1 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java @@ -1,43 +1,39 @@ package com.ether.pms.mdm.service; +import com.ether.pms.common.ApiResponse; import com.ether.pms.common.BusinessException; import com.ether.pms.common.ErrorCode; +import com.ether.pms.mdm.dto.EquipmentCreateDTO; +import com.ether.pms.mdm.dto.FloorDetailVO; +import com.ether.pms.mdm.dto.FloorInfoVO; import com.ether.pms.mdm.dto.SpaceNodeCreateDTO; import com.ether.pms.mdm.dto.SpaceNodeDeleteCheckDTO; import com.ether.pms.mdm.dto.SpaceNodeEquipmentDTO; import com.ether.pms.mdm.dto.SpaceNodeTreeDTO; import com.ether.pms.mdm.dto.SpaceNodeUpdateDTO; -import com.ether.pms.mdm.dto.FloorInfoVO; -import com.ether.pms.mdm.dto.FloorDetailVO; -import com.ether.pms.mdm.entity.SpaceNode; import com.ether.pms.mdm.entity.Project; -import com.ether.pms.mdm.repository.SpaceNodeRepository; +import com.ether.pms.mdm.entity.SpaceNode; import com.ether.pms.mdm.repository.ProjectRepository; +import com.ether.pms.mdm.repository.SpaceNodeRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - import org.springframework.web.multipart.MultipartFile; -import com.ether.pms.common.ApiResponse; -import com.ether.pms.mdm.dto.EquipmentCreateDTO; - @Service @RequiredArgsConstructor public class SpaceNodeService { @@ -59,7 +55,7 @@ public class SpaceNodeService { /** * 查询所有空间节点(不分页,仅用于内部小规模查询) * - *

警告:此方法会加载全表数据,仅在确认数据量较小时使用。

+ *

警告:此方法会加载全表数据,仅在确认数据量较小时使用。 * * @return 所有未删除的空间节点列表 * @deprecated 建议使用 {@link #findAll(Pageable)} 分页版本 @@ -70,7 +66,8 @@ public class SpaceNodeService { } public SpaceNode findById(UUID id) { - return spaceNodeRepository.findByIdAndIsDeletedFalse(id) + return spaceNodeRepository + .findByIdAndIsDeletedFalse(id) .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); } @@ -110,9 +107,8 @@ public class SpaceNodeService { } List rootNodes = new ArrayList<>(); - List allDtos = nodes.stream() - .map(SpaceNodeTreeDTO::fromEntity) - .collect(Collectors.toList()); + List allDtos = + nodes.stream().map(SpaceNodeTreeDTO::fromEntity).collect(Collectors.toList()); for (SpaceNodeTreeDTO dto : allDtos) { if (dto.getParentId() == null) { @@ -289,13 +285,21 @@ public class SpaceNodeService { } public SpaceNodeDeleteCheckDTO checkDeleteInfo(UUID id) { - SpaceNode node = spaceNodeRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); + SpaceNode node = + spaceNodeRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); List children = findChildrenRecursively(id); - Map childTypeCount = children.stream() - .collect(Collectors.groupingBy(c -> c.getNodeType().name(), Collectors.collectingAndThen(Collectors.counting(), Long::intValue))); + Map childTypeCount = + children.stream() + .collect( + Collectors.groupingBy( + c -> c.getNodeType().name(), + Collectors.collectingAndThen( + Collectors.counting(), Long::intValue))); int totalDescendant = countDescendants(children); - return new SpaceNodeDeleteCheckDTO(id, node.getName(), children.size(), childTypeCount, totalDescendant); + return new SpaceNodeDeleteCheckDTO( + id, node.getName(), children.size(), childTypeCount, totalDescendant); } private int countDescendants(List children) { @@ -309,8 +313,10 @@ public class SpaceNodeService { @Transactional public void deleteWithChildren(UUID id) { - SpaceNode node = spaceNodeRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); + SpaceNode node = + spaceNodeRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); List children = findChildrenRecursively(id); for (SpaceNode child : children) { child.setIsDeleted(true); @@ -320,56 +326,58 @@ public class SpaceNodeService { spaceNodeRepository.save(node); } - // ========== 设备相关方法 ========== + // ========== 设备相关方法(@Deprecated:将在后续版本迁移到 module-asset) ========== /** * 根据ID获取设备详情 + * + * @deprecated 此方法属于设备管理,将在后续版本迁移到 module-asset */ + @Deprecated public SpaceNodeEquipmentDTO getEquipmentById(UUID id) { - SpaceNode node = spaceNodeRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); + SpaceNode node = + spaceNodeRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); if (!Boolean.TRUE.equals(node.getIsEquipment())) { throw new BusinessException(ErrorCode.SPACE_004); } return convertToEquipmentDTO(node); } - /** - * 获取项目下的设备列表 - */ + /** 获取项目下的设备列表 */ public List getEquipmentList(UUID projectId) { - List equipmentList = spaceNodeRepository.findByProjectIdAndIsEquipment(projectId, true); - return equipmentList.stream() - .map(this::convertToEquipmentDTO) - .collect(Collectors.toList()); + List equipmentList = + spaceNodeRepository.findByProjectIdAndIsEquipment(projectId, true); + return equipmentList.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList()); } - /** - * 获取特种设备列表 - */ + /** 获取特种设备列表 */ public List getSpecialEquipmentList(UUID projectId) { - List specialEquipmentList = spaceNodeRepository - .findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull(projectId, true); + List specialEquipmentList = + spaceNodeRepository.findByProjectIdAndIsEquipmentAndSpecialEquipmentTypeIsNotNull( + projectId, true); return specialEquipmentList.stream() .map(this::convertToEquipmentDTO) .collect(Collectors.toList()); } - /** - * 获取即将年检的设备 - */ - public List getExpiringInspectionEquipment(UUID projectId, Integer daysAhead) { + /** 获取即将年检的设备 */ + public List getExpiringInspectionEquipment( + UUID projectId, Integer daysAhead) { LocalDate targetDate = LocalDate.now().plusDays(daysAhead); - List expiringList = spaceNodeRepository - .findByProjectIdAndIsEquipmentAndNextInspectionDateBefore(projectId, true, targetDate); - return expiringList.stream() - .map(this::convertToEquipmentDTO) - .collect(Collectors.toList()); + List expiringList = + spaceNodeRepository.findByProjectIdAndIsEquipmentAndNextInspectionDateBefore( + projectId, true, targetDate); + return expiringList.stream().map(this::convertToEquipmentDTO).collect(Collectors.toList()); } /** * 将SpaceNode实体转换为SpaceNodeEquipmentDTO + * + * @deprecated 此方法属于设备管理,将在后续版本迁移到 module-asset */ + @Deprecated private SpaceNodeEquipmentDTO convertToEquipmentDTO(SpaceNode node) { SpaceNodeEquipmentDTO dto = new SpaceNodeEquipmentDTO(); dto.setId(node.getId()); @@ -428,9 +436,10 @@ public class SpaceNodeService { // 解析 commonSpareParts JSON if (node.getCommonSpareParts() != null && !node.getCommonSpareParts().isEmpty()) { try { - List spareParts = objectMapper.readValue( - node.getCommonSpareParts(), - new TypeReference>() {}); + List spareParts = + objectMapper.readValue( + node.getCommonSpareParts(), + new TypeReference>() {}); dto.setCommonSpareParts(spareParts); } catch (JsonProcessingException e) { // JSON解析失败时设置为空列表 @@ -443,9 +452,7 @@ public class SpaceNodeService { return dto; } - /** - * 创建设备 - */ + /** 创建设备 */ @Transactional public SpaceNode createEquipment(EquipmentCreateDTO dto) { SpaceNode node = new SpaceNode(); @@ -486,9 +493,7 @@ public class SpaceNodeService { return spaceNodeRepository.save(node); } - /** - * 批量创建空间节点 - */ + /** 批量创建空间节点 */ @Transactional public List batchCreate(List dtoList) { List createdNodes = new ArrayList<>(); @@ -501,7 +506,10 @@ public class SpaceNodeService { /** * 批量创建设备 + * + * @deprecated 此方法属于设备管理,将在后续版本迁移到 module-asset */ + @Deprecated @Transactional public List batchCreateEquipment(List dtoList) { List createdNodes = new ArrayList<>(); @@ -514,7 +522,10 @@ public class SpaceNodeService { /** * Excel导入设备 + * + * @deprecated 此方法属于设备管理,将在后续版本迁移到 module-asset */ + @Deprecated @Transactional public ApiResponse importEquipmentFromExcel(MultipartFile file, UUID projectId) { List successList = new ArrayList<>(); @@ -553,7 +564,8 @@ public class SpaceNodeService { dto.setRatedCurrent(new BigDecimal(fields[4].replace("\"", "").trim())); } if (fields.length > 5 && !fields[5].replace("\"", "").trim().isEmpty()) { - dto.setDesignLifeYears(Integer.parseInt(fields[5].replace("\"", "").trim())); + dto.setDesignLifeYears( + Integer.parseInt(fields[5].replace("\"", "").trim())); } if (fields.length > 6 && !fields[6].replace("\"", "").trim().isEmpty()) { dto.setMaintenanceVendor(fields[6].replace("\"", "").trim()); @@ -565,7 +577,8 @@ public class SpaceNodeService { dto.setSpecialEquipmentType(fields[8].replace("\"", "").trim()); } if (fields.length > 9 && !fields[9].replace("\"", "").trim().isEmpty()) { - dto.setInspectionCycle(Integer.parseInt(fields[9].replace("\"", "").trim())); + dto.setInspectionCycle( + Integer.parseInt(fields[9].replace("\"", "").trim())); } createEquipment(dto); @@ -596,9 +609,7 @@ public class SpaceNodeService { // ========== 楼层信息相关方法 ========== - /** - * 获取楼栋楼层信息 - */ + /** 获取楼栋楼层信息 */ public FloorInfoVO getBuildingFloorInfo(String buildingIdStr) { UUID buildingId = UUID.fromString(buildingIdStr); SpaceNode building = findById(buildingId); @@ -616,13 +627,15 @@ public class SpaceNodeService { BuildingFloorConfig config = parseBuildingFloorConfig(building.getAttributes()); // 查询该楼栋下所有房间和商铺 - List roomAndShops = spaceNodeRepository.findByParentIdAndNodeTypeInAndIsDeletedFalse( - buildingId, List.of(SpaceNode.NodeType.ROOM, SpaceNode.NodeType.SHOP)); + List roomAndShops = + spaceNodeRepository.findByParentIdAndNodeTypeInAndIsDeletedFalse( + buildingId, List.of(SpaceNode.NodeType.ROOM, SpaceNode.NodeType.SHOP)); // 按楼层号分组统计 - Map> floorGrouped = roomAndShops.stream() - .filter(node -> node.getFloorNumber() != null) - .collect(Collectors.groupingBy(SpaceNode::getFloorNumber)); + Map> floorGrouped = + roomAndShops.stream() + .filter(node -> node.getFloorNumber() != null) + .collect(Collectors.groupingBy(SpaceNode::getFloorNumber)); // 确定总楼层数和地下楼层数 int totalFloors = 0; @@ -630,17 +643,19 @@ public class SpaceNodeService { if (!floorGrouped.isEmpty()) { // 地上楼层:floorNumber >= 1 - int maxAboveGround = floorGrouped.keySet().stream() - .filter(f -> f >= 1) - .max(Integer::compareTo) - .orElse(0); + int maxAboveGround = + floorGrouped.keySet().stream() + .filter(f -> f >= 1) + .max(Integer::compareTo) + .orElse(0); totalFloors = maxAboveGround; // 地下楼层:floorNumber < 0 - int maxUnderground = floorGrouped.keySet().stream() - .filter(f -> f < 0) - .min(Integer::compareTo) - .orElse(0); + int maxUnderground = + floorGrouped.keySet().stream() + .filter(f -> f < 0) + .min(Integer::compareTo) + .orElse(0); // 地下楼层数 = |最小负楼层号|,如 -1 则 1层,-2 则 2层 if (maxUnderground < 0) { undergroundFloors = Math.abs(maxUnderground); @@ -648,7 +663,8 @@ public class SpaceNodeService { } else if (config != null) { // 没有房间数据时使用配置 totalFloors = config.getTotalFloors() != null ? config.getTotalFloors() : 0; - undergroundFloors = config.getUndergroundFloors() != null ? config.getUndergroundFloors() : 0; + undergroundFloors = + config.getUndergroundFloors() != null ? config.getUndergroundFloors() : 0; } floorInfoVO.setTotalFloors(totalFloors); @@ -674,8 +690,12 @@ public class SpaceNodeService { List nodes = floorGrouped.get(floorNum); detail.setRoomCount(nodes.size()); // 根据实际数据更新 hasRooms/hasShop - detail.setHasRooms(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); - detail.setHasShop(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); + detail.setHasRooms( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); + detail.setHasShop( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); } else { detail.setRoomCount(0); } @@ -693,8 +713,12 @@ public class SpaceNodeService { if (floorGrouped.containsKey(i)) { List nodes = floorGrouped.get(i); detail.setRoomCount(nodes.size()); - detail.setHasRooms(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); - detail.setHasShop(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); + detail.setHasRooms( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); + detail.setHasShop( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); } else { detail.setRoomCount(0); detail.setHasRooms(false); @@ -710,8 +734,12 @@ public class SpaceNodeService { if (floorGrouped.containsKey(i)) { List nodes = floorGrouped.get(i); detail.setRoomCount(nodes.size()); - detail.setHasRooms(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); - detail.setHasShop(nodes.stream().anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); + detail.setHasRooms( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.ROOM)); + detail.setHasShop( + nodes.stream() + .anyMatch(n -> n.getNodeType() == SpaceNode.NodeType.SHOP)); } else { detail.setRoomCount(0); detail.setHasRooms(false); @@ -728,9 +756,7 @@ public class SpaceNodeService { return floorInfoVO; } - /** - * 解析楼栋楼层配置JSON - */ + /** 解析楼栋楼层配置JSON */ private BuildingFloorConfig parseBuildingFloorConfig(String attributes) { if (attributes == null || attributes.isEmpty()) { return null; @@ -742,22 +768,24 @@ public class SpaceNodeService { } } - /** - * 楼栋楼层配置(从JSON解析) - */ + /** 楼栋楼层配置(从JSON解析) */ private String generateCode(UUID projectId, SpaceNode.NodeType nodeType) { - Project project = projectRepository.findById(projectId) - .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); + Project project = + projectRepository + .findById(projectId) + .orElseThrow(() -> new BusinessException(ErrorCode.SPACE_002)); - String projSuffix = project.getCode().length() > 4 - ? project.getCode().substring(project.getCode().length() - 4) - : project.getCode(); + String projSuffix = + project.getCode().length() > 4 + ? project.getCode().substring(project.getCode().length() - 4) + : project.getCode(); String prefix = getCodePrefix(nodeType); String codePrefix = prefix + "-" + projSuffix + "-"; - Optional maxCode = spaceNodeRepository.findMaxCodeByProjectAndTypeAndPrefix( - projectId, nodeType, codePrefix); + Optional maxCode = + spaceNodeRepository.findMaxCodeByProjectAndTypeAndPrefix( + projectId, nodeType, codePrefix); int nextSeq = 1; if (maxCode.isPresent() && maxCode.get() != null) { @@ -792,14 +820,11 @@ public class SpaceNodeService { private Map floorConfig; } - /** - * 楼层配置项 - */ + /** 楼层配置项 */ @Data public static class FloorConfigItem { private Boolean hasRooms; private Boolean hasShop; private String remark; } - } diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java index 77c366b..80bde97 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/SparePartService.java @@ -9,19 +9,33 @@ import java.util.UUID; public interface SparePartService { // 分类管理 List getCategories(); + SparePartCategory createCategory(SparePartCategory category); // 备件管理 SparePart createSparePart(SparePart sparePart); + SparePart updateSparePart(UUID id, SparePart sparePart); + void deleteSparePart(UUID id); + SparePart getSparePartById(UUID id); + List getSparePartsByProject(UUID projectId); + List getSparePartsByCategory(UUID categoryId); + List getLowStockParts(UUID projectId); // 库存管理 SparePartRecord inStock(UUID sparePartId, Integer quantity, UUID recordedBy, String remarks); - SparePartRecord outStock(UUID sparePartId, Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks); + + SparePartRecord outStock( + UUID sparePartId, + Integer quantity, + UUID relatedOrderId, + UUID recordedBy, + String remarks); + List getSparePartRecords(UUID sparePartId); -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyConsumptionServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyConsumptionServiceImpl.java index 61a3a5c..1297504 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyConsumptionServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyConsumptionServiceImpl.java @@ -6,13 +6,12 @@ import com.ether.pms.mdm.entity.EnergyMeter; import com.ether.pms.mdm.repository.EnergyConsumptionRepository; import com.ether.pms.mdm.repository.EnergyMeterRepository; import com.ether.pms.mdm.service.EnergyConsumptionService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.math.BigDecimal; import java.time.LocalDate; import java.util.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -23,18 +22,21 @@ public class EnergyConsumptionServiceImpl implements EnergyConsumptionService { @Override @Transactional - public EnergyConsumption recordConsumption(UUID meterId, BigDecimal currentReading, UUID recordedBy) { + public EnergyConsumption recordConsumption( + UUID meterId, BigDecimal currentReading, UUID recordedBy) { // 获取电表信息 - EnergyMeter meter = energyMeterRepository.findById(meterId) - .orElseThrow(() -> new BusinessException(6101, "能源仪表不存在")); + EnergyMeter meter = + energyMeterRepository + .findById(meterId) + .orElseThrow(() -> new BusinessException(6101, "能源仪表不存在")); if (meter.getStatus() != EnergyMeter.Status.ACTIVE) { throw new BusinessException(6102, "只能对ACTIVE状态的仪表进行抄表"); } // 获取上一个抄表记录 - Optional lastConsumption = energyConsumptionRepository - .findTopByMeterIdOrderByConsumptionDateDesc(meterId); + Optional lastConsumption = + energyConsumptionRepository.findTopByMeterIdOrderByConsumptionDateDesc(meterId); BigDecimal previousReading = BigDecimal.ZERO; if (lastConsumption.isPresent()) { @@ -74,16 +76,20 @@ public class EnergyConsumptionServiceImpl implements EnergyConsumptionService { } @Override - public List getConsumptionByMeterAndDateRange(UUID meterId, LocalDate startDate, LocalDate endDate) { - return energyConsumptionRepository.findByMeterIdAndConsumptionDateBetween(meterId, startDate, endDate); + public List getConsumptionByMeterAndDateRange( + UUID meterId, LocalDate startDate, LocalDate endDate) { + return energyConsumptionRepository.findByMeterIdAndConsumptionDateBetween( + meterId, startDate, endDate); } @Override - public Map getConsumptionByType(UUID projectId, LocalDate month) { + public Map getConsumptionByType( + UUID projectId, LocalDate month) { LocalDate startDate = month.withDayOfMonth(1); LocalDate endDate = month.withDayOfMonth(month.lengthOfMonth()); - Map result = new EnumMap<>(EnergyMeter.EnergyType.class); + Map result = + new EnumMap<>(EnergyMeter.EnergyType.class); // 初始化所有能耗类型为0 for (EnergyMeter.EnergyType type : EnergyMeter.EnergyType.values()) { @@ -91,8 +97,9 @@ public class EnergyConsumptionServiceImpl implements EnergyConsumptionService { } // 由于 EnergyConsumption 没有 energyType 字段,按项目统计总消耗 - BigDecimal totalConsumption = energyConsumptionRepository - .sumConsumptionByProjectAndDateRange(projectId, startDate, endDate); + BigDecimal totalConsumption = + energyConsumptionRepository.sumConsumptionByProjectAndDateRange( + projectId, startDate, endDate); if (totalConsumption != null) { // 将总消耗分配给 LIGHTING 类型作为展示(实际业务中应按 meter.energyType 汇总) result.put(EnergyMeter.EnergyType.LIGHTING, totalConsumption); @@ -107,12 +114,15 @@ public class EnergyConsumptionServiceImpl implements EnergyConsumptionService { LocalDate endDate = month.withDayOfMonth(month.lengthOfMonth()); // 获取该项目所有ACTIVE状态的电表 - List meters = energyMeterRepository.findByProjectIdAndStatus(projectId, EnergyMeter.Status.ACTIVE); + List meters = + energyMeterRepository.findByProjectIdAndStatus( + projectId, EnergyMeter.Status.ACTIVE); BigDecimal totalConsumption = BigDecimal.ZERO; for (EnergyMeter meter : meters) { - BigDecimal meterConsumption = energyConsumptionRepository - .sumConsumptionByMeterIdAndDateRange(meter.getId(), startDate, endDate); + BigDecimal meterConsumption = + energyConsumptionRepository.sumConsumptionByMeterIdAndDateRange( + meter.getId(), startDate, endDate); if (meterConsumption != null) { totalConsumption = totalConsumption.add(meterConsumption); } @@ -120,4 +130,4 @@ public class EnergyConsumptionServiceImpl implements EnergyConsumptionService { return totalConsumption; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyMeterServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyMeterServiceImpl.java index a8ff06d..1e06576 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyMeterServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/EnergyMeterServiceImpl.java @@ -4,21 +4,21 @@ import com.ether.pms.common.BusinessException; import com.ether.pms.mdm.entity.EnergyMeter; import com.ether.pms.mdm.repository.EnergyMeterRepository; import com.ether.pms.mdm.service.EnergyMeterService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class EnergyMeterServiceImpl implements EnergyMeterService { private final EnergyMeterRepository energyMeterRepository; - private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final DateTimeFormatter CODE_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); @Override public EnergyMeter createMeter(EnergyMeter meter) { @@ -76,7 +76,8 @@ public class EnergyMeterServiceImpl implements EnergyMeterService { @Override public EnergyMeter getMeterById(UUID id) { - return energyMeterRepository.findById(id) + return energyMeterRepository + .findById(id) .orElseThrow(() -> new BusinessException(6101, "能源仪表不存在")); } @@ -90,9 +91,7 @@ public class EnergyMeterServiceImpl implements EnergyMeterService { return energyMeterRepository.findByProjectIdAndEnergyType(projectId, energyType); } - /** - * 生成电表编码:EM + yyyyMMddHHmmss - */ + /** 生成电表编码:EM + yyyyMMddHHmmss */ private String generateMeterCode() { String timestamp = LocalDateTime.now().format(CODE_FORMATTER); String meterCode = "EM" + timestamp; @@ -106,4 +105,4 @@ public class EnergyMeterServiceImpl implements EnergyMeterService { return meterCode; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionItemServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionItemServiceImpl.java index 918bed2..490a26c 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionItemServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionItemServiceImpl.java @@ -5,16 +5,13 @@ import com.ether.pms.common.ErrorCode; import com.ether.pms.mdm.entity.InspectionItem; import com.ether.pms.mdm.repository.InspectionItemRepository; import com.ether.pms.mdm.service.InspectionItemService; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - -/** - * 巡检标准项服务实现 - */ +/** 巡检标准项服务实现 */ @Service @RequiredArgsConstructor public class InspectionItemServiceImpl implements InspectionItemService { @@ -79,7 +76,8 @@ public class InspectionItemServiceImpl implements InspectionItemService { @Override public InspectionItem getItemById(UUID id) { - return inspectionItemRepository.findById(id) + return inspectionItemRepository + .findById(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "巡检标准项不存在")); } @@ -93,12 +91,14 @@ public class InspectionItemServiceImpl implements InspectionItemService { @Override public List getItemsByEquipmentType(String equipmentType) { - return inspectionItemRepository.findByEquipmentTypeAndStatus(equipmentType, InspectionItem.Status.ACTIVE); + return inspectionItemRepository.findByEquipmentTypeAndStatus( + equipmentType, InspectionItem.Status.ACTIVE); } @Override public List getItemsBySystemType(String systemType) { - return inspectionItemRepository.findBySystemTypeAndStatus(systemType, InspectionItem.Status.ACTIVE); + return inspectionItemRepository.findBySystemTypeAndStatus( + systemType, InspectionItem.Status.ACTIVE); } @Override @@ -107,7 +107,8 @@ public class InspectionItemServiceImpl implements InspectionItemService { } @Override - public List getItemsByEquipmentTypeAndSystemType(String equipmentType, String systemType) { + public List getItemsByEquipmentTypeAndSystemType( + String equipmentType, String systemType) { return inspectionItemRepository.findByEquipmentTypeAndSystemType(equipmentType, systemType); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionRecordServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionRecordServiceImpl.java index 824ff14..8a349bb 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionRecordServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionRecordServiceImpl.java @@ -5,18 +5,15 @@ import com.ether.pms.common.ErrorCode; import com.ether.pms.mdm.entity.InspectionRecord; import com.ether.pms.mdm.repository.InspectionRecordRepository; import com.ether.pms.mdm.service.InspectionRecordService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -/** - * 巡检记录服务实现 - */ +/** 巡检记录服务实现 */ @Service @RequiredArgsConstructor public class InspectionRecordServiceImpl implements InspectionRecordService { @@ -80,7 +77,8 @@ public class InspectionRecordServiceImpl implements InspectionRecordService { @Override public InspectionRecord getRecordById(UUID id) { - return inspectionRecordRepository.findById(id) + return inspectionRecordRepository + .findById(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "巡检记录不存在")); } @@ -118,8 +116,10 @@ public class InspectionRecordServiceImpl implements InspectionRecordService { } @Override - public List getRecordsByEquipmentAndDateRange(UUID equipmentId, LocalDate startDate, LocalDate endDate) { - return inspectionRecordRepository.findByEquipmentIdAndInspectionDateBetween(equipmentId, startDate, endDate); + public List getRecordsByEquipmentAndDateRange( + UUID equipmentId, LocalDate startDate, LocalDate endDate) { + return inspectionRecordRepository.findByEquipmentIdAndInspectionDateBetween( + equipmentId, startDate, endDate); } @Override @@ -132,4 +132,4 @@ public class InspectionRecordServiceImpl implements InspectionRecordService { return inspectionRecordRepository.save(record); } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionTemplateServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionTemplateServiceImpl.java index 9274818..5b60bde 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionTemplateServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/InspectionTemplateServiceImpl.java @@ -5,21 +5,21 @@ import com.ether.pms.common.ErrorCode; import com.ether.pms.mdm.entity.InspectionTemplate; import com.ether.pms.mdm.repository.InspectionTemplateRepository; import com.ether.pms.mdm.service.InspectionTemplateService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class InspectionTemplateServiceImpl implements InspectionTemplateService { private final InspectionTemplateRepository inspectionTemplateRepository; - private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final DateTimeFormatter CODE_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); @Override @Transactional @@ -95,7 +95,8 @@ public class InspectionTemplateServiceImpl implements InspectionTemplateService @Override public InspectionTemplate getTemplateById(UUID id) { - return inspectionTemplateRepository.findById(id) + return inspectionTemplateRepository + .findById(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "点检模板不存在")); } @@ -111,4 +112,4 @@ public class InspectionTemplateServiceImpl implements InspectionTemplateService return templateCode; } -} \ No newline at end of file +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectConfigServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectConfigServiceImpl.java new file mode 100644 index 0000000..286c703 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectConfigServiceImpl.java @@ -0,0 +1,119 @@ +package com.ether.pms.mdm.service.impl; + +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import com.ether.pms.mdm.dto.ProjectConfigDTO; +import com.ether.pms.mdm.entity.ProjectConfig; +import com.ether.pms.mdm.repository.ProjectConfigRepository; +import com.ether.pms.mdm.repository.ProjectRepository; +import com.ether.pms.mdm.service.ProjectConfigService; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProjectConfigServiceImpl implements ProjectConfigService { + + private final ProjectConfigRepository projectConfigRepository; + private final ProjectRepository projectRepository; + + @Override + public ProjectConfigDTO getConfig(UUID projectId) { + if (!projectRepository.existsById(projectId)) { + throw new BusinessException(ErrorCode.PROJECT_002); + } + + ProjectConfig config = + projectConfigRepository + .findByProjectId(projectId) + .orElseGet(() -> createDefaultConfig(projectId)); + + return toDTO(config); + } + + @Override + @Transactional + public ProjectConfigDTO updateConfig(UUID projectId, ProjectConfigDTO dto) { + if (!projectRepository.existsById(projectId)) { + throw new BusinessException(ErrorCode.PROJECT_002); + } + + ProjectConfig config = + projectConfigRepository + .findByProjectId(projectId) + .orElseGet( + () -> { + ProjectConfig newConfig = new ProjectConfig(); + newConfig.setProjectId(projectId); + return newConfig; + }); + + if (dto.getEnableReservation() != null) { + config.setEnableReservation(dto.getEnableReservation()); + } + if (dto.getEnableVisitor() != null) { + config.setEnableVisitor(dto.getEnableVisitor()); + } + if (dto.getEnableComplaint() != null) { + config.setEnableComplaint(dto.getEnableComplaint()); + } + if (dto.getEnablePayment() != null) { + config.setEnablePayment(dto.getEnablePayment()); + } + if (dto.getEnableAnnouncement() != null) { + config.setEnableAnnouncement(dto.getEnableAnnouncement()); + } + if (dto.getEnableSurvey() != null) { + config.setEnableSurvey(dto.getEnableSurvey()); + } + if (dto.getEnableVote() != null) { + config.setEnableVote(dto.getEnableVote()); + } + if (dto.getEnableMaintenance() != null) { + config.setEnableMaintenance(dto.getEnableMaintenance()); + } + if (dto.getEnableAsset() != null) { + config.setEnableAsset(dto.getEnableAsset()); + } + if (dto.getCustomConfig() != null) { + config.setCustomConfig(dto.getCustomConfig()); + } + + ProjectConfig saved = projectConfigRepository.save(config); + return toDTO(saved); + } + + private ProjectConfig createDefaultConfig(UUID projectId) { + ProjectConfig config = new ProjectConfig(); + config.setProjectId(projectId); + config.setEnableReservation(false); + config.setEnableVisitor(false); + config.setEnableComplaint(true); + config.setEnablePayment(false); + config.setEnableAnnouncement(true); + config.setEnableSurvey(false); + config.setEnableVote(false); + config.setEnableMaintenance(true); + config.setEnableAsset(false); + return projectConfigRepository.save(config); + } + + private ProjectConfigDTO toDTO(ProjectConfig config) { + ProjectConfigDTO dto = new ProjectConfigDTO(); + dto.setId(config.getId()); + dto.setProjectId(config.getProjectId()); + dto.setEnableReservation(config.getEnableReservation()); + dto.setEnableVisitor(config.getEnableVisitor()); + dto.setEnableComplaint(config.getEnableComplaint()); + dto.setEnablePayment(config.getEnablePayment()); + dto.setEnableAnnouncement(config.getEnableAnnouncement()); + dto.setEnableSurvey(config.getEnableSurvey()); + dto.setEnableVote(config.getEnableVote()); + dto.setEnableMaintenance(config.getEnableMaintenance()); + dto.setEnableAsset(config.getEnableAsset()); + dto.setCustomConfig(config.getCustomConfig()); + return dto; + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectMemberServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectMemberServiceImpl.java new file mode 100644 index 0000000..847e8e1 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectMemberServiceImpl.java @@ -0,0 +1,121 @@ +package com.ether.pms.mdm.service.impl; + +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.entity.UserProject; +import com.ether.pms.auth.repository.UserProjectRepository; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import com.ether.pms.mdm.dto.AddMemberRequest; +import com.ether.pms.mdm.dto.PageResponse; +import com.ether.pms.mdm.dto.ProjectMemberDTO; +import com.ether.pms.mdm.repository.ProjectRepository; +import com.ether.pms.mdm.service.ProjectMemberService; +import java.time.LocalDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProjectMemberServiceImpl implements ProjectMemberService { + + private final UserProjectRepository userProjectRepository; + private final UserRepository userRepository; + private final ProjectRepository projectRepository; + + @Override + public PageResponse getMembers(UUID projectId, Pageable pageable) { + if (!projectRepository.existsById(projectId)) { + throw new BusinessException(ErrorCode.PROJECT_002); + } + + Page userProjectPage = + userProjectRepository.findByProjectId(projectId, pageable); + + List userIds = + userProjectPage.getContent().stream() + .map(UserProject::getUserId) + .collect(Collectors.toList()); + + Map userMap; + if (!userIds.isEmpty()) { + List users = userRepository.findAllById(userIds); + userMap = users.stream().collect(Collectors.toMap(User::getId, Function.identity())); + } else { + userMap = Collections.emptyMap(); + } + + final Map finalUserMap = userMap; + + List members = + userProjectPage.getContent().stream() + .map( + up -> { + User user = finalUserMap.get(up.getUserId()); + ProjectMemberDTO dto = new ProjectMemberDTO(); + dto.setId(up.getId()); + dto.setUserId(up.getUserId()); + dto.setRoleInProject(up.getRoleInProject()); + dto.setJoinedAt(up.getJoinedAt()); + if (user != null) { + dto.setUsername(user.getUsername()); + dto.setRealName(user.getRealName()); + dto.setPhone(user.getPhone()); + dto.setAvatar(user.getAvatar()); + } + return dto; + }) + .collect(Collectors.toList()); + + return PageResponse.of( + members, + userProjectPage.getNumber(), + userProjectPage.getSize(), + userProjectPage.getTotalElements()); + } + + @Override + @Transactional + public void addMembers(UUID projectId, AddMemberRequest request) { + if (!projectRepository.existsById(projectId)) { + throw new BusinessException(ErrorCode.PROJECT_002); + } + + List userProjects = new ArrayList<>(); + + for (UUID userId : request.getUserIds()) { + if (!userRepository.existsById(userId)) { + throw new BusinessException(ErrorCode.USER_003); + } + + if (userProjectRepository.existsByUserIdAndProjectId(userId, projectId)) { + throw new BusinessException(ErrorCode.PROJECT_004); + } + + UserProject userProject = new UserProject(); + userProject.setUserId(userId); + userProject.setProjectId(projectId); + userProject.setRoleInProject(request.getRoleInProject()); + userProject.setJoinedAt(LocalDateTime.now()); + userProjects.add(userProject); + } + + userProjectRepository.saveAll(userProjects); + } + + @Override + @Transactional + public void removeMember(UUID projectId, UUID memberId) { + if (!userProjectRepository.existsById(memberId)) { + throw new BusinessException(ErrorCode.PROJECT_005); + } + + userProjectRepository.deleteById(memberId); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectServiceImpl.java new file mode 100644 index 0000000..b4a48a2 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectServiceImpl.java @@ -0,0 +1,257 @@ +package com.ether.pms.mdm.service.impl; + +import com.ether.pms.auth.repository.UserProjectRepository; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; +import com.ether.pms.mdm.dto.PageResponse; +import com.ether.pms.mdm.dto.ProjectDeleteCheckVO; +import com.ether.pms.mdm.dto.ProjectDeleteStatistics; +import com.ether.pms.mdm.dto.ProjectQueryRequest; +import com.ether.pms.mdm.dto.ProjectSelectorItem; +import com.ether.pms.mdm.entity.Project; +import com.ether.pms.mdm.repository.ProjectRepository; +import com.ether.pms.mdm.repository.SpaceNodeRepository; +import com.ether.pms.mdm.service.ProjectService; +import java.math.BigDecimal; +import java.time.Year; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProjectServiceImpl implements ProjectService { + + private final ProjectRepository projectRepository; + private final SpaceNodeRepository spaceNodeRepository; + private final UserProjectRepository userProjectRepository; + + @Override + public List findAll() { + return projectRepository.findAll(); + } + + @Override + public Project findById(UUID id) { + return projectRepository + .findById(id) + .orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_002)); + } + + @Override + public Project findByCode(String code) { + return projectRepository + .findByCode(code) + .orElseThrow(() -> new BusinessException(ErrorCode.PROJECT_002)); + } + + @Override + public PageResponse queryProjects(ProjectQueryRequest request) { + Sort sort = + Sort.by( + "DESC".equalsIgnoreCase(request.getSortDirection()) + ? Sort.Direction.DESC + : Sort.Direction.ASC, + request.getSortBy()); + Pageable pageable = PageRequest.of(request.getPage(), request.getSize(), sort); + + Page projectPage = + projectRepository.searchProjects( + request.getKeyword(), request.getStatus(), pageable); + + return PageResponse.of( + projectPage.getContent(), + projectPage.getNumber(), + projectPage.getSize(), + projectPage.getTotalElements()); + } + + @Override + public List getSelectorList() { + List projects = projectRepository.findActiveProjectsForSelector(); + return projects.stream() + .map( + p -> + new ProjectSelectorItem( + p.getId().toString(), + p.getCode(), + p.getName(), + p.getStatus())) + .collect(Collectors.toList()); + } + + @Override + public String generateCode() { + int year = Year.now().getValue(); + int sequence = 1; + + String code; + do { + code = String.format("PRJ-%d%04d", year, sequence); + sequence++; + if (sequence > 9999) { + code = + String.format( + "PRJ-%d%04d", year, ThreadLocalRandom.current().nextInt(1, 10000)); + break; + } + } while (projectRepository.existsByCode(code)); + + return code; + } + + @Override + @Transactional + public Project create(Project project) { + if (projectRepository.existsByCode(project.getCode())) { + throw new BusinessException(ErrorCode.PROJECT_001); + } + return projectRepository.save(project); + } + + @Override + @Transactional + public Project update(UUID id, Project project) { + Project existing = findById(id); + + if (project.getCode() != null && !project.getCode().equals(existing.getCode())) { + if (projectRepository.existsByCodeAndIdNot(project.getCode(), id)) { + throw new BusinessException(ErrorCode.PROJECT_001); + } + existing.setCode(project.getCode()); + } + + if (project.getName() != null) { + existing.setName(project.getName()); + } + if (project.getDescription() != null) { + existing.setDescription(project.getDescription()); + } + if (project.getAddress() != null) { + existing.setAddress(project.getAddress()); + } + if (project.getProjectType() != null) { + existing.setProjectType(project.getProjectType()); + } + if (project.getProvince() != null) { + existing.setProvince(project.getProvince()); + } + if (project.getCity() != null) { + existing.setCity(project.getCity()); + } + if (project.getDistrict() != null) { + existing.setDistrict(project.getDistrict()); + } + if (project.getLongitude() != null) { + existing.setLongitude(project.getLongitude()); + } + if (project.getLatitude() != null) { + existing.setLatitude(project.getLatitude()); + } + if (project.getStatus() != null) { + existing.setStatus(project.getStatus()); + } + if (project.getBuildingCount() != null) { + existing.setBuildingCount(project.getBuildingCount()); + } + if (project.getUnitCount() != null) { + existing.setUnitCount(project.getUnitCount()); + } + if (project.getRoomCount() != null) { + existing.setRoomCount(project.getRoomCount()); + } + if (project.getFloorCount() != null) { + existing.setFloorCount(project.getFloorCount()); + } + if (project.getLogo() != null) { + existing.setLogo(project.getLogo()); + } + if (project.getContact() != null) { + existing.setContact(project.getContact()); + } + if (project.getContactPhone() != null) { + existing.setContactPhone(project.getContactPhone()); + } + + return projectRepository.save(existing); + } + + @Override + @Transactional + public void delete(UUID id) { + projectRepository.deleteById(id); + } + + @Override + @Transactional + public void changeStatus(UUID projectId, String newStatus, String reason) { + Project project = findById(projectId); + String currentStatus = project.getStatus(); + + if (!isValidStatusTransition(currentStatus, newStatus)) { + throw new BusinessException(ErrorCode.PROJECT_003); + } + + project.setStatus(newStatus); + projectRepository.save(project); + } + + private boolean isValidStatusTransition(String currentStatus, String newStatus) { + if (currentStatus == null || newStatus == null) { + return false; + } + + if (currentStatus.equals(newStatus)) { + return true; + } + + return switch (currentStatus) { + case "ACTIVE" -> "INACTIVE".equals(newStatus) || "ARCHIVED".equals(newStatus); + case "INACTIVE" -> "ACTIVE".equals(newStatus) || "ARCHIVED".equals(newStatus); + case "ARCHIVED" -> false; + default -> false; + }; + } + + @Override + public ProjectDeleteCheckVO checkProjectDelete(UUID projectId) { + findById(projectId); + + long spaceCount = spaceNodeRepository.countByProjectIdAndIsDeletedFalse(projectId); + long memberCount = userProjectRepository.countByProjectId(projectId); + long equipmentCount = projectRepository.countEquipmentsByProjectId(projectId); + + int billCount = 0; + BigDecimal totalReceivable = BigDecimal.ZERO; + BigDecimal unpaidAmount = BigDecimal.ZERO; + + ProjectDeleteStatistics statistics = new ProjectDeleteStatistics(); + statistics.setSpaceCount((int) spaceCount); + statistics.setMemberCount((int) memberCount); + statistics.setEquipmentCount((int) equipmentCount); + statistics.setBillCount(billCount); + statistics.setTotalReceivable(totalReceivable); + statistics.setUnpaidAmount(unpaidAmount); + + ProjectDeleteCheckVO result = new ProjectDeleteCheckVO(); + result.setStatistics(statistics); + + if (unpaidAmount.compareTo(BigDecimal.ZERO) > 0) { + result.setCanDelete(false); + result.setReason( + String.format("该项目存在 %d 笔应收未收费用,共计 ¥%s 元", billCount, unpaidAmount.toString())); + } else { + result.setCanDelete(true); + } + + return result; + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectStatisticsServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectStatisticsServiceImpl.java new file mode 100644 index 0000000..989eb39 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/ProjectStatisticsServiceImpl.java @@ -0,0 +1,62 @@ +package com.ether.pms.mdm.service.impl; + +import com.ether.pms.auth.repository.ProjectStaffRepository; +import com.ether.pms.mdm.entity.ProjectStatistics; +import com.ether.pms.mdm.entity.SpaceNode; +import com.ether.pms.mdm.repository.ProjectStatisticsRepository; +import com.ether.pms.mdm.repository.SpaceNodeRepository; +import com.ether.pms.mdm.service.ProjectStatisticsService; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProjectStatisticsServiceImpl implements ProjectStatisticsService { + + private final ProjectStatisticsRepository statisticsRepository; + private final SpaceNodeRepository spaceNodeRepository; + private final ProjectStaffRepository projectStaffRepository; + + @Override + public ProjectStatistics getStatistics(UUID projectId) { + ProjectStatistics stats = + statisticsRepository + .findByProjectId(projectId) + .orElseGet( + () -> { + ProjectStatistics newStats = new ProjectStatistics(); + newStats.setProjectId(projectId); + return newStats; + }); + + stats.setMemberCount((int) projectStaffRepository.countByProjectId(projectId)); + stats.setBuildingCount( + (int) + spaceNodeRepository.countByProjectIdAndNodeTypeAndIsDeletedFalse( + projectId, SpaceNode.NodeType.BUILDING)); + stats.setRoomCount( + (int) + spaceNodeRepository.countByProjectIdAndNodeTypeAndIsDeletedFalse( + projectId, SpaceNode.NodeType.ROOM)); + + stats.setOwnerCount(0); + stats.setTenantCount(0); + + stats.setLastSyncedAt(LocalDateTime.now()); + return stats; + } + + @Override + @Transactional + public ProjectStatistics initializeStatistics(UUID projectId) { + if (statisticsRepository.existsByProjectId(projectId)) { + return getStatistics(projectId); + } + ProjectStatistics stats = new ProjectStatistics(); + stats.setProjectId(projectId); + return statisticsRepository.save(stats); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/SparePartServiceImpl.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/SparePartServiceImpl.java index 5073200..6273156 100644 --- a/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/SparePartServiceImpl.java +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/impl/SparePartServiceImpl.java @@ -8,14 +8,13 @@ import com.ether.pms.mdm.repository.SparePartCategoryRepository; import com.ether.pms.mdm.repository.SparePartRecordRepository; import com.ether.pms.mdm.repository.SparePartRepository; import com.ether.pms.mdm.service.SparePartService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -25,7 +24,8 @@ public class SparePartServiceImpl implements SparePartService { private final SparePartCategoryRepository sparePartCategoryRepository; private final SparePartRecordRepository sparePartRecordRepository; - private static final DateTimeFormatter CODE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final DateTimeFormatter CODE_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); // ==================== 分类管理 ==================== @@ -119,7 +119,8 @@ public class SparePartServiceImpl implements SparePartService { @Override public SparePart getSparePartById(UUID id) { - return sparePartRepository.findById(id) + return sparePartRepository + .findById(id) .orElseThrow(() -> new BusinessException(6101, "备件不存在")); } @@ -142,7 +143,8 @@ public class SparePartServiceImpl implements SparePartService { @Override @Transactional - public SparePartRecord inStock(UUID sparePartId, Integer quantity, UUID recordedBy, String remarks) { + public SparePartRecord inStock( + UUID sparePartId, Integer quantity, UUID recordedBy, String remarks) { if (quantity == null || quantity <= 0) { throw new BusinessException(6102, "入库数量必须大于0"); } @@ -154,12 +156,18 @@ public class SparePartServiceImpl implements SparePartService { sparePartRepository.save(sparePart); // 创建入库记录 - return createSparePartRecord(sparePartId, SparePartRecord.RecordType.IN, quantity, recordedBy, remarks); + return createSparePartRecord( + sparePartId, SparePartRecord.RecordType.IN, quantity, recordedBy, remarks); } @Override @Transactional - public SparePartRecord outStock(UUID sparePartId, Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks) { + public SparePartRecord outStock( + UUID sparePartId, + Integer quantity, + UUID relatedOrderId, + UUID recordedBy, + String remarks) { if (quantity == null || quantity <= 0) { throw new BusinessException(6103, "出库数量必须大于0"); } @@ -168,7 +176,8 @@ public class SparePartServiceImpl implements SparePartService { // 校验库存是否充足 if (sparePart.getCurrentStock() < quantity) { - throw new BusinessException(6104, "库存不足,当前库存:" + sparePart.getCurrentStock() + ",需要出库:" + quantity); + throw new BusinessException( + 6104, "库存不足,当前库存:" + sparePart.getCurrentStock() + ",需要出库:" + quantity); } // 更新库存 @@ -176,7 +185,13 @@ public class SparePartServiceImpl implements SparePartService { sparePartRepository.save(sparePart); // 创建出库记录 - return createSparePartRecordWithOrder(sparePartId, SparePartRecord.RecordType.OUT, quantity, relatedOrderId, recordedBy, remarks); + return createSparePartRecordWithOrder( + sparePartId, + SparePartRecord.RecordType.OUT, + quantity, + relatedOrderId, + recordedBy, + remarks); } @Override @@ -186,9 +201,7 @@ public class SparePartServiceImpl implements SparePartService { // ==================== 私有方法 ==================== - /** - * 生成备件编码:SP + yyyyMMddHHmmss - */ + /** 生成备件编码:SP + yyyyMMddHHmmss */ private String generateSparePartCode() { String timestamp = LocalDateTime.now().format(CODE_FORMATTER); String sparePartCode = "SP" + timestamp; @@ -203,9 +216,7 @@ public class SparePartServiceImpl implements SparePartService { return sparePartCode; } - /** - * 生成分类编码:CC + yyyyMMddHHmmss - */ + /** 生成分类编码:CC + yyyyMMddHHmmss */ private String generateCategoryCode() { String timestamp = LocalDateTime.now().format(CODE_FORMATTER); String categoryCode = "CC" + timestamp; @@ -220,11 +231,13 @@ public class SparePartServiceImpl implements SparePartService { return categoryCode; } - /** - * 创建库存记录 - */ - private SparePartRecord createSparePartRecord(UUID sparePartId, SparePartRecord.RecordType recordType, - Integer quantity, UUID recordedBy, String remarks) { + /** 创建库存记录 */ + private SparePartRecord createSparePartRecord( + UUID sparePartId, + SparePartRecord.RecordType recordType, + Integer quantity, + UUID recordedBy, + String remarks) { SparePart sparePart = getSparePartById(sparePartId); SparePartRecord record = new SparePartRecord(); @@ -240,11 +253,14 @@ public class SparePartServiceImpl implements SparePartService { return sparePartRecordRepository.save(record); } - /** - * 创建带订单信息的库存记录 - */ - private SparePartRecord createSparePartRecordWithOrder(UUID sparePartId, SparePartRecord.RecordType recordType, - Integer quantity, UUID relatedOrderId, UUID recordedBy, String remarks) { + /** 创建带订单信息的库存记录 */ + private SparePartRecord createSparePartRecordWithOrder( + UUID sparePartId, + SparePartRecord.RecordType recordType, + Integer quantity, + UUID relatedOrderId, + UUID recordedBy, + String remarks) { SparePart sparePart = getSparePartById(sparePartId); SparePartRecord record = new SparePartRecord(); @@ -261,12 +277,10 @@ public class SparePartServiceImpl implements SparePartService { return sparePartRecordRepository.save(record); } - /** - * 生成记录编码:REC + 类型 + 时间戳 - */ + /** 生成记录编码:REC + 类型 + 时间戳 */ private String generateRecordCode(SparePartRecord.RecordType recordType) { String typeCode = recordType.name(); String timestamp = LocalDateTime.now().format(CODE_FORMATTER); return "REC" + typeCode + timestamp; } -} \ No newline at end of file +} diff --git a/module-mdm/src/test/java/com/ether/pms/mdm/service/EnergyMeterServiceTest.java b/module-mdm/src/test/java/com/ether/pms/mdm/service/EnergyMeterServiceTest.java index c719cce..507edc2 100644 --- a/module-mdm/src/test/java/com/ether/pms/mdm/service/EnergyMeterServiceTest.java +++ b/module-mdm/src/test/java/com/ether/pms/mdm/service/EnergyMeterServiceTest.java @@ -1,31 +1,28 @@ package com.ether.pms.mdm.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.mdm.entity.EnergyMeter; import com.ether.pms.mdm.repository.EnergyMeterRepository; import com.ether.pms.mdm.service.impl.EnergyMeterServiceImpl; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.math.BigDecimal; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class EnergyMeterServiceTest { - @Mock - private EnergyMeterRepository energyMeterRepository; + @Mock private EnergyMeterRepository energyMeterRepository; - @InjectMocks - private EnergyMeterServiceImpl energyMeterService; + @InjectMocks private EnergyMeterServiceImpl energyMeterService; @Test void createMeter_shouldCreateMeter_withValidInput() { @@ -39,11 +36,13 @@ class EnergyMeterServiceTest { meter.setUnitPrice(new BigDecimal("0.8000")); when(energyMeterRepository.existsByMeterCode(any())).thenReturn(false); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> { - EnergyMeter m = invocation.getArgument(0); - m.setId(UUID.randomUUID()); - return m; - }); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer( + invocation -> { + EnergyMeter m = invocation.getArgument(0); + m.setId(UUID.randomUUID()); + return m; + }); EnergyMeter result = energyMeterService.createMeter(meter); @@ -64,11 +63,13 @@ class EnergyMeterServiceTest { meter.setEnergyType(EnergyMeter.EnergyType.HVAC); when(energyMeterRepository.existsByMeterCode(any())).thenReturn(false); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> { - EnergyMeter m = invocation.getArgument(0); - m.setId(UUID.randomUUID()); - return m; - }); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer( + invocation -> { + EnergyMeter m = invocation.getArgument(0); + m.setId(UUID.randomUUID()); + return m; + }); EnergyMeter result = energyMeterService.createMeter(meter); @@ -80,11 +81,13 @@ class EnergyMeterServiceTest { UUID projectId = UUID.randomUUID(); when(energyMeterRepository.existsByMeterCode(any())).thenReturn(false); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> { - EnergyMeter m = invocation.getArgument(0); - m.setId(UUID.randomUUID()); - return m; - }); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer( + invocation -> { + EnergyMeter m = invocation.getArgument(0); + m.setId(UUID.randomUUID()); + return m; + }); EnergyMeter lightingMeter = new EnergyMeter(); lightingMeter.setProjectId(projectId); @@ -116,12 +119,24 @@ class EnergyMeterServiceTest { gasMeter.setMeterName("燃气"); gasMeter.setEnergyType(EnergyMeter.EnergyType.GAS); - assertEquals(EnergyMeter.EnergyType.LIGHTING, energyMeterService.createMeter(lightingMeter).getEnergyType()); - assertEquals(EnergyMeter.EnergyType.HVAC, energyMeterService.createMeter(hvacMeter).getEnergyType()); - assertEquals(EnergyMeter.EnergyType.POWER, energyMeterService.createMeter(powerMeter).getEnergyType()); - assertEquals(EnergyMeter.EnergyType.SPECIAL, energyMeterService.createMeter(specialMeter).getEnergyType()); - assertEquals(EnergyMeter.EnergyType.WATER, energyMeterService.createMeter(waterMeter).getEnergyType()); - assertEquals(EnergyMeter.EnergyType.GAS, energyMeterService.createMeter(gasMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.LIGHTING, + energyMeterService.createMeter(lightingMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.HVAC, + energyMeterService.createMeter(hvacMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.POWER, + energyMeterService.createMeter(powerMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.SPECIAL, + energyMeterService.createMeter(specialMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.WATER, + energyMeterService.createMeter(waterMeter).getEnergyType()); + assertEquals( + EnergyMeter.EnergyType.GAS, + energyMeterService.createMeter(gasMeter).getEnergyType()); } @Test @@ -141,7 +156,8 @@ class EnergyMeterServiceTest { updatedMeter.setEnergyType(EnergyMeter.EnergyType.HVAC); when(energyMeterRepository.findById(meterId)).thenReturn(Optional.of(existingMeter)); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); EnergyMeter result = energyMeterService.updateMeter(meterId, updatedMeter); @@ -157,7 +173,8 @@ class EnergyMeterServiceTest { meter.setStatus(EnergyMeter.Status.ACTIVE); when(energyMeterRepository.findById(meterId)).thenReturn(Optional.of(meter)); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); energyMeterService.deleteMeter(meterId); @@ -210,10 +227,12 @@ class EnergyMeterServiceTest { hvacMeter.setProjectId(projectId); hvacMeter.setEnergyType(EnergyMeter.EnergyType.HVAC); - when(energyMeterRepository.findByProjectIdAndEnergyType(projectId, EnergyMeter.EnergyType.HVAC)) + when(energyMeterRepository.findByProjectIdAndEnergyType( + projectId, EnergyMeter.EnergyType.HVAC)) .thenReturn(List.of(hvacMeter)); - List results = energyMeterService.getMetersByType(projectId, EnergyMeter.EnergyType.HVAC); + List results = + energyMeterService.getMetersByType(projectId, EnergyMeter.EnergyType.HVAC); assertEquals(1, results.size()); assertEquals(EnergyMeter.EnergyType.HVAC, results.get(0).getEnergyType()); @@ -230,15 +249,17 @@ class EnergyMeterServiceTest { meter.setUnitPrice(new BigDecimal("0.8500")); when(energyMeterRepository.existsByMeterCode(any())).thenReturn(false); - when(energyMeterRepository.save(any(EnergyMeter.class))).thenAnswer(invocation -> { - EnergyMeter m = invocation.getArgument(0); - m.setId(UUID.randomUUID()); - return m; - }); + when(energyMeterRepository.save(any(EnergyMeter.class))) + .thenAnswer( + invocation -> { + EnergyMeter m = invocation.getArgument(0); + m.setId(UUID.randomUUID()); + return m; + }); EnergyMeter result = energyMeterService.createMeter(meter); assertEquals(new BigDecimal("100.00"), result.getRatedCapacity()); assertEquals(new BigDecimal("0.8500"), result.getUnitPrice()); } -} \ No newline at end of file +} diff --git a/module-mdm/src/test/java/com/ether/pms/mdm/service/InspectionTemplateServiceTest.java b/module-mdm/src/test/java/com/ether/pms/mdm/service/InspectionTemplateServiceTest.java index ece623a..57d7694 100644 --- a/module-mdm/src/test/java/com/ether/pms/mdm/service/InspectionTemplateServiceTest.java +++ b/module-mdm/src/test/java/com/ether/pms/mdm/service/InspectionTemplateServiceTest.java @@ -1,30 +1,27 @@ package com.ether.pms.mdm.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.mdm.entity.InspectionTemplate; import com.ether.pms.mdm.repository.InspectionTemplateRepository; import com.ether.pms.mdm.service.impl.InspectionTemplateServiceImpl; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class InspectionTemplateServiceTest { - @Mock - private InspectionTemplateRepository inspectionTemplateRepository; + @Mock private InspectionTemplateRepository inspectionTemplateRepository; - @InjectMocks - private InspectionTemplateServiceImpl inspectionTemplateService; + @InjectMocks private InspectionTemplateServiceImpl inspectionTemplateService; @Test void createTemplate_shouldCreateTemplate_withValidInput() { @@ -37,11 +34,13 @@ class InspectionTemplateServiceTest { template.setEstimatedDuration(30); when(inspectionTemplateRepository.existsByTemplateCode(any())).thenReturn(false); - when(inspectionTemplateRepository.save(any(InspectionTemplate.class))).thenAnswer(invocation -> { - InspectionTemplate t = invocation.getArgument(0); - t.setId(UUID.randomUUID()); - return t; - }); + when(inspectionTemplateRepository.save(any(InspectionTemplate.class))) + .thenAnswer( + invocation -> { + InspectionTemplate t = invocation.getArgument(0); + t.setId(UUID.randomUUID()); + return t; + }); InspectionTemplate result = inspectionTemplateService.createTemplate(template); @@ -62,11 +61,13 @@ class InspectionTemplateServiceTest { template.setEquipmentType("中央空调"); when(inspectionTemplateRepository.existsByTemplateCode(any())).thenReturn(false); - when(inspectionTemplateRepository.save(any(InspectionTemplate.class))).thenAnswer(invocation -> { - InspectionTemplate t = invocation.getArgument(0); - t.setId(UUID.randomUUID()); - return t; - }); + when(inspectionTemplateRepository.save(any(InspectionTemplate.class))) + .thenAnswer( + invocation -> { + InspectionTemplate t = invocation.getArgument(0); + t.setId(UUID.randomUUID()); + return t; + }); InspectionTemplate result = inspectionTemplateService.createTemplate(template); @@ -89,10 +90,13 @@ class InspectionTemplateServiceTest { updatedTemplate.setTemplateName("更新后的模板名称"); updatedTemplate.setEstimatedDuration(60); - when(inspectionTemplateRepository.findById(templateId)).thenReturn(Optional.of(existingTemplate)); - when(inspectionTemplateRepository.save(any(InspectionTemplate.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(inspectionTemplateRepository.findById(templateId)) + .thenReturn(Optional.of(existingTemplate)); + when(inspectionTemplateRepository.save(any(InspectionTemplate.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - InspectionTemplate result = inspectionTemplateService.updateTemplate(templateId, updatedTemplate); + InspectionTemplate result = + inspectionTemplateService.updateTemplate(templateId, updatedTemplate); assertEquals("更新后的模板名称", result.getTemplateName()); assertEquals(60, result.getEstimatedDuration()); @@ -113,13 +117,16 @@ class InspectionTemplateServiceTest { originalTemplate.setVersion(1); originalTemplate.setCreatedBy("admin"); - when(inspectionTemplateRepository.findById(originalId)).thenReturn(Optional.of(originalTemplate)); + when(inspectionTemplateRepository.findById(originalId)) + .thenReturn(Optional.of(originalTemplate)); when(inspectionTemplateRepository.existsByTemplateCode(any())).thenReturn(false); - when(inspectionTemplateRepository.save(any(InspectionTemplate.class))).thenAnswer(invocation -> { - InspectionTemplate t = invocation.getArgument(0); - t.setId(UUID.randomUUID()); - return t; - }); + when(inspectionTemplateRepository.save(any(InspectionTemplate.class))) + .thenAnswer( + invocation -> { + InspectionTemplate t = invocation.getArgument(0); + t.setId(UUID.randomUUID()); + return t; + }); InspectionTemplate result = inspectionTemplateService.copyTemplate(originalId, "复制模板"); @@ -161,7 +168,8 @@ class InspectionTemplateServiceTest { when(inspectionTemplateRepository.findByProjectId(projectId)) .thenReturn(List.of(template1, template2)); - List results = inspectionTemplateService.getTemplatesByProject(projectId); + List results = + inspectionTemplateService.getTemplatesByProject(projectId); assertEquals(2, results.size()); } @@ -192,15 +200,17 @@ class InspectionTemplateServiceTest { template.setInspectionItems("1. 检查灭火器压力\n2. 检查消防泵\n3. 测试报警系统"); when(inspectionTemplateRepository.existsByTemplateCode(any())).thenReturn(false); - when(inspectionTemplateRepository.save(any(InspectionTemplate.class))).thenAnswer(invocation -> { - InspectionTemplate t = invocation.getArgument(0); - t.setId(UUID.randomUUID()); - return t; - }); + when(inspectionTemplateRepository.save(any(InspectionTemplate.class))) + .thenAnswer( + invocation -> { + InspectionTemplate t = invocation.getArgument(0); + t.setId(UUID.randomUUID()); + return t; + }); InspectionTemplate result = inspectionTemplateService.createTemplate(template); assertNotNull(result.getInspectionItems()); assertTrue(result.getInspectionItems().contains("灭火器")); } -} \ No newline at end of file +} diff --git a/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectMemberServiceTest.java b/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectMemberServiceTest.java index e6d7c8c..096e845 100644 --- a/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectMemberServiceTest.java +++ b/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectMemberServiceTest.java @@ -1,20 +1,27 @@ package com.ether.pms.mdm.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + import com.ether.pms.auth.entity.User; import com.ether.pms.auth.entity.UserProject; import com.ether.pms.auth.repository.UserProjectRepository; import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; import com.ether.pms.mdm.dto.AddMemberRequest; import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.mdm.dto.ProjectMemberDTO; import com.ether.pms.mdm.entity.Project; import com.ether.pms.mdm.repository.ProjectRepository; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; +import com.ether.pms.mdm.service.impl.ProjectMemberServiceImpl; +import java.time.LocalDateTime; +import java.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; @@ -22,31 +29,16 @@ import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import java.time.LocalDateTime; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -/** - * ProjectMemberService 测试类 - TDD方式开发 - */ @ExtendWith(MockitoExtension.class) class ProjectMemberServiceTest { - @Mock - private UserProjectRepository userProjectRepository; + @Mock private UserProjectRepository userProjectRepository; - @Mock - private UserRepository userRepository; + @Mock private UserRepository userRepository; - @Mock - private ProjectRepository projectRepository; + @Mock private ProjectRepository projectRepository; - @InjectMocks - private ProjectMemberService projectMemberService; + private ProjectMemberServiceImpl projectMemberService; private UUID testProjectId; private UUID testUserId; @@ -56,6 +48,9 @@ class ProjectMemberServiceTest { @BeforeEach void setUp() { + projectMemberService = + new ProjectMemberServiceImpl( + userProjectRepository, userRepository, projectRepository); testProjectId = UUID.randomUUID(); testUserId = UUID.randomUUID(); @@ -80,13 +75,8 @@ class ProjectMemberServiceTest { testUserProject.setJoinedAt(LocalDateTime.now()); } - // ======================================== - // PM-003: 成员管理测试 - // ======================================== - @Test void testGetMembers_shouldReturnPagedMembers() { - // Given List userProjects = new ArrayList<>(); userProjects.add(testUserProject); Page userProjectPage = new PageImpl<>(userProjects, PageRequest.of(0, 20), 1); @@ -96,10 +86,9 @@ class ProjectMemberServiceTest { .thenReturn(userProjectPage); when(userRepository.findAllById(any())).thenReturn(List.of(testUser)); - // When - PageResponse result = projectMemberService.getMembers(testProjectId, PageRequest.of(0, 20)); + PageResponse result = + projectMemberService.getMembers(testProjectId, PageRequest.of(0, 20)); - // Then assertNotNull(result); assertEquals(1, result.getTotalElements()); assertEquals(1, result.getContent().size()); @@ -109,56 +98,56 @@ class ProjectMemberServiceTest { @Test void testGetMembers_projectNotFound_shouldThrowException() { - // Given when(projectRepository.existsById(testProjectId)).thenReturn(false); - // When & Then - BusinessException exception = assertThrows(BusinessException.class, () -> { - projectMemberService.getMembers(testProjectId, PageRequest.of(0, 20)); - }); + BusinessException exception = + assertThrows( + BusinessException.class, + () -> { + projectMemberService.getMembers(testProjectId, PageRequest.of(0, 20)); + }); assertEquals(ErrorCode.PROJECT_002.getCode(), exception.getCode()); } @Test void testAddMembers_shouldAddMembersSuccessfully() { - // Given AddMemberRequest request = new AddMemberRequest(); request.setUserIds(List.of(testUserId)); request.setRoleInProject("member"); when(projectRepository.existsById(testProjectId)).thenReturn(true); when(userRepository.existsById(testUserId)).thenReturn(true); - when(userProjectRepository.existsByUserIdAndProjectId(testUserId, testProjectId)).thenReturn(false); + when(userProjectRepository.existsByUserIdAndProjectId(testUserId, testProjectId)) + .thenReturn(false); when(userProjectRepository.saveAll(any())).thenReturn(List.of(testUserProject)); - // When projectMemberService.addMembers(testProjectId, request); - // Then verify(userProjectRepository).saveAll(any()); } @Test void testAddMembers_memberAlreadyExists_shouldThrowException() { - // Given AddMemberRequest request = new AddMemberRequest(); request.setUserIds(List.of(testUserId)); request.setRoleInProject("member"); when(projectRepository.existsById(testProjectId)).thenReturn(true); when(userRepository.existsById(testUserId)).thenReturn(true); - when(userProjectRepository.existsByUserIdAndProjectId(testUserId, testProjectId)).thenReturn(true); + when(userProjectRepository.existsByUserIdAndProjectId(testUserId, testProjectId)) + .thenReturn(true); - // When & Then - BusinessException exception = assertThrows(BusinessException.class, () -> { - projectMemberService.addMembers(testProjectId, request); - }); + BusinessException exception = + assertThrows( + BusinessException.class, + () -> { + projectMemberService.addMembers(testProjectId, request); + }); assertEquals(ErrorCode.PROJECT_004.getCode(), exception.getCode()); } @Test void testAddMembers_userNotFound_shouldThrowException() { - // Given AddMemberRequest request = new AddMemberRequest(); request.setUserIds(List.of(testUserId)); request.setRoleInProject("member"); @@ -166,38 +155,38 @@ class ProjectMemberServiceTest { when(projectRepository.existsById(testProjectId)).thenReturn(true); when(userRepository.existsById(testUserId)).thenReturn(false); - // When & Then - BusinessException exception = assertThrows(BusinessException.class, () -> { - projectMemberService.addMembers(testProjectId, request); - }); + BusinessException exception = + assertThrows( + BusinessException.class, + () -> { + projectMemberService.addMembers(testProjectId, request); + }); assertEquals(ErrorCode.USER_003.getCode(), exception.getCode()); } @Test void testRemoveMember_shouldRemoveMemberSuccessfully() { - // Given UUID memberRelationId = UUID.randomUUID(); when(userProjectRepository.existsById(memberRelationId)).thenReturn(true); doNothing().when(userProjectRepository).deleteById(memberRelationId); - // When projectMemberService.removeMember(testProjectId, memberRelationId); - // Then verify(userProjectRepository).deleteById(memberRelationId); } @Test void testRemoveMember_memberNotFound_shouldThrowException() { - // Given UUID memberRelationId = UUID.randomUUID(); when(userProjectRepository.existsById(memberRelationId)).thenReturn(false); - // When & Then - BusinessException exception = assertThrows(BusinessException.class, () -> { - projectMemberService.removeMember(testProjectId, memberRelationId); - }); + BusinessException exception = + assertThrows( + BusinessException.class, + () -> { + projectMemberService.removeMember(testProjectId, memberRelationId); + }); assertEquals(ErrorCode.PROJECT_005.getCode(), exception.getCode()); } } diff --git a/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectServiceTest.java b/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectServiceTest.java index c75e93c..cdff780 100644 --- a/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectServiceTest.java +++ b/module-mdm/src/test/java/com/ether/pms/mdm/service/ProjectServiceTest.java @@ -1,47 +1,50 @@ package com.ether.pms.mdm.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import com.ether.pms.auth.repository.UserProjectRepository; import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.mdm.dto.ProjectQueryRequest; import com.ether.pms.mdm.entity.Project; import com.ether.pms.mdm.repository.ProjectRepository; +import com.ether.pms.mdm.repository.SpaceNodeRepository; +import com.ether.pms.mdm.service.impl.ProjectServiceImpl; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -/** - * ProjectService 测试类 - TDD方式开发 - */ @ExtendWith(MockitoExtension.class) class ProjectServiceTest { - @Mock - private ProjectRepository projectRepository; + @Mock private ProjectRepository projectRepository; - @InjectMocks - private ProjectService projectService; + @Mock private SpaceNodeRepository spaceNodeRepository; + + @Mock private UserProjectRepository userProjectRepository; + + private ProjectServiceImpl projectService; private Project testProject; private UUID testProjectId; @BeforeEach void setUp() { + projectService = + new ProjectServiceImpl( + projectRepository, spaceNodeRepository, userProjectRepository); testProjectId = UUID.randomUUID(); testProject = new Project(); testProject.setId(testProjectId); @@ -50,13 +53,8 @@ class ProjectServiceTest { testProject.setStatus("ACTIVE"); } - // ======================================== - // PM-001: 分页查询测试 - // ======================================== - @Test void testQueryProjects_withKeyword_shouldReturnFilteredResults() { - // Given ProjectQueryRequest request = new ProjectQueryRequest(); request.setKeyword("测试"); request.setPage(0); @@ -69,10 +67,8 @@ class ProjectServiceTest { when(projectRepository.searchProjects(eq("测试"), eq(null), any(Pageable.class))) .thenReturn(projectPage); - // When PageResponse result = projectService.queryProjects(request); - // Then assertNotNull(result); assertEquals(1, result.getTotalElements()); assertEquals(1, result.getContent().size()); @@ -82,7 +78,6 @@ class ProjectServiceTest { @Test void testQueryProjects_withStatusFilter_shouldReturnFilteredResults() { - // Given ProjectQueryRequest request = new ProjectQueryRequest(); request.setStatus("ACTIVE"); request.setPage(0); @@ -95,10 +90,8 @@ class ProjectServiceTest { when(projectRepository.searchProjects(eq(null), eq("ACTIVE"), any(Pageable.class))) .thenReturn(projectPage); - // When PageResponse result = projectService.queryProjects(request); - // Then assertNotNull(result); assertEquals(1, result.getTotalElements()); assertEquals("ACTIVE", result.getContent().get(0).getStatus()); @@ -107,7 +100,6 @@ class ProjectServiceTest { @Test void testQueryProjects_withPagination_shouldReturnPagedResults() { - // Given ProjectQueryRequest request = new ProjectQueryRequest(); request.setPage(1); request.setSize(10); @@ -120,10 +112,8 @@ class ProjectServiceTest { when(projectRepository.searchProjects(eq(null), eq(null), any(Pageable.class))) .thenReturn(projectPage); - // When PageResponse result = projectService.queryProjects(request); - // Then assertNotNull(result); assertEquals(1, result.getPage()); assertEquals(10, result.getSize()); @@ -135,7 +125,6 @@ class ProjectServiceTest { @Test void testQueryProjects_emptyResult_shouldReturnEmptyPage() { - // Given ProjectQueryRequest request = new ProjectQueryRequest(); request.setKeyword("不存在"); @@ -143,22 +132,15 @@ class ProjectServiceTest { when(projectRepository.searchProjects(eq("不存在"), eq(null), any(Pageable.class))) .thenReturn(emptyPage); - // When PageResponse result = projectService.queryProjects(request); - // Then assertNotNull(result); assertTrue(result.getContent().isEmpty()); assertEquals(0, result.getTotalElements()); } - // ======================================== - // PM-010: 选择器列表测试 - // ======================================== - @Test void testGetSelectorList_shouldReturnActiveProjects() { - // Given List projects = new ArrayList<>(); projects.add(testProject); @@ -171,28 +153,19 @@ class ProjectServiceTest { when(projectRepository.findActiveProjectsForSelector()).thenReturn(projects); - // When var result = projectService.getSelectorList(); - // Then assertNotNull(result); assertEquals(2, result.size()); verify(projectRepository).findActiveProjectsForSelector(); } - // ======================================== - // PM-005: 编码生成测试 - // ======================================== - @Test void testGenerateCode_shouldReturnUniqueCode() { - // Given when(projectRepository.existsByCode(anyString())).thenReturn(false); - // When String code = projectService.generateCode(); - // Then assertNotNull(code); assertTrue(code.startsWith("PRJ-")); verify(projectRepository).existsByCode(code); @@ -200,17 +173,13 @@ class ProjectServiceTest { @Test void testGenerateCode_shouldRetryIfCodeExists() { - // Given - 第一个编码存在,第二个编码不存在 when(projectRepository.existsByCode("PRJ-20260001")).thenReturn(true); when(projectRepository.existsByCode("PRJ-20260002")).thenReturn(false); - // When String code = projectService.generateCode(); - // Then assertNotNull(code); assertTrue(code.startsWith("PRJ-")); - // 验证最终返回的编码不存在 verify(projectRepository).existsByCode(code); } } diff --git a/module-mdm/src/test/java/com/ether/pms/mdm/service/SparePartServiceTest.java b/module-mdm/src/test/java/com/ether/pms/mdm/service/SparePartServiceTest.java index 535013c..22bba69 100644 --- a/module-mdm/src/test/java/com/ether/pms/mdm/service/SparePartServiceTest.java +++ b/module-mdm/src/test/java/com/ether/pms/mdm/service/SparePartServiceTest.java @@ -1,5 +1,9 @@ package com.ether.pms.mdm.service; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.mdm.entity.SparePart; import com.ether.pms.mdm.entity.SparePartCategory; import com.ether.pms.mdm.entity.SparePartRecord; @@ -7,35 +11,26 @@ import com.ether.pms.mdm.repository.SparePartCategoryRepository; import com.ether.pms.mdm.repository.SparePartRecordRepository; import com.ether.pms.mdm.repository.SparePartRepository; import com.ether.pms.mdm.service.impl.SparePartServiceImpl; +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.math.BigDecimal; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class SparePartServiceTest { - @Mock - private SparePartRepository sparePartRepository; + @Mock private SparePartRepository sparePartRepository; - @Mock - private SparePartCategoryRepository sparePartCategoryRepository; + @Mock private SparePartCategoryRepository sparePartCategoryRepository; - @Mock - private SparePartRecordRepository sparePartRecordRepository; + @Mock private SparePartRecordRepository sparePartRecordRepository; - @InjectMocks - private SparePartServiceImpl sparePartService; + @InjectMocks private SparePartServiceImpl sparePartService; @Test void createSparePart_shouldCreateSparePart_withValidInput() { @@ -49,11 +44,13 @@ class SparePartServiceTest { sparePart.setUnitPrice(new BigDecimal("299.00")); when(sparePartRepository.existsBySparePartCode(any())).thenReturn(false); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> { - SparePart sp = invocation.getArgument(0); - sp.setId(UUID.randomUUID()); - return sp; - }); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer( + invocation -> { + SparePart sp = invocation.getArgument(0); + sp.setId(UUID.randomUUID()); + return sp; + }); SparePart result = sparePartService.createSparePart(sparePart); @@ -74,11 +71,13 @@ class SparePartServiceTest { sparePart.setUnit("盒"); when(sparePartRepository.existsBySparePartCode(any())).thenReturn(false); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> { - SparePart sp = invocation.getArgument(0); - sp.setId(UUID.randomUUID()); - return sp; - }); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer( + invocation -> { + SparePart sp = invocation.getArgument(0); + sp.setId(UUID.randomUUID()); + return sp; + }); SparePart result = sparePartService.createSparePart(sparePart); @@ -101,7 +100,8 @@ class SparePartServiceTest { updatedPart.setUnitPrice(new BigDecimal("399.00")); when(sparePartRepository.findById(sparePartId)).thenReturn(Optional.of(existingPart)); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); SparePart result = sparePartService.updateSparePart(sparePartId, updatedPart); @@ -117,7 +117,8 @@ class SparePartServiceTest { sparePart.setStatus(SparePart.Status.ACTIVE); when(sparePartRepository.findById(sparePartId)).thenReturn(Optional.of(sparePart)); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); sparePartService.deleteSparePart(sparePartId); @@ -172,12 +173,15 @@ class SparePartServiceTest { sparePart.setSparePartCode("SP-001"); when(sparePartRepository.findById(sparePartId)).thenReturn(Optional.of(sparePart)); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(sparePartRecordRepository.save(any(SparePartRecord.class))).thenAnswer(invocation -> { - SparePartRecord record = invocation.getArgument(0); - record.setId(UUID.randomUUID()); - return record; - }); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + when(sparePartRecordRepository.save(any(SparePartRecord.class))) + .thenAnswer( + invocation -> { + SparePartRecord record = invocation.getArgument(0); + record.setId(UUID.randomUUID()); + return record; + }); SparePartRecord result = sparePartService.inStock(sparePartId, 5, recordedBy, "新批次入库"); @@ -198,14 +202,18 @@ class SparePartServiceTest { sparePart.setSparePartCode("SP-001"); when(sparePartRepository.findById(sparePartId)).thenReturn(Optional.of(sparePart)); - when(sparePartRepository.save(any(SparePart.class))).thenAnswer(invocation -> invocation.getArgument(0)); - when(sparePartRecordRepository.save(any(SparePartRecord.class))).thenAnswer(invocation -> { - SparePartRecord record = invocation.getArgument(0); - record.setId(UUID.randomUUID()); - return record; - }); + when(sparePartRepository.save(any(SparePart.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + when(sparePartRecordRepository.save(any(SparePartRecord.class))) + .thenAnswer( + invocation -> { + SparePartRecord record = invocation.getArgument(0); + record.setId(UUID.randomUUID()); + return record; + }); - SparePartRecord result = sparePartService.outStock(sparePartId, 3, orderId, recordedBy, "维修使用"); + SparePartRecord result = + sparePartService.outStock(sparePartId, 3, orderId, recordedBy, "维修使用"); assertEquals(7, sparePart.getCurrentStock()); assertEquals(SparePartRecord.RecordType.OUT, result.getRecordType()); @@ -223,7 +231,9 @@ class SparePartServiceTest { when(sparePartRepository.findById(sparePartId)).thenReturn(Optional.of(sparePart)); - assertThrows(Exception.class, () -> sparePartService.outStock(sparePartId, 5, null, recordedBy, "测试")); + assertThrows( + Exception.class, + () -> sparePartService.outStock(sparePartId, 5, null, recordedBy, "测试")); } @Test @@ -235,8 +245,7 @@ class SparePartServiceTest { lowStockPart.setCurrentStock(3); lowStockPart.setSafeStock(10); - when(sparePartRepository.findLowStockParts(projectId)) - .thenReturn(List.of(lowStockPart)); + when(sparePartRepository.findLowStockParts(projectId)).thenReturn(List.of(lowStockPart)); List results = sparePartService.getLowStockParts(projectId); @@ -250,11 +259,13 @@ class SparePartServiceTest { category.setCategoryName("电气配件"); when(sparePartCategoryRepository.findByCategoryCode(any())).thenReturn(Optional.empty()); - when(sparePartCategoryRepository.save(any(SparePartCategory.class))).thenAnswer(invocation -> { - SparePartCategory c = invocation.getArgument(0); - c.setId(UUID.randomUUID()); - return c; - }); + when(sparePartCategoryRepository.save(any(SparePartCategory.class))) + .thenAnswer( + invocation -> { + SparePartCategory c = invocation.getArgument(0); + c.setId(UUID.randomUUID()); + return c; + }); SparePartCategory result = sparePartService.createCategory(category); @@ -262,4 +273,4 @@ class SparePartServiceTest { assertNotNull(result.getId()); assertTrue(result.getCategoryCode().startsWith("CC")); } -} \ No newline at end of file +} diff --git a/module-wo/pom.xml b/module-wo/pom.xml index fefc1df..bbf88b2 100644 --- a/module-wo/pom.xml +++ b/module-wo/pom.xml @@ -71,5 +71,11 @@ spring-boot-starter-test test + + + com.h2database + h2 + test + diff --git a/module-wo/src/main/java/com/ether/pms/ops/controller/MaintenanceTaskController.java b/module-wo/src/main/java/com/ether/pms/ops/controller/MaintenanceTaskController.java index cad9dc7..d9ec753 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/controller/MaintenanceTaskController.java +++ b/module-wo/src/main/java/com/ether/pms/ops/controller/MaintenanceTaskController.java @@ -5,19 +5,16 @@ import com.ether.pms.ops.dto.MaintenanceTaskStatsDTO; import com.ether.pms.ops.entity.MaintenanceTask; import com.ether.pms.ops.service.MaintenanceTaskService; import jakarta.validation.Valid; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.List; -import java.util.UUID; - -/** - * 维保工单控制器 - */ +/** 维保工单控制器 */ @RestController @RequestMapping("/api/ops/maintenance-tasks") @RequiredArgsConstructor @@ -64,7 +61,8 @@ public class MaintenanceTaskController { } @PutMapping("/{id}") - public ApiResponse updateTask(@PathVariable UUID id, @Valid @RequestBody MaintenanceTask task) { + public ApiResponse updateTask( + @PathVariable UUID id, @Valid @RequestBody MaintenanceTask task) { return ApiResponse.success(maintenanceTaskService.updateTask(id, task)); } @@ -75,8 +73,11 @@ public class MaintenanceTaskController { } @PostMapping("/{id}/assign") - public ApiResponse assignTask(@PathVariable UUID id, @Valid @RequestBody AssignRequest request) { - return ApiResponse.success(maintenanceTaskService.assignTask(id, request.getAssignedTo(), request.getAssignedDate())); + public ApiResponse assignTask( + @PathVariable UUID id, @Valid @RequestBody AssignRequest request) { + return ApiResponse.success( + maintenanceTaskService.assignTask( + id, request.getAssignedTo(), request.getAssignedDate())); } @PostMapping("/{id}/start") @@ -85,20 +86,29 @@ public class MaintenanceTaskController { } @PostMapping("/{id}/complete") - public ApiResponse completeTask(@PathVariable UUID id, @Valid @RequestBody CompleteRequest request) { - return ApiResponse.success(maintenanceTaskService.completeTask( - id, request.getResult(), request.getActualHours(), request.getCost(), request.getCompletedBy())); + public ApiResponse completeTask( + @PathVariable UUID id, @Valid @RequestBody CompleteRequest request) { + return ApiResponse.success( + maintenanceTaskService.completeTask( + id, + request.getResult(), + request.getActualHours(), + request.getCost(), + request.getCompletedBy())); } @PostMapping("/{id}/complete-details") - public ApiResponse completeTaskWithDetails(@PathVariable UUID id, @Valid @RequestBody MaintenanceTask taskData) { + public ApiResponse completeTaskWithDetails( + @PathVariable UUID id, @Valid @RequestBody MaintenanceTask taskData) { return ApiResponse.success(maintenanceTaskService.completeTaskWithDetails(id, taskData)); } @PostMapping("/{id}/verify") - public ApiResponse verifyTask(@PathVariable UUID id, @Valid @RequestBody VerifyRequest request) { - return ApiResponse.success(maintenanceTaskService.verifyTask( - id, request.getVerifiedBy(), request.getRemark(), request.getRating())); + public ApiResponse verifyTask( + @PathVariable UUID id, @Valid @RequestBody VerifyRequest request) { + return ApiResponse.success( + maintenanceTaskService.verifyTask( + id, request.getVerifiedBy(), request.getRemark(), request.getRating())); } @PostMapping("/{id}/cancel") @@ -107,7 +117,8 @@ public class MaintenanceTaskController { } @PostMapping("/{id}/rate") - public ApiResponse rateTask(@PathVariable UUID id, @RequestParam Integer rating) { + public ApiResponse rateTask( + @PathVariable UUID id, @RequestParam Integer rating) { return ApiResponse.success(maintenanceTaskService.rateTask(id, rating)); } diff --git a/module-wo/src/main/java/com/ether/pms/ops/controller/WorkOrderController.java b/module-wo/src/main/java/com/ether/pms/ops/controller/WorkOrderController.java index 83e2b56..b37135d 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/controller/WorkOrderController.java +++ b/module-wo/src/main/java/com/ether/pms/ops/controller/WorkOrderController.java @@ -3,20 +3,25 @@ package com.ether.pms.ops.controller; import com.ether.pms.common.ApiResponse; import com.ether.pms.common.util.BatchOperationValidator; import com.ether.pms.mdm.dto.PageResponse; +import com.ether.pms.ops.dto.ResumeRequest; +import com.ether.pms.ops.dto.ReturnRequest; +import com.ether.pms.ops.dto.SuspendRequest; import com.ether.pms.ops.dto.WorkOrderStatsDTO; import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.entity.WorkOrderItem; +import com.ether.pms.ops.entity.WorkOrderStatusHistory; import com.ether.pms.ops.service.WorkOrderService; import jakarta.validation.Valid; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.UUID; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/wo/work-orders") @@ -43,8 +48,37 @@ public class WorkOrderController { @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { - return ApiResponse.success(workOrderService.queryWorkOrders( - projectId, equipmentId, source, type, status, priority, assignedTo, keyword, page, size)); + return ApiResponse.success( + workOrderService.queryWorkOrders( + projectId, + equipmentId, + source, + type, + status, + priority, + assignedTo, + keyword, + page, + size)); + } + + @GetMapping("/all") + public ApiResponse> listAll( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + return ApiResponse.success(workOrderService.getAllWorkOrders(pageable)); + } + + @GetMapping("/by-project") + public ApiResponse> getWorkOrdersByProject( + @RequestParam UUID projectId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(required = false) WorkOrder.Status status, + @RequestParam(required = false) WorkOrder.Priority priority) { + Pageable pageable = PageRequest.of(page, size); + return ApiResponse.success(workOrderService.getWorkOrdersByProject(projectId, pageable)); } @GetMapping("/{id}") @@ -53,7 +87,8 @@ public class WorkOrderController { } @PutMapping("/{id}") - public ApiResponse update(@PathVariable UUID id, @Valid @RequestBody WorkOrder workOrder) { + public ApiResponse update( + @PathVariable UUID id, @Valid @RequestBody WorkOrder workOrder) { return ApiResponse.success(workOrderService.updateWorkOrder(id, workOrder)); } @@ -64,8 +99,14 @@ public class WorkOrderController { } @PostMapping("/{id}/assign") - public ApiResponse assign(@PathVariable UUID id, @Valid @RequestBody AssignRequest request) { - return ApiResponse.success(workOrderService.assignWorkOrder(id, request.getAssignedTo(), request.getAssignedVendor(), request.getAssignedDate())); + public ApiResponse assign( + @PathVariable UUID id, @Valid @RequestBody AssignRequest request) { + return ApiResponse.success( + workOrderService.assignWorkOrder( + id, + request.getAssignedTo(), + request.getAssignedVendor(), + request.getAssignedDate())); } @PostMapping("/{id}/start") @@ -74,13 +115,17 @@ public class WorkOrderController { } @PostMapping("/{id}/complete") - public ApiResponse complete(@PathVariable UUID id, @Valid @RequestBody WorkOrder data) { + public ApiResponse complete( + @PathVariable UUID id, @Valid @RequestBody WorkOrder data) { return ApiResponse.success(workOrderService.completeWorkOrder(id, data)); } @PostMapping("/{id}/verify") - public ApiResponse verify(@PathVariable UUID id, @Valid @RequestBody VerifyRequest request) { - return ApiResponse.success(workOrderService.verifyWorkOrder(id, request.getVerifiedBy(), request.getRemark(), request.getRating())); + public ApiResponse verify( + @PathVariable UUID id, @Valid @RequestBody VerifyRequest request) { + return ApiResponse.success( + workOrderService.verifyWorkOrder( + id, request.getVerifiedBy(), request.getRemark(), request.getRating())); } @PostMapping("/{id}/cancel") @@ -89,18 +134,38 @@ public class WorkOrderController { } @PostMapping("/{id}/suspend") - public ApiResponse suspend(@PathVariable UUID id) { - return ApiResponse.success(workOrderService.suspendWorkOrder(id)); + public ApiResponse suspend( + @PathVariable UUID id, @Valid @RequestBody SuspendRequest request) { + return ApiResponse.success( + workOrderService.suspendWorkOrder( + id, + request.getReason(), + request.getOperatorId(), + request.getOperatorName())); } @PostMapping("/{id}/resume") - public ApiResponse resume(@PathVariable UUID id) { - return ApiResponse.success(workOrderService.resumeWorkOrder(id)); + public ApiResponse resume( + @PathVariable UUID id, @RequestBody ResumeRequest request) { + return ApiResponse.success( + workOrderService.resumeWorkOrder( + id, request.getOperatorId(), request.getOperatorName())); } @PostMapping("/{id}/return") - public ApiResponse returnOrder(@PathVariable UUID id) { - return ApiResponse.success(workOrderService.returnWorkOrder(id)); + public ApiResponse returnOrder( + @PathVariable UUID id, @Valid @RequestBody ReturnRequest request) { + return ApiResponse.success( + workOrderService.returnWorkOrder( + id, + request.getReason(), + request.getOperatorId(), + request.getOperatorName())); + } + + @GetMapping("/{id}/history") + public ApiResponse> getStatusHistory(@PathVariable UUID id) { + return ApiResponse.success(workOrderService.getStatusHistory(id)); } @GetMapping("/stats") @@ -114,7 +179,8 @@ public class WorkOrderController { } @PostMapping("/{id}/items") - public ApiResponse addItems(@PathVariable UUID id, @Valid @RequestBody List items) { + public ApiResponse addItems( + @PathVariable UUID id, @Valid @RequestBody List items) { BatchOperationValidator.validateUpdateSize(items.size()); return ApiResponse.success(workOrderService.addWorkOrderItems(id, items)); } @@ -132,4 +198,4 @@ public class WorkOrderController { private String remark; private Integer rating; } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/dto/MaintenanceTaskStatsDTO.java b/module-wo/src/main/java/com/ether/pms/ops/dto/MaintenanceTaskStatsDTO.java index 558d2dd..fd6871f 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/dto/MaintenanceTaskStatsDTO.java +++ b/module-wo/src/main/java/com/ether/pms/ops/dto/MaintenanceTaskStatsDTO.java @@ -1,14 +1,11 @@ package com.ether.pms.ops.dto; -import lombok.Builder; -import lombok.Data; - import java.math.BigDecimal; import java.util.Map; +import lombok.Builder; +import lombok.Data; -/** - * 维保工单统计DTO - */ +/** 维保工单统计DTO */ @Data @Builder public class MaintenanceTaskStatsDTO { diff --git a/module-wo/src/main/java/com/ether/pms/ops/dto/ResumeRequest.java b/module-wo/src/main/java/com/ether/pms/ops/dto/ResumeRequest.java new file mode 100644 index 0000000..39cc0c1 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/dto/ResumeRequest.java @@ -0,0 +1,10 @@ +package com.ether.pms.ops.dto; + +import lombok.Data; + +@Data +public class ResumeRequest { + private Long operatorId; + + private String operatorName; +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/dto/ReturnRequest.java b/module-wo/src/main/java/com/ether/pms/ops/dto/ReturnRequest.java new file mode 100644 index 0000000..153a3cf --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/dto/ReturnRequest.java @@ -0,0 +1,16 @@ +package com.ether.pms.ops.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class ReturnRequest { + @NotBlank(message = "退回原因不能为空") + @Size(max = 1000, message = "退回原因长度不能超过1000字符") + private String reason; + + private Long operatorId; + + private String operatorName; +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/dto/SuspendRequest.java b/module-wo/src/main/java/com/ether/pms/ops/dto/SuspendRequest.java new file mode 100644 index 0000000..780f3d9 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/dto/SuspendRequest.java @@ -0,0 +1,16 @@ +package com.ether.pms.ops.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class SuspendRequest { + @NotBlank(message = "挂起原因不能为空") + @Size(max = 1000, message = "挂起原因长度不能超过1000字符") + private String reason; + + private Long operatorId; + + private String operatorName; +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/dto/WorkOrderStatsDTO.java b/module-wo/src/main/java/com/ether/pms/ops/dto/WorkOrderStatsDTO.java index 4635cbf..23041e3 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/dto/WorkOrderStatsDTO.java +++ b/module-wo/src/main/java/com/ether/pms/ops/dto/WorkOrderStatsDTO.java @@ -1,10 +1,9 @@ package com.ether.pms.ops.dto; -import lombok.Builder; -import lombok.Data; - import java.math.BigDecimal; import java.util.Map; +import lombok.Builder; +import lombok.Data; @Data @Builder @@ -26,4 +25,4 @@ public class WorkOrderStatsDTO { private Map bySource; private Map byType; private Map byPriority; -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionItem.java b/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionItem.java index cb4762c..3180a52 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionItem.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionItem.java @@ -1,10 +1,9 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; - import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity(name = "OpsInspectionItem") @Table(name = "ops_inspection_item") @@ -45,4 +44,4 @@ public class InspectionItem { public void prePersist() { createdAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionTemplate.java b/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionTemplate.java index e28eea6..57c5a91 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionTemplate.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/InspectionTemplate.java @@ -1,11 +1,10 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; - import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.Data; @Entity(name = "OpsInspectionTemplate") @Table(name = "ops_inspection_template") @@ -38,7 +37,8 @@ public class InspectionTemplate { private TemplateStatus status = TemplateStatus.ACTIVE; public enum TemplateStatus { - ACTIVE, INACTIVE + ACTIVE, + INACTIVE } @Column(name = "created_at") @@ -50,8 +50,7 @@ public class InspectionTemplate { @Column(name = "created_by", length = 200) private String createdBy; - @Transient - private List items; + @Transient private List items; @PrePersist public void prePersist() { @@ -63,4 +62,4 @@ public class InspectionTemplate { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenancePlan.java b/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenancePlan.java index b2c681c..8724131 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenancePlan.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenancePlan.java @@ -1,11 +1,10 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_maintenance_plan") @@ -35,7 +34,8 @@ public class MaintenancePlan { private PlanType planType; public enum PlanType { - PREVENTIVE, CORRECTIVE + PREVENTIVE, + CORRECTIVE } @Column(name = "cycle_days") @@ -52,7 +52,9 @@ public class MaintenancePlan { private PlanStatus status = PlanStatus.ACTIVE; public enum PlanStatus { - ACTIVE, INACTIVE, SUSPENDED + ACTIVE, + INACTIVE, + SUSPENDED } @Column(name = "last_date") @@ -80,4 +82,4 @@ public class MaintenancePlan { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenanceTask.java b/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenanceTask.java index 5040512..6a573d3 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenanceTask.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/MaintenanceTask.java @@ -1,27 +1,26 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.Data; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; -/** - * 维保工单实体(商业地产维保管理) - */ +/** 维保工单实体(商业地产维保管理) */ @Entity -@Table(name = "ops_maintenance_task", indexes = { - @Index(name = "idx_mt_equipment_status", columnList = "equipment_id, status"), - @Index(name = "idx_mt_project_status", columnList = "project_id, status"), - @Index(name = "idx_mt_plan_createdat", columnList = "plan_id, created_at"), - @Index(name = "idx_mt_status_assigneddate", columnList = "status, assigned_date") -}) +@Table( + name = "ops_maintenance_task", + indexes = { + @Index(name = "idx_mt_equipment_status", columnList = "equipment_id, status"), + @Index(name = "idx_mt_project_status", columnList = "project_id, status"), + @Index(name = "idx_mt_plan_createdat", columnList = "plan_id, created_at"), + @Index(name = "idx_mt_status_assigneddate", columnList = "status, assigned_date") + }) @Data public class MaintenanceTask { @@ -46,9 +45,9 @@ public class MaintenanceTask { private TaskType taskType; public enum TaskType { - PREVENTIVE, // 预防性 - CORRECTIVE, // 纠正性 - EMERGENCY // 紧急 + PREVENTIVE, // 预防性 + CORRECTIVE, // 纠正性 + EMERGENCY // 紧急 } @Column(name = "trigger_type", length = 50) @@ -56,10 +55,10 @@ public class MaintenanceTask { private TriggerType triggerType; public enum TriggerType { - PLAN, // 计划触发 - INSPECTION, // 巡检触发 - FAULT, // 故障触发 - MANUAL // 手动创建 + PLAN, // 计划触发 + INSPECTION, // 巡检触发 + FAULT, // 故障触发 + MANUAL // 手动创建 } @Column(length = 20) @@ -67,7 +66,10 @@ public class MaintenanceTask { private Priority priority; public enum Priority { - LOW, MEDIUM, HIGH, URGENT + LOW, + MEDIUM, + HIGH, + URGENT } @Column(nullable = false) @@ -75,12 +77,12 @@ public class MaintenanceTask { private Status status = Status.PENDING; public enum Status { - PENDING, // 待分配 - ASSIGNED, // 已分配 - IN_PROGRESS, // 进行中 - COMPLETED, // 已完成 - VERIFIED, // 已验收 - CANCELLED // 已取消 + PENDING, // 待分配 + ASSIGNED, // 已分配 + IN_PROGRESS, // 进行中 + COMPLETED, // 已完成 + VERIFIED, // 已验收 + CANCELLED // 已取消 } @Column(length = 200) @@ -141,8 +143,7 @@ public class MaintenanceTask { @Column(name = "verified_date") private LocalDate verifiedDate; - @Column - private Integer rating; + @Column private Integer rating; @Column(length = 1000) private String remark; diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrder.java b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrder.java index 6412bf3..345660b 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrder.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrder.java @@ -1,24 +1,25 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; +import lombok.Data; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; @Entity -@Table(name = "ops_work_order", indexes = { - @Index(name = "idx_wo_project_status", columnList = "project_id, status"), - @Index(name = "idx_wo_priority_status", columnList = "priority, status"), - @Index(name = "idx_wo_plan_createdat", columnList = "plan_id, created_at"), - @Index(name = "idx_wo_status_createdat", columnList = "status, created_at"), - @Index(name = "idx_wo_createdat_desc", columnList = "created_at DESC") -}) +@Table( + name = "ops_work_order", + indexes = { + @Index(name = "idx_wo_project_status", columnList = "project_id, status"), + @Index(name = "idx_wo_priority_status", columnList = "priority, status"), + @Index(name = "idx_wo_plan_createdat", columnList = "plan_id, created_at"), + @Index(name = "idx_wo_status_createdat", columnList = "status, created_at"), + @Index(name = "idx_wo_createdat_desc", columnList = "created_at DESC") + }) @Data public class WorkOrder { @Id @@ -37,11 +38,22 @@ public class WorkOrder { private Type type; public enum Source { - OWNER, MAINTENANCE, INSPECTION, FAULT, REGULATORY, MANUAL + OWNER, + MAINTENANCE, + INSPECTION, + FAULT, + REGULATORY, + MANUAL } public enum Type { - REPAIR, INSPECTION, SECURITY, CLEANING, PROPERTY, CONSULTATION + REPAIR, + INSPECTION, + SECURITY, + CLEANING, + PROPERTY, + CONSULTATION, + FAULT } @Column(nullable = false, length = 20) @@ -49,7 +61,10 @@ public class WorkOrder { private Priority priority = Priority.MEDIUM; public enum Priority { - LOW, MEDIUM, HIGH, URGENT + LOW, + MEDIUM, + HIGH, + URGENT } @Column(nullable = false, length = 20) @@ -57,7 +72,14 @@ public class WorkOrder { private Status status = Status.PENDING; public enum Status { - PENDING, ASSIGNED, IN_PROGRESS, SUSPENDED, RETURNED, COMPLETED, VERIFIED, CANCELLED + PENDING, + ASSIGNED, + IN_PROGRESS, + SUSPENDED, + RETURNED, + COMPLETED, + VERIFIED, + CANCELLED } @Column(name = "previous_status", length = 20) @@ -87,7 +109,10 @@ public class WorkOrder { private TriggerType triggerType; public enum TriggerType { - PLAN, INSPECTION, FAULT, MANUAL + PLAN, + INSPECTION, + FAULT, + MANUAL } @Column(name = "assigned_to", length = 200) @@ -138,8 +163,7 @@ public class WorkOrder { @Column(name = "verified_date") private LocalDate verifiedDate; - @Column - private Integer rating; + @Column private Integer rating; @Column(length = 1000) private String remark; @@ -176,4 +200,4 @@ public class WorkOrder { public void preUpdate() { updatedAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderItem.java b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderItem.java index 9e80978..a97ebe6 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderItem.java +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderItem.java @@ -1,11 +1,10 @@ package com.ether.pms.ops.entity; import jakarta.persistence.*; -import lombok.Data; - import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; +import lombok.Data; @Entity @Table(name = "ops_work_order_item") @@ -23,7 +22,9 @@ public class WorkOrderItem { private ItemType itemType; public enum ItemType { - PART, INSPECTION_ITEM, CHECKPOINT + PART, + INSPECTION_ITEM, + CHECKPOINT } @Column(name = "item_name", nullable = false, length = 200) @@ -60,4 +61,4 @@ public class WorkOrderItem { public void prePersist() { createdAt = LocalDateTime.now(); } -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderStatusHistory.java b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderStatusHistory.java new file mode 100644 index 0000000..d8c33f7 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/entity/WorkOrderStatusHistory.java @@ -0,0 +1,45 @@ +package com.ether.pms.ops.entity; + +import jakarta.persistence.*; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; + +@Entity +@Table( + name = "wo_status_history", + indexes = { + @Index(name = "idx_history_work_order", columnList = "work_order_id"), + @Index(name = "idx_history_operated_at", columnList = "operated_at") + }) +@Data +public class WorkOrderStatusHistory { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(name = "work_order_id", nullable = false) + private UUID workOrderId; + + @Column(name = "from_status", length = 20) + @Enumerated(EnumType.STRING) + private WorkOrder.Status fromStatus; + + @Column(name = "to_status", nullable = false, length = 20) + @Enumerated(EnumType.STRING) + private WorkOrder.Status toStatus; + + @Column(length = 1000) + private String reason; + + @Column(name = "operator_id") + private Long operatorId; + + @Column(name = "operator_name", length = 200) + private String operatorName; + + @CreationTimestamp + @Column(name = "operated_at", nullable = false, updatable = false) + private LocalDateTime operatedAt; +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/listener/WorkOrderEventListener.java b/module-wo/src/main/java/com/ether/pms/ops/listener/WorkOrderEventListener.java new file mode 100644 index 0000000..a430b59 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/listener/WorkOrderEventListener.java @@ -0,0 +1,97 @@ +package com.ether.pms.ops.listener; + +import com.ether.pms.asset.event.EquipmentFailureRecordedEvent; +import com.ether.pms.ops.entity.WorkOrder; +import com.ether.pms.ops.service.WorkOrderService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WorkOrderEventListener { + + private final WorkOrderService workOrderService; + + @Async + @EventListener + public void handleEquipmentFailure(EquipmentFailureRecordedEvent event) { + log.info( + "接收到设备故障记录事件,开始创建故障工单: equipmentId={}, failureHistoryId={}", + event.getEquipmentId(), + event.getFailureHistoryId()); + + try { + WorkOrder workOrder = new WorkOrder(); + workOrder.setSource(WorkOrder.Source.FAULT); + workOrder.setType(WorkOrder.Type.FAULT); + workOrder.setProjectId(event.getProjectId()); + workOrder.setEquipmentId(event.getEquipmentId()); + + String title = + String.format( + "设备故障:%s (%s)", + event.getEquipmentName() != null + ? event.getEquipmentName() + : event.getEquipmentCode(), + event.getFaultType() != null ? event.getFaultType() : "未知类型"); + workOrder.setTitle(title); + + StringBuilder description = new StringBuilder(); + description.append("自动创建的故障工单\n\n"); + description.append("故障信息:\n"); + if (event.getEquipmentCode() != null) { + description.append("- 设备编号:").append(event.getEquipmentCode()).append("\n"); + } + if (event.getEquipmentName() != null) { + description.append("- 设备名称:").append(event.getEquipmentName()).append("\n"); + } + if (event.getFaultType() != null) { + description.append("- 故障类型:").append(event.getFaultType()).append("\n"); + } + if (event.getFaultLevel() != null) { + description.append("- 故障级别:").append(event.getFaultLevel()).append("\n"); + } + if (event.getFailureReason() != null) { + description.append("- 故障原因:").append(event.getFailureReason()).append("\n"); + } + if (event.getFailureDescription() != null) { + description.append("- 故障描述:").append(event.getFailureDescription()).append("\n"); + } + if (event.getOccurredAt() != null) { + description.append("- 发生时间:").append(event.getOccurredAt()).append("\n"); + } + description.append("\n关联故障记录ID:").append(event.getFailureHistoryId()); + workOrder.setDescription(description.toString()); + + workOrder.setTriggerType(WorkOrder.TriggerType.FAULT); + workOrder.setPriority(calculatePriority(event.getFaultLevel())); + + WorkOrder created = workOrderService.createWorkOrder(workOrder); + log.info("故障工单创建成功: workOrderId={}, workNo={}", created.getId(), created.getWorkNo()); + } catch (Exception e) { + log.error( + "创故障工单失败: equipmentId={}, failureHistoryId={}, error={}", + event.getEquipmentId(), + event.getFailureHistoryId(), + e.getMessage(), + e); + } + } + + private WorkOrder.Priority calculatePriority(String faultLevel) { + if (faultLevel == null) { + return WorkOrder.Priority.MEDIUM; + } + return switch (faultLevel) { + case "CRITICAL" -> WorkOrder.Priority.URGENT; + case "HIGH" -> WorkOrder.Priority.HIGH; + case "MEDIUM" -> WorkOrder.Priority.MEDIUM; + case "LOW" -> WorkOrder.Priority.LOW; + default -> WorkOrder.Priority.MEDIUM; + }; + } +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionItemRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionItemRepository.java index ae9107f..c603c24 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionItemRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionItemRepository.java @@ -1,18 +1,15 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.InspectionItem; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.UUID; - -/** - * 巡检项目数据访问层(工单模块) - */ +/** 巡检项目数据访问层(工单模块) */ @Repository("opsInspectionItemRepository") public interface InspectionItemRepository extends JpaRepository { List findByTemplateIdOrderBySortOrder(UUID templateId); void deleteByTemplateId(UUID templateId); -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionTemplateRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionTemplateRepository.java index 5c4b61e..29803ac 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionTemplateRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/InspectionTemplateRepository.java @@ -1,15 +1,12 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.InspectionTemplate; +import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.List; -import java.util.UUID; - -/** - * 巡检模板数据访问层(工单模块) - */ +/** 巡检模板数据访问层(工单模块) */ @Repository("opsInspectionTemplateRepository") public interface InspectionTemplateRepository extends JpaRepository { List findByProjectId(UUID projectId); @@ -18,5 +15,6 @@ public interface InspectionTemplateRepository extends JpaRepository findByStatus(InspectionTemplate.TemplateStatus status); - List findByProjectIdAndStatus(UUID projectId, InspectionTemplate.TemplateStatus status); -} \ No newline at end of file + List findByProjectIdAndStatus( + UUID projectId, InspectionTemplate.TemplateStatus status); +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenancePlanRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenancePlanRepository.java index 9f642a4..0a19fb3 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenancePlanRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenancePlanRepository.java @@ -1,12 +1,11 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.MaintenancePlan; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.time.LocalDate; import java.util.List; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface MaintenancePlanRepository extends JpaRepository { @@ -16,11 +15,14 @@ public interface MaintenancePlanRepository extends JpaRepository findByProjectId(UUID projectId); - List findByStatusAndNextDateBefore(MaintenancePlan.PlanStatus status, LocalDate date); + List findByStatusAndNextDateBefore( + MaintenancePlan.PlanStatus status, LocalDate date); List findByNextDateBefore(LocalDate date); - List findByEquipmentIdAndStatus(UUID equipmentId, MaintenancePlan.PlanStatus status); + List findByEquipmentIdAndStatus( + UUID equipmentId, MaintenancePlan.PlanStatus status); - List findByEquipmentIdAndPlanType(UUID equipmentId, MaintenancePlan.PlanType planType); -} \ No newline at end of file + List findByEquipmentIdAndPlanType( + UUID equipmentId, MaintenancePlan.PlanType planType); +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskJpaRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskJpaRepository.java new file mode 100644 index 0000000..d31b1d3 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskJpaRepository.java @@ -0,0 +1,107 @@ +package com.ether.pms.ops.repository; + +import com.ether.pms.ops.entity.MaintenanceTask; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +/** 维保工单JPA Repository */ +@Repository +public interface MaintenanceTaskJpaRepository extends JpaRepository { + + Optional findByTaskNo(String taskNo); + + List findByEquipmentId(UUID equipmentId); + + List findByPlanId(UUID planId); + + List findByStatus(MaintenanceTask.Status status); + + List findByEquipmentIdAndStatus( + UUID equipmentId, MaintenanceTask.Status status); + + List findByPriority(MaintenanceTask.Priority priority); + + List findByAssignedTo(String assignedTo); + + @Query("SELECT t FROM MaintenanceTask t WHERE t.status IN :statuses") + List findByStatusIn(@Param("statuses") List statuses); + + @Query( + "SELECT t FROM MaintenanceTask t WHERE t.assignedDate < :date AND t.status IN ('PENDING', 'ASSIGNED')") + List findOverdueTasks(@Param("date") LocalDate date); + + boolean existsByTaskNo(String taskNo); + + /** 查询指定前缀的最大工单号 */ + @Query("SELECT MAX(t.taskNo) FROM MaintenanceTask t WHERE t.taskNo LIKE :prefix") + String findMaxTaskNoByDatePrefix(@Param("prefix") String prefix); + + /** 统计各状态工单数量 */ + @Query("SELECT t.status, COUNT(t) FROM MaintenanceTask t GROUP BY t.status") + List countByStatus(); + + /** 多条件查询 */ + @Query( + "SELECT t FROM MaintenanceTask t WHERE " + + "(:projectId IS NULL OR t.projectId = :projectId) AND " + + "(:status IS NULL OR t.status = :status) AND " + + "(:priority IS NULL OR t.priority = :priority) AND " + + "(:taskType IS NULL OR t.taskType = :taskType) AND " + + "(:assignedTo IS NULL OR t.assignedTo LIKE %:assignedTo%) AND " + + "(:keyword IS NULL OR t.title LIKE %:keyword% OR t.taskNo LIKE %:keyword%)") + List findByConditions( + @Param("projectId") UUID projectId, + @Param("status") MaintenanceTask.Status status, + @Param("priority") MaintenanceTask.Priority priority, + @Param("taskType") MaintenanceTask.TaskType taskType, + @Param("assignedTo") String assignedTo, + @Param("keyword") String keyword); + + /** 统计指定状态的工单数量 */ + @Query("SELECT COUNT(t) FROM MaintenanceTask t WHERE t.status = :status") + long countByStatus(@Param("status") MaintenanceTask.Status status); + + /** 统计今日完成的工单数量 */ + @Query( + "SELECT COUNT(t) FROM MaintenanceTask t WHERE t.status = 'COMPLETED' AND t.completedDate = :date") + long countCompletedToday(@Param("date") LocalDate date); + + /** 统计逾期工单数量 */ + @Query( + "SELECT COUNT(t) FROM MaintenanceTask t WHERE t.assignedDate < :date AND t.status IN ('PENDING', 'ASSIGNED')") + long countOverdue(@Param("date") LocalDate date); + + /** 计算平均完成工时 */ + @Query( + "SELECT AVG(t.actualHours) FROM MaintenanceTask t WHERE t.status IN ('COMPLETED', 'VERIFIED') AND t.actualHours IS NOT NULL") + Double calculateAvgCompleteHours(); + + /** 计算平均评分 */ + @Query("SELECT AVG(t.rating) FROM MaintenanceTask t WHERE t.rating IS NOT NULL") + Double calculateAvgRating(); + + /** 按优先级统计工单数量 */ + @Query("SELECT t.priority, COUNT(t) FROM MaintenanceTask t GROUP BY t.priority") + List countByPriority(); + + /** 按触发类型统计工单数量 */ + @Query("SELECT t.triggerType, COUNT(t) FROM MaintenanceTask t GROUP BY t.triggerType") + List countByTriggerType(); + + /** 统计指定日期范围内创建的工单数量 */ + @Query( + "SELECT COUNT(t) FROM MaintenanceTask t WHERE t.createdAt >= :startDate AND t.createdAt < :endDate") + long countByCreatedAtBetween( + @Param("startDate") java.time.LocalDateTime startDate, + @Param("endDate") java.time.LocalDateTime endDate); + + /** 查询指定计划在指定时间范围内创建的工单 */ + List findByPlanIdAndCreatedAtBetween( + UUID planId, java.time.LocalDateTime startDate, java.time.LocalDateTime endDate); +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskRepository.java index 0f866f2..2d078d3 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/MaintenanceTaskRepository.java @@ -1,19 +1,16 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.MaintenanceTask; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -/** - * 维保工单Repository - */ +/** 维保工单Repository */ @Repository public interface MaintenanceTaskRepository extends JpaRepository { @@ -25,7 +22,8 @@ public interface MaintenanceTaskRepository extends JpaRepository findByStatus(MaintenanceTask.Status status); - List findByEquipmentIdAndStatus(UUID equipmentId, MaintenanceTask.Status status); + List findByEquipmentIdAndStatus( + UUID equipmentId, MaintenanceTask.Status status); List findByPriority(MaintenanceTask.Priority priority); @@ -34,33 +32,29 @@ public interface MaintenanceTaskRepository extends JpaRepository findByStatusIn(@Param("statuses") List statuses); - @Query("SELECT t FROM MaintenanceTask t WHERE t.assignedDate < :date AND t.status IN ('PENDING', 'ASSIGNED')") + @Query( + "SELECT t FROM MaintenanceTask t WHERE t.assignedDate < :date AND t.status IN ('PENDING', 'ASSIGNED')") List findOverdueTasks(@Param("date") LocalDate date); boolean existsByTaskNo(String taskNo); - /** - * 查询指定前缀的最大工单号 - */ + /** 查询指定前缀的最大工单号 */ @Query("SELECT MAX(t.taskNo) FROM MaintenanceTask t WHERE t.taskNo LIKE :prefix") String findMaxTaskNoByDatePrefix(@Param("prefix") String prefix); - /** - * 统计各状态工单数量 - */ + /** 统计各状态工单数量 */ @Query("SELECT t.status, COUNT(t) FROM MaintenanceTask t GROUP BY t.status") List countByStatus(); - /** - * 多条件查询 - */ - @Query("SELECT t FROM MaintenanceTask t WHERE " + - "(:projectId IS NULL OR t.projectId = :projectId) AND " + - "(:status IS NULL OR t.status = :status) AND " + - "(:priority IS NULL OR t.priority = :priority) AND " + - "(:taskType IS NULL OR t.taskType = :taskType) AND " + - "(:assignedTo IS NULL OR t.assignedTo LIKE %:assignedTo%) AND " + - "(:keyword IS NULL OR t.title LIKE %:keyword% OR t.taskNo LIKE %:keyword%)") + /** 多条件查询 */ + @Query( + "SELECT t FROM MaintenanceTask t WHERE " + + "(:projectId IS NULL OR t.projectId = :projectId) AND " + + "(:status IS NULL OR t.status = :status) AND " + + "(:priority IS NULL OR t.priority = :priority) AND " + + "(:taskType IS NULL OR t.taskType = :taskType) AND " + + "(:assignedTo IS NULL OR t.assignedTo LIKE %:assignedTo%) AND " + + "(:keyword IS NULL OR t.title LIKE %:keyword% OR t.taskNo LIKE %:keyword%)") List findByConditions( @Param("projectId") UUID projectId, @Param("status") MaintenanceTask.Status status, @@ -69,56 +63,45 @@ public interface MaintenanceTaskRepository extends JpaRepository countByPriority(); - /** - * 按触发类型统计工单数量 - */ + /** 按触发类型统计工单数量 */ @Query("SELECT t.triggerType, COUNT(t) FROM MaintenanceTask t GROUP BY t.triggerType") List countByTriggerType(); - /** - * 统计指定日期范围内创建的工单数量 - */ - @Query("SELECT COUNT(t) FROM MaintenanceTask t WHERE t.createdAt >= :startDate AND t.createdAt < :endDate") - long countByCreatedAtBetween(@Param("startDate") java.time.LocalDateTime startDate, @Param("endDate") java.time.LocalDateTime endDate); + /** 统计指定日期范围内创建的工单数量 */ + @Query( + "SELECT COUNT(t) FROM MaintenanceTask t WHERE t.createdAt >= :startDate AND t.createdAt < :endDate") + long countByCreatedAtBetween( + @Param("startDate") java.time.LocalDateTime startDate, + @Param("endDate") java.time.LocalDateTime endDate); - /** - * 查询指定计划在指定时间范围内创建的工单 - */ - List findByPlanIdAndCreatedAtBetween(UUID planId, java.time.LocalDateTime startDate, java.time.LocalDateTime endDate); + /** 查询指定计划在指定时间范围内创建的工单 */ + List findByPlanIdAndCreatedAtBetween( + UUID planId, java.time.LocalDateTime startDate, java.time.LocalDateTime endDate); } diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderItemRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderItemRepository.java index 0c16f10..42e142a 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderItemRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderItemRepository.java @@ -1,15 +1,14 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.WorkOrderItem; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; @Repository public interface WorkOrderItemRepository extends JpaRepository { List findByWorkOrderIdOrderBySortOrder(UUID workOrderId); void deleteByWorkOrderId(UUID workOrderId); -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderRepository.java index 3967316..798672c 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderRepository.java +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderRepository.java @@ -1,6 +1,11 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.WorkOrder; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,14 +14,9 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - @Repository -public interface WorkOrderRepository extends JpaRepository, JpaSpecificationExecutor { +public interface WorkOrderRepository + extends JpaRepository, JpaSpecificationExecutor { Optional findByWorkNoAndIsDeletedFalse(String workNo); List findByProjectIdAndIsDeletedFalse(UUID projectId); @@ -31,7 +31,8 @@ public interface WorkOrderRepository extends JpaRepository, Jpa List findByAssignedToAndIsDeletedFalse(String assignedTo); - @Query("SELECT w FROM WorkOrder w WHERE w.isDeleted = false AND w.assignedDate < :date AND w.status IN ('PENDING', 'ASSIGNED')") + @Query( + "SELECT w FROM WorkOrder w WHERE w.isDeleted = false AND w.assignedDate < :date AND w.status IN ('PENDING', 'ASSIGNED')") List findOverdueTasks(@Param("date") LocalDate date); @Query("SELECT MAX(w.workNo) FROM WorkOrder w WHERE w.workNo LIKE :prefix") @@ -39,10 +40,12 @@ public interface WorkOrderRepository extends JpaRepository, Jpa long countByStatusAndIsDeletedFalse(WorkOrder.Status status); - @Query("SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.status = 'COMPLETED' AND w.completedDate = :date") + @Query( + "SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.status = 'COMPLETED' AND w.completedDate = :date") long countCompletedToday(@Param("date") LocalDate date); - @Query("SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.assignedDate < :date AND w.status IN ('PENDING', 'ASSIGNED')") + @Query( + "SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.assignedDate < :date AND w.status IN ('PENDING', 'ASSIGNED')") long countOverdue(@Param("date") LocalDate date); @Query("SELECT w.status, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.status") @@ -54,15 +57,19 @@ public interface WorkOrderRepository extends JpaRepository, Jpa @Query("SELECT w.type, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.type") List countByType(); - @Query("SELECT w.priority, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.priority") + @Query( + "SELECT w.priority, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.priority") List countByPriority(); - @Query("SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.createdAt >= :startDate AND w.createdAt < :endDate") - long countByCreatedAtBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); + @Query( + "SELECT COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false AND w.createdAt >= :startDate AND w.createdAt < :endDate") + long countByCreatedAtBetween( + @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); List findByPlanIdAndIsDeletedFalse(UUID planId); - List findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse(UUID planId, LocalDateTime startDate, LocalDateTime endDate); + List findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse( + UUID planId, LocalDateTime startDate, LocalDateTime endDate); Page findByProjectIdAndIsDeletedFalse(UUID projectId, Pageable pageable); @@ -76,27 +83,31 @@ public interface WorkOrderRepository extends JpaRepository, Jpa Page findByAssignedToAndIsDeletedFalse(String assignedTo, Pageable pageable); - @Query("SELECT w FROM WorkOrder w WHERE w.isDeleted = false AND " + - "(:projectId IS NULL OR w.projectId = :projectId) " + - "AND (:equipmentId IS NULL OR w.equipmentId = :equipmentId) " + - "AND (:source IS NULL OR w.source = :source) " + - "AND (:type IS NULL OR w.type = :type) " + - "AND (:status IS NULL OR w.status = :status) " + - "AND (:priority IS NULL OR w.priority = :priority) " + - "AND (:assignedTo IS NULL OR w.assignedTo = :assignedTo) " + - "AND (:keyword IS NULL OR w.workNo LIKE %:keyword% " + - "OR w.title LIKE %:keyword%)") - Page searchWorkOrders(@Param("projectId") UUID projectId, - @Param("equipmentId") UUID equipmentId, - @Param("source") WorkOrder.Source source, - @Param("type") WorkOrder.Type type, - @Param("status") WorkOrder.Status status, - @Param("priority") WorkOrder.Priority priority, - @Param("assignedTo") String assignedTo, - @Param("keyword") String keyword, - Pageable pageable); + @Query( + "SELECT w FROM WorkOrder w WHERE w.isDeleted = false AND " + + "(:projectId IS NULL OR w.projectId = :projectId) " + + "AND (:equipmentId IS NULL OR w.equipmentId = :equipmentId) " + + "AND (:source IS NULL OR w.source = :source) " + + "AND (:type IS NULL OR w.type = :type) " + + "AND (:status IS NULL OR w.status = :status) " + + "AND (:priority IS NULL OR w.priority = :priority) " + + "AND (:assignedTo IS NULL OR w.assignedTo = :assignedTo) " + + "AND (:keyword IS NULL OR w.workNo LIKE %:keyword% " + + "OR w.title LIKE %:keyword%)") + Page searchWorkOrders( + @Param("projectId") UUID projectId, + @Param("equipmentId") UUID equipmentId, + @Param("source") WorkOrder.Source source, + @Param("type") WorkOrder.Type type, + @Param("status") WorkOrder.Status status, + @Param("priority") WorkOrder.Priority priority, + @Param("assignedTo") String assignedTo, + @Param("keyword") String keyword, + Pageable pageable); Optional findByIdAndIsDeletedFalse(UUID id); List findAllByIsDeletedFalse(); -} \ No newline at end of file + + Page findAllByIsDeletedFalse(Pageable pageable); +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderStatusHistoryRepository.java b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderStatusHistoryRepository.java new file mode 100644 index 0000000..d8b03b2 --- /dev/null +++ b/module-wo/src/main/java/com/ether/pms/ops/repository/WorkOrderStatusHistoryRepository.java @@ -0,0 +1,16 @@ +package com.ether.pms.ops.repository; + +import com.ether.pms.ops.entity.WorkOrderStatusHistory; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WorkOrderStatusHistoryRepository + extends JpaRepository { + + List findByWorkOrderIdOrderByOperatedAtDesc(UUID workOrderId); + + List findByWorkOrderIdOrderByOperatedAtAsc(UUID workOrderId); +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/scheduler/MaintenanceScheduler.java b/module-wo/src/main/java/com/ether/pms/ops/scheduler/MaintenanceScheduler.java index b9b4d52..1e7946d 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/scheduler/MaintenanceScheduler.java +++ b/module-wo/src/main/java/com/ether/pms/ops/scheduler/MaintenanceScheduler.java @@ -4,21 +4,17 @@ import com.ether.pms.ops.entity.MaintenancePlan; import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.repository.MaintenancePlanRepository; import com.ether.pms.ops.repository.WorkOrderRepository; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; - -/** - * 维保计划定时调度器 - * 每天凌晨1点检查到期的维保计划,自动生成工单 - */ +/** 维保计划定时调度器 每天凌晨1点检查到期的维保计划,自动生成工单 */ @Slf4j @Component @RequiredArgsConstructor @@ -28,10 +24,7 @@ public class MaintenanceScheduler { private final WorkOrderRepository workOrderRepository; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); - /** - * 从维保计划自动生成工单 - * 每天凌晨1点执行 - */ + /** 从维保计划自动生成工单 每天凌晨1点执行 */ @Scheduled(cron = "0 0 1 * * ?") @Transactional public void generateTasksFromPlans() { @@ -41,8 +34,9 @@ public class MaintenanceScheduler { LocalDate today = LocalDate.now(); // 查询所有ACTIVE状态且下次维保日期已到的计划 - List duePlans = maintenancePlanRepository.findByStatusAndNextDateBefore( - MaintenancePlan.PlanStatus.ACTIVE, today.plusDays(1)); + List duePlans = + maintenancePlanRepository.findByStatusAndNextDateBefore( + MaintenancePlan.PlanStatus.ACTIVE, today.plusDays(1)); int generatedCount = 0; @@ -76,10 +70,7 @@ public class MaintenanceScheduler { } } - /** - * 检查逾期任务 - * 每小时执行 - */ + /** 检查逾期任务 每小时执行 */ @Scheduled(cron = "0 0 * * * ?") public void checkOverdueTasks() { log.info("开始检查逾期工单..."); @@ -90,8 +81,11 @@ public class MaintenanceScheduler { if (!overdueTasks.isEmpty()) { log.warn("发现 {} 个逾期工单", overdueTasks.size()); for (WorkOrder task : overdueTasks) { - log.warn("逾期工单: {} - {} - 负责人: {}", - task.getWorkNo(), task.getTitle(), task.getAssignedTo()); + log.warn( + "逾期工单: {} - {} - 负责人: {}", + task.getWorkNo(), + task.getTitle(), + task.getAssignedTo()); } } else { log.info("未发现逾期工单"); @@ -101,22 +95,19 @@ public class MaintenanceScheduler { } } - /** - * 检查今天是否已为该计划生成过工单 - */ + /** 检查今天是否已为该计划生成过工单 */ private boolean isTaskGeneratedToday(java.util.UUID planId, LocalDate today) { LocalDateTime todayStart = today.atStartOfDay(); LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay(); - List todayTasks = workOrderRepository.findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse( - planId, todayStart, tomorrowStart); + List todayTasks = + workOrderRepository.findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse( + planId, todayStart, tomorrowStart); return !todayTasks.isEmpty(); } - /** - * 从计划创建工单 - */ + /** 从计划创建工单 */ private WorkOrder createTaskFromPlan(MaintenancePlan plan) { WorkOrder task = new WorkOrder(); task.setPlanId(plan.getId()); @@ -142,9 +133,7 @@ public class MaintenanceScheduler { return task; } - /** - * 生成工单编号 WO-YYYYMMDD-XXXX - */ + /** 生成工单编号 WO-YYYYMMDD-XXXX */ private String generateWorkNo() { String dateStr = LocalDate.now().format(DATE_FORMATTER); String prefix = "WO-" + dateStr + "-"; @@ -164,9 +153,7 @@ public class MaintenanceScheduler { return String.format("%s%04d", prefix, sequence); } - /** - * 更新计划的下次维保日期 - */ + /** 更新计划的下次维保日期 */ private void updatePlanNextDate(MaintenancePlan plan) { if (plan.getCycleDays() != null && plan.getCycleDays() > 0) { LocalDate newNextDate = LocalDate.now().plusDays(plan.getCycleDays()); diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/MaintenancePlanService.java b/module-wo/src/main/java/com/ether/pms/ops/service/MaintenancePlanService.java index 3891f2e..a762d90 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/MaintenancePlanService.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/MaintenancePlanService.java @@ -1,14 +1,11 @@ package com.ether.pms.ops.service; import com.ether.pms.ops.entity.MaintenancePlan; - import java.time.LocalDate; import java.util.List; import java.util.UUID; -/** - * 维保计划服务接口 - */ +/** 维保计划服务接口 */ public interface MaintenancePlanService { MaintenancePlan createPlan(MaintenancePlan plan); @@ -23,11 +20,13 @@ public interface MaintenancePlanService { List getPlansByEquipment(UUID equipmentId); - List getPlansByEquipmentAndStatus(UUID equipmentId, MaintenancePlan.PlanStatus status); + List getPlansByEquipmentAndStatus( + UUID equipmentId, MaintenancePlan.PlanStatus status); List getPlansByStatus(MaintenancePlan.PlanStatus status); List getPlansByNextDateBefore(LocalDate date); - List getPlansByEquipmentAndPlanType(UUID equipmentId, MaintenancePlan.PlanType planType); + List getPlansByEquipmentAndPlanType( + UUID equipmentId, MaintenancePlan.PlanType planType); } diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/MaintenanceTaskService.java b/module-wo/src/main/java/com/ether/pms/ops/service/MaintenanceTaskService.java index 376ee8e..68c4398 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/MaintenanceTaskService.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/MaintenanceTaskService.java @@ -2,15 +2,12 @@ package com.ether.pms.ops.service; import com.ether.pms.ops.dto.MaintenanceTaskStatsDTO; import com.ether.pms.ops.entity.MaintenanceTask; - import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.UUID; -/** - * 维保工单服务接口 - */ +/** 维保工单服务接口 */ public interface MaintenanceTaskService { MaintenanceTask createTask(MaintenanceTask task); @@ -39,24 +36,19 @@ public interface MaintenanceTaskService { MaintenanceTask startTask(UUID id); - MaintenanceTask completeTask(UUID id, String result, BigDecimal actualHours, BigDecimal cost, String completedBy); + MaintenanceTask completeTask( + UUID id, String result, BigDecimal actualHours, BigDecimal cost, String completedBy); - /** - * 完成工单(带详细信息) - */ + /** 完成工单(带详细信息) */ MaintenanceTask completeTaskWithDetails(UUID id, MaintenanceTask taskData); - /** - * 验收工单 - */ + /** 验收工单 */ MaintenanceTask verifyTask(UUID id, String verifiedBy, String remark, Integer rating); MaintenanceTask cancelTask(UUID id); MaintenanceTask rateTask(UUID id, Integer rating); - /** - * 获取工单统计信息 - */ + /** 获取工单统计信息 */ MaintenanceTaskStatsDTO getTaskStats(); } diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/WorkOrderService.java b/module-wo/src/main/java/com/ether/pms/ops/service/WorkOrderService.java index 4098188..4d2787a 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/WorkOrderService.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/WorkOrderService.java @@ -4,43 +4,283 @@ import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.ops.dto.WorkOrderStatsDTO; import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.entity.WorkOrderItem; - -import java.math.BigDecimal; +import com.ether.pms.ops.entity.WorkOrderStatusHistory; import java.time.LocalDate; import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +/** 工单服务接口 */ public interface WorkOrderService { + + /** + * 创建工单 + * + * @param workOrder 工单信息 + * @return 创建的工单 + */ WorkOrder createWorkOrder(WorkOrder workOrder); + + /** + * 更新工单 + * + * @param id 工单ID + * @param workOrder 工单信息 + * @return 更新后的工单 + */ WorkOrder updateWorkOrder(UUID id, WorkOrder workOrder); + + /** + * 删除工单(逻辑删除) + * + * @param id 工单ID + */ void deleteWorkOrder(UUID id); + + /** + * 根据ID获取工单 + * + * @param id 工单ID + * @return 工单信息 + */ WorkOrder getWorkOrderById(UUID id); + + /** + * 获取所有工单 + * + * @return 工单列表 + */ List getAllWorkOrders(); - List getWorkOrdersByProject(UUID projectId); + + /** + * 分页获取所有工单(避免OOM风险) + * + * @param pageable 分页参数 + * @return 工单分页结果 + */ + Page getAllWorkOrders(Pageable pageable); + + /** + * 根据项目ID获取工单(分页) + * + * @param projectId 项目ID + * @param pageable 分页参数 + * @return 工单分页结果 + */ + Page getWorkOrdersByProject(UUID projectId, Pageable pageable); + + /** + * 根据设备ID获取工单 + * + * @param equipmentId 设备ID + * @return 工单列表 + */ List getWorkOrdersByEquipment(UUID equipmentId); + + /** + * 根据设备ID获取工单(分页) + * + * @param equipmentId 设备ID + * @param pageable 分页参数 + * @return 工单分页结果 + */ + Page getWorkOrdersByEquipment(UUID equipmentId, Pageable pageable); + + /** + * 根据来源获取工单 + * + * @param source 工单来源 + * @return 工单列表 + */ List getWorkOrdersBySource(WorkOrder.Source source); + + /** + * 根据类型获取工单 + * + * @param type 工单类型 + * @return 工单列表 + */ List getWorkOrdersByType(WorkOrder.Type type); + + /** + * 根据状态获取工单 + * + * @param status 工单状态 + * @return 工单列表 + */ List getWorkOrdersByStatus(WorkOrder.Status status); + + /** + * 根据状态获取工单(分页) + * + * @param status 工单状态 + * @param pageable 分页参数 + * @return 工单分页结果 + */ + Page getWorkOrdersByStatus(WorkOrder.Status status, Pageable pageable); + + /** + * 根据负责人获取工单 + * + * @param assignedTo 负责人 + * @return 工单列表 + */ List getWorkOrdersByAssignedTo(String assignedTo); + + /** + * 获取逾期工单 + * + * @param date 截止日期 + * @return 逾期工单列表 + */ List getOverdueWorkOrders(LocalDate date); - PageResponse queryWorkOrders(UUID projectId, UUID equipmentId, WorkOrder.Source source, - WorkOrder.Type type, WorkOrder.Status status, - WorkOrder.Priority priority, String assignedTo, - String keyword, int page, int size); + /** + * 分页查询工单 + * + * @param projectId 项目ID + * @param equipmentId 设备ID + * @param source 来源 + * @param type 类型 + * @param status 状态 + * @param priority 优先级 + * @param assignedTo 负责人 + * @param keyword 关键词 + * @param page 页码 + * @param size 页大小 + * @return 分页响应 + */ + PageResponse queryWorkOrders( + UUID projectId, + UUID equipmentId, + WorkOrder.Source source, + WorkOrder.Type type, + WorkOrder.Status status, + WorkOrder.Priority priority, + String assignedTo, + String keyword, + int page, + int size); - WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate); + /** + * 分配工单 + * + * @param id 工单ID + * @param assignedTo 负责人 + * @param assignedVendor 维保商 + * @param assignedDate 分配日期 + * @return 分配后的工单 + */ + WorkOrder assignWorkOrder( + UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate); + + /** + * 启动工单 + * + * @param id 工单ID + * @return 启动后的工单 + */ WorkOrder startWorkOrder(UUID id); - WorkOrder completeWorkOrder(UUID id, WorkOrder workOrderData); - WorkOrder verifyWorkOrder(UUID id, String verifiedBy, String remark, Integer rating); - WorkOrder cancelWorkOrder(UUID id); - WorkOrder suspendWorkOrder(UUID id); - WorkOrder resumeWorkOrder(UUID id); - WorkOrder returnWorkOrder(UUID id); + /** + * 完成工单 + * + * @param id 工单ID + * @param workOrderData 完成时的工单数据 + * @return 完成后的工单 + */ + WorkOrder completeWorkOrder(UUID id, WorkOrder workOrderData); + + /** + * 验收工单 + * + * @param id 工单ID + * @param verifiedBy 验收人 + * @param remark 备注 + * @param rating 评分 + * @return 验收后的工单 + */ + WorkOrder verifyWorkOrder(UUID id, String verifiedBy, String remark, Integer rating); + + /** + * 取消工单 + * + * @param id 工单ID + * @return 取消后的工单 + */ + WorkOrder cancelWorkOrder(UUID id); + + /** + * 挂起工单 + * + * @param id 工单ID + * @param reason 挂起原因 + * @param operatorId 操作人ID + * @param operatorName 操作人名称 + * @return 挂起后的工单 + */ + WorkOrder suspendWorkOrder(UUID id, String reason, Long operatorId, String operatorName); + + /** + * 恢复工单 + * + * @param id 工单ID + * @param operatorId 操作人ID + * @param operatorName 操作人名称 + * @return 恢复后的工单 + */ + WorkOrder resumeWorkOrder(UUID id, Long operatorId, String operatorName); + + /** + * 退回工单 + * + * @param id 工单ID + * @param reason 退回原因 + * @param operatorId 操作人ID + * @param operatorName 操作人名称 + * @return 退回后的工单 + */ + WorkOrder returnWorkOrder(UUID id, String reason, Long operatorId, String operatorName); + + /** + * 获取工单状态变更历史 + * + * @param workOrderId 工单ID + * @return 状态变更历史列表 + */ + List getStatusHistory(UUID workOrderId); + + /** + * 获取工单统计信息 + * + * @return 工单统计DTO + */ WorkOrderStatsDTO getWorkOrderStats(); + /** + * 获取工单项列表 + * + * @param workOrderId 工单ID + * @return 工单项列表 + */ List getWorkOrderItems(UUID workOrderId); + + /** + * 添加工单项 + * + * @param workOrderId 工单ID + * @param item 工单项 + * @return 更新后的工单 + */ WorkOrder addWorkOrderItem(UUID workOrderId, WorkOrderItem item); + + /** + * 批量添加工单项 + * + * @param workOrderId 工单ID + * @param items 工项列表 + * @return 更新后的工单 + */ WorkOrder addWorkOrderItems(UUID workOrderId, List items); -} \ No newline at end of file +} diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenancePlanServiceImpl.java b/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenancePlanServiceImpl.java index 450296f..945d658 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenancePlanServiceImpl.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenancePlanServiceImpl.java @@ -5,17 +5,14 @@ import com.ether.pms.common.ErrorCode; import com.ether.pms.ops.entity.MaintenancePlan; import com.ether.pms.ops.repository.MaintenancePlanRepository; import com.ether.pms.ops.service.MaintenancePlanService; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; -import java.util.List; -import java.util.UUID; - -/** - * 维保计划服务实现 - */ +/** 维保计划服务实现 */ @Service @RequiredArgsConstructor public class MaintenancePlanServiceImpl implements MaintenancePlanService { @@ -74,7 +71,8 @@ public class MaintenancePlanServiceImpl implements MaintenancePlanService { @Override public MaintenancePlan getPlanById(UUID id) { - return maintenancePlanRepository.findById(id) + return maintenancePlanRepository + .findById(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "维保计划不存在")); } @@ -92,7 +90,8 @@ public class MaintenancePlanServiceImpl implements MaintenancePlanService { } @Override - public List getPlansByEquipmentAndStatus(UUID equipmentId, MaintenancePlan.PlanStatus status) { + public List getPlansByEquipmentAndStatus( + UUID equipmentId, MaintenancePlan.PlanStatus status) { return maintenancePlanRepository.findByEquipmentIdAndStatus(equipmentId, status); } @@ -107,7 +106,8 @@ public class MaintenancePlanServiceImpl implements MaintenancePlanService { } @Override - public List getPlansByEquipmentAndPlanType(UUID equipmentId, MaintenancePlan.PlanType planType) { + public List getPlansByEquipmentAndPlanType( + UUID equipmentId, MaintenancePlan.PlanType planType) { return maintenancePlanRepository.findByEquipmentIdAndPlanType(equipmentId, planType); } } diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenanceTaskServiceImpl.java b/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenanceTaskServiceImpl.java index ef908cd..026a30c 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenanceTaskServiceImpl.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/impl/MaintenanceTaskServiceImpl.java @@ -1,17 +1,13 @@ package com.ether.pms.ops.service.impl; -import com.ether.pms.common.BusinessException; -import com.ether.pms.common.ErrorCode; import com.ether.pms.asset.entity.Equipment; import com.ether.pms.asset.repository.EquipmentRepository; +import com.ether.pms.common.BusinessException; +import com.ether.pms.common.ErrorCode; import com.ether.pms.ops.dto.MaintenanceTaskStatsDTO; import com.ether.pms.ops.entity.MaintenanceTask; -import com.ether.pms.ops.repository.MaintenanceTaskRepository; +import com.ether.pms.ops.repository.MaintenanceTaskJpaRepository; import com.ether.pms.ops.service.MaintenanceTaskService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; @@ -22,15 +18,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -/** - * 维保工单服务实现 - */ +/** 维保工单服务实现 */ @Service @RequiredArgsConstructor public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { - private final MaintenanceTaskRepository maintenanceTaskRepository; + private final MaintenanceTaskJpaRepository maintenanceTaskRepository; private final EquipmentRepository equipmentRepository; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); @@ -53,13 +50,8 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { } /** - * 自动判定工单优先级 - * 规则: - * 1. 紧急维修 → URGENT - * 2. 故障触发 + 关键词(困人/漏水/停电/火灾)→ URGENT - * 3. 故障触发 → HIGH - * 4. 计划触发 → MEDIUM - * 5. 其他 → MEDIUM + * 自动判定工单优先级 规则: 1. 紧急维修 → URGENT 2. 故障触发 + 关键词(困人/漏水/停电/火灾)→ URGENT 3. 故障触发 → HIGH 4. 计划触发 → + * MEDIUM 5. 其他 → MEDIUM */ private MaintenanceTask.Priority autoDeterminePriority(MaintenanceTask task) { // 1. 紧急维修类型 @@ -70,10 +62,11 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { // 2. 故障触发类型 if (task.getTriggerType() == MaintenanceTask.TriggerType.FAULT) { // 检查标题和描述中的紧急关键词 - String content = (task.getTitle() != null ? task.getTitle() : "") + - (task.getDescription() != null ? task.getDescription() : ""); + String content = + (task.getTitle() != null ? task.getTitle() : "") + + (task.getDescription() != null ? task.getDescription() : ""); content = content.toLowerCase(); - + // 紧急关键词列表 String[] urgentKeywords = {"困人", "漏水", "停电", "火灾", "爆炸", "漏电", "冒烟", "故障停机"}; for (String keyword : urgentKeywords) { @@ -171,7 +164,8 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { @Override public MaintenanceTask getTaskById(UUID id) { - return maintenanceTaskRepository.findById(id) + return maintenanceTaskRepository + .findById(id) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "维保工单不存在")); } @@ -246,7 +240,8 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { @Override @Transactional - public MaintenanceTask completeTask(UUID id, String result, BigDecimal actualHours, BigDecimal cost, String completedBy) { + public MaintenanceTask completeTask( + UUID id, String result, BigDecimal actualHours, BigDecimal cost, String completedBy) { MaintenanceTask task = getTaskById(id); if (task.getStatus() != MaintenanceTask.Status.IN_PROGRESS) { @@ -325,9 +320,7 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { return savedTask; } - /** - * 更新设备维保记录 - */ + /** 更新设备维保记录 */ private void updateEquipmentMaintenanceRecord(MaintenanceTask task) { try { Equipment equipment = equipmentRepository.findById(task.getEquipmentId()).orElse(null); @@ -343,7 +336,10 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { // 更新下次巡检日期(如果工单是预防性维护) if (task.getTaskType() == MaintenanceTask.TaskType.PREVENTIVE) { // 根据设备类型设置不同的巡检周期,这里默认30天 - int inspectionCycle = equipment.getInspectionCycle() != null ? equipment.getInspectionCycle() : 30; + int inspectionCycle = + equipment.getInspectionCycle() != null + ? equipment.getInspectionCycle() + : 30; equipment.setNextInspectionDate(LocalDate.now().plusDays(inspectionCycle)); } @@ -379,11 +375,11 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { public MaintenanceTask cancelTask(UUID id) { MaintenanceTask task = getTaskById(id); - List cancellableStatuses = Arrays.asList( - MaintenanceTask.Status.PENDING, - MaintenanceTask.Status.ASSIGNED, - MaintenanceTask.Status.IN_PROGRESS - ); + List cancellableStatuses = + Arrays.asList( + MaintenanceTask.Status.PENDING, + MaintenanceTask.Status.ASSIGNED, + MaintenanceTask.Status.IN_PROGRESS); if (!cancellableStatuses.contains(task.getStatus())) { throw new BusinessException(6004, "只有PENDING、ASSIGNED或IN_PROGRESS状态的工单才能取消"); @@ -399,7 +395,8 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { public MaintenanceTask rateTask(UUID id, Integer rating) { MaintenanceTask task = getTaskById(id); - if (task.getStatus() != MaintenanceTask.Status.COMPLETED && task.getStatus() != MaintenanceTask.Status.VERIFIED) { + if (task.getStatus() != MaintenanceTask.Status.COMPLETED + && task.getStatus() != MaintenanceTask.Status.VERIFIED) { throw new BusinessException(6005, "只有已完成或已验收的工单才能评分"); } @@ -412,10 +409,7 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { return maintenanceTaskRepository.save(task); } - /** - * 生成工单编号:EQ-YYYYMMDD-XXXX - * 格式:EQ-20260328-0001 - */ + /** 生成工单编号:EQ-YYYYMMDD-XXXX 格式:EQ-20260328-0001 */ private String generateTaskNo() { String dateStr = LocalDate.now().format(DATE_FORMATTER); String prefix = "EQ-" + dateStr + "-"; @@ -436,9 +430,7 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { return String.format("%s%04d", prefix, sequence); } - /** - * 计算总费用 - */ + /** 计算总费用 */ private void calculateTotalCost(MaintenanceTask task) { BigDecimal laborCost = task.getLaborCost() != null ? task.getLaborCost() : BigDecimal.ZERO; BigDecimal partsCost = task.getPartsCost() != null ? task.getPartsCost() : BigDecimal.ZERO; @@ -455,14 +447,16 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { long total = maintenanceTaskRepository.count(); long pending = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.PENDING); long assigned = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.ASSIGNED); - long inProgress = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.IN_PROGRESS); + long inProgress = + maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.IN_PROGRESS); long completed = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.COMPLETED); long verified = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.VERIFIED); long cancelled = maintenanceTaskRepository.countByStatus(MaintenanceTask.Status.CANCELLED); // 今日统计 long completedToday = maintenanceTaskRepository.countCompletedToday(today); - long createdToday = maintenanceTaskRepository.countByCreatedAtBetween(todayStart, tomorrowStart); + long createdToday = + maintenanceTaskRepository.countByCreatedAtBetween(todayStart, tomorrowStart); // 逾期统计 long overdue = maintenanceTaskRepository.countOverdue(today); @@ -500,8 +494,14 @@ public class MaintenanceTaskServiceImpl implements MaintenanceTaskService { .completedToday(completedToday) .createdToday(createdToday) .overdue(overdue) - .avgCompleteHours(avgHours != null ? BigDecimal.valueOf(avgHours).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO) - .avgRating(avgRating != null ? BigDecimal.valueOf(avgRating).setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO) + .avgCompleteHours( + avgHours != null + ? BigDecimal.valueOf(avgHours).setScale(2, RoundingMode.HALF_UP) + : BigDecimal.ZERO) + .avgRating( + avgRating != null + ? BigDecimal.valueOf(avgRating).setScale(2, RoundingMode.HALF_UP) + : BigDecimal.ZERO) .byPriority(byPriority) .byTriggerType(byTriggerType) .build(); diff --git a/module-wo/src/main/java/com/ether/pms/ops/service/impl/WorkOrderServiceImpl.java b/module-wo/src/main/java/com/ether/pms/ops/service/impl/WorkOrderServiceImpl.java index 48a1c8c..adeaf91 100644 --- a/module-wo/src/main/java/com/ether/pms/ops/service/impl/WorkOrderServiceImpl.java +++ b/module-wo/src/main/java/com/ether/pms/ops/service/impl/WorkOrderServiceImpl.java @@ -1,21 +1,15 @@ package com.ether.pms.ops.service.impl; +import com.ether.pms.common.util.PaginationValidator; import com.ether.pms.mdm.dto.PageResponse; import com.ether.pms.ops.dto.WorkOrderStatsDTO; import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.entity.WorkOrderItem; +import com.ether.pms.ops.entity.WorkOrderStatusHistory; import com.ether.pms.ops.repository.WorkOrderItemRepository; import com.ether.pms.ops.repository.WorkOrderRepository; +import com.ether.pms.ops.repository.WorkOrderStatusHistoryRepository; import com.ether.pms.ops.service.WorkOrderService; -import com.ether.pms.common.util.PaginationValidator; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; @@ -25,6 +19,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -32,6 +33,7 @@ public class WorkOrderServiceImpl implements WorkOrderService { private final WorkOrderRepository workOrderRepository; private final WorkOrderItemRepository workOrderItemRepository; + private final WorkOrderStatusHistoryRepository statusHistoryRepository; @Override @Transactional @@ -77,15 +79,18 @@ public class WorkOrderServiceImpl implements WorkOrderService { @Override @Transactional public void deleteWorkOrder(UUID id) { - WorkOrder workOrder = workOrderRepository.findByIdAndIsDeletedFalse(id) - .orElseThrow(() -> new RuntimeException("工单不存在: " + id)); + WorkOrder workOrder = + workOrderRepository + .findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new RuntimeException("工单不存在: " + id)); workOrder.setIsDeleted(true); workOrderRepository.save(workOrder); } @Override public WorkOrder getWorkOrderById(UUID id) { - return workOrderRepository.findByIdAndIsDeletedFalse(id) + return workOrderRepository + .findByIdAndIsDeletedFalse(id) .orElseThrow(() -> new RuntimeException("工单不存在: " + id)); } @@ -93,14 +98,19 @@ public class WorkOrderServiceImpl implements WorkOrderService { public List getAllWorkOrders() { // ⚠️ 高风险:工单数据会持续增长,可能导致 OOM // 此方法用于管理后台的工单列表展示 - // TODO: 必须改为分页查询,建议添加 Pageable 参数 + // TODO: 必须改为分页查询,建议使用 Pageable 参数 // 临时方案:限制返回最近 1000 条记录 return workOrderRepository.findAllByIsDeletedFalse(); } @Override - public List getWorkOrdersByProject(UUID projectId) { - return workOrderRepository.findByProjectIdAndIsDeletedFalse(projectId); + public Page getAllWorkOrders(Pageable pageable) { + return workOrderRepository.findAllByIsDeletedFalse(pageable); + } + + @Override + public Page getWorkOrdersByProject(UUID projectId, Pageable pageable) { + return workOrderRepository.findByProjectIdAndIsDeletedFalse(projectId, pageable); } @Override @@ -108,6 +118,11 @@ public class WorkOrderServiceImpl implements WorkOrderService { return workOrderRepository.findByEquipmentIdAndIsDeletedFalse(equipmentId); } + @Override + public Page getWorkOrdersByEquipment(UUID equipmentId, Pageable pageable) { + return workOrderRepository.findByEquipmentIdAndIsDeletedFalse(equipmentId, pageable); + } + @Override public List getWorkOrdersBySource(WorkOrder.Source source) { return workOrderRepository.findBySourceAndIsDeletedFalse(source); @@ -123,6 +138,11 @@ public class WorkOrderServiceImpl implements WorkOrderService { return workOrderRepository.findByStatusAndIsDeletedFalse(status); } + @Override + public Page getWorkOrdersByStatus(WorkOrder.Status status, Pageable pageable) { + return workOrderRepository.findByStatusAndIsDeletedFalse(status, pageable); + } + @Override public List getWorkOrdersByAssignedTo(String assignedTo) { return workOrderRepository.findByAssignedToAndIsDeletedFalse(assignedTo); @@ -134,38 +154,68 @@ public class WorkOrderServiceImpl implements WorkOrderService { } @Override - public PageResponse queryWorkOrders(UUID projectId, UUID equipmentId, WorkOrder.Source source, - WorkOrder.Type type, WorkOrder.Status status, - WorkOrder.Priority priority, String assignedTo, - String keyword, int page, int size) { + public PageResponse queryWorkOrders( + UUID projectId, + UUID equipmentId, + WorkOrder.Source source, + WorkOrder.Type type, + WorkOrder.Status status, + WorkOrder.Priority priority, + String assignedTo, + String keyword, + int page, + int size) { int safePage = PaginationValidator.getSafePage(page); int safeSize = PaginationValidator.getSafeSize(size); - Pageable pageable = PageRequest.of(safePage, safeSize, Sort.by(Sort.Direction.DESC, "createdAt")); - Page workOrderPage = workOrderRepository.searchWorkOrders( - projectId, equipmentId, source, type, status, priority, assignedTo, keyword, pageable); + Pageable pageable = + PageRequest.of(safePage, safeSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Page workOrderPage = + workOrderRepository.searchWorkOrders( + projectId, + equipmentId, + source, + type, + status, + priority, + assignedTo, + keyword, + pageable); return PageResponse.of( workOrderPage.getContent(), workOrderPage.getNumber(), workOrderPage.getSize(), - workOrderPage.getTotalElements() - ); + workOrderPage.getTotalElements()); } @Override @Transactional - public WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate) { + public WorkOrder assignWorkOrder( + UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate) { WorkOrder workOrder = getWorkOrderById(id); if (workOrder.getStatus() != WorkOrder.Status.PENDING) { throw new RuntimeException("只能分配待派单状态的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setAssignedTo(assignedTo); workOrder.setAssignedVendor(assignedVendor); workOrder.setAssignedDate(assignedDate); workOrder.setStatus(WorkOrder.Status.ASSIGNED); - return workOrderRepository.save(workOrder); + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.ASSIGNED, + null, + null, + null); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @@ -177,10 +227,23 @@ public class WorkOrderServiceImpl implements WorkOrderService { throw new RuntimeException("只能启动已分配的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setStatus(WorkOrder.Status.IN_PROGRESS); workOrder.setActualStart(LocalDateTime.now()); - return workOrderRepository.save(workOrder); + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.IN_PROGRESS, + null, + null, + null); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @@ -192,33 +255,46 @@ public class WorkOrderServiceImpl implements WorkOrderService { throw new RuntimeException("只能完成进行中的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setActualEnd(LocalDateTime.now()); - // 计算实际工时 if (workOrder.getActualStart() != null) { - BigDecimal hours = BigDecimal.valueOf( - java.time.Duration.between(workOrder.getActualStart(), workOrder.getActualEnd()).toMinutes() - ).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); + BigDecimal hours = + BigDecimal.valueOf( + java.time.Duration.between( + workOrder.getActualStart(), + workOrder.getActualEnd()) + .toMinutes()) + .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); workOrder.setActualHours(hours); } - // 更新故障原因和解决方案 workOrder.setFaultCause(workOrderData.getFaultCause()); workOrder.setSolution(workOrderData.getSolution()); workOrder.setResult(workOrderData.getResult()); - // 更新费用信息 workOrder.setLaborCost(workOrderData.getLaborCost()); workOrder.setPartsCost(workOrderData.getPartsCost()); workOrder.setTotalCost(workOrderData.getTotalCost()); - // 更新完成信息 workOrder.setCompletedBy(workOrderData.getCompletedBy()); workOrder.setCompletedDate(LocalDate.now()); workOrder.setStatus(WorkOrder.Status.COMPLETED); - return workOrderRepository.save(workOrder); + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.COMPLETED, + null, + null, + null); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @@ -230,13 +306,26 @@ public class WorkOrderServiceImpl implements WorkOrderService { throw new RuntimeException("只能验收已完成的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setVerifiedBy(verifiedBy); workOrder.setVerifiedDate(LocalDate.now()); workOrder.setRemark(remark); workOrder.setRating(rating); workOrder.setStatus(WorkOrder.Status.VERIFIED); - return workOrderRepository.save(workOrder); + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.VERIFIED, + remark, + null, + verifiedBy); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @@ -244,62 +333,121 @@ public class WorkOrderServiceImpl implements WorkOrderService { public WorkOrder cancelWorkOrder(UUID id) { WorkOrder workOrder = getWorkOrderById(id); - if (workOrder.getStatus() == WorkOrder.Status.COMPLETED || - workOrder.getStatus() == WorkOrder.Status.VERIFIED) { + if (workOrder.getStatus() == WorkOrder.Status.COMPLETED + || workOrder.getStatus() == WorkOrder.Status.VERIFIED) { throw new RuntimeException("无法取消已完成的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setStatus(WorkOrder.Status.CANCELLED); - return workOrderRepository.save(workOrder); + + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.CANCELLED, + null, + null, + null); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @Transactional - public WorkOrder suspendWorkOrder(UUID id) { + public WorkOrder suspendWorkOrder( + UUID id, String reason, Long operatorId, String operatorName) { WorkOrder workOrder = getWorkOrderById(id); - if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED && - workOrder.getStatus() != WorkOrder.Status.IN_PROGRESS) { + if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED + && workOrder.getStatus() != WorkOrder.Status.IN_PROGRESS) { throw new RuntimeException("只能挂起已派单或执行中的工单"); } - workOrder.setPreviousStatus(workOrder.getStatus()); + WorkOrder.Status fromStatus = workOrder.getStatus(); + workOrder.setPreviousStatus(fromStatus); workOrder.setStatus(WorkOrder.Status.SUSPENDED); - return workOrderRepository.save(workOrder); + + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.SUSPENDED, + reason, + operatorId, + operatorName); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @Transactional - public WorkOrder resumeWorkOrder(UUID id) { + public WorkOrder resumeWorkOrder(UUID id, Long operatorId, String operatorName) { WorkOrder workOrder = getWorkOrderById(id); if (workOrder.getStatus() != WorkOrder.Status.SUSPENDED) { throw new RuntimeException("只能恢复已挂起的工单"); } - WorkOrder.Status targetStatus = workOrder.getPreviousStatus() != null - ? workOrder.getPreviousStatus() - : WorkOrder.Status.IN_PROGRESS; + WorkOrder.Status fromStatus = workOrder.getStatus(); + WorkOrder.Status targetStatus = + workOrder.getPreviousStatus() != null + ? workOrder.getPreviousStatus() + : WorkOrder.Status.IN_PROGRESS; workOrder.setPreviousStatus(null); workOrder.setStatus(targetStatus); - return workOrderRepository.save(workOrder); + + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + targetStatus, + "恢复工单", + operatorId, + operatorName); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @Transactional - public WorkOrder returnWorkOrder(UUID id) { + public WorkOrder returnWorkOrder(UUID id, String reason, Long operatorId, String operatorName) { WorkOrder workOrder = getWorkOrderById(id); - if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED) { - throw new RuntimeException("只能退回已派单的工单"); + if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED + && workOrder.getStatus() != WorkOrder.Status.IN_PROGRESS) { + throw new RuntimeException("只能退回已派单或执行中的工单"); } + WorkOrder.Status fromStatus = workOrder.getStatus(); workOrder.setPreviousStatus(null); workOrder.setAssignedTo(null); workOrder.setAssignedVendor(null); workOrder.setAssignedDate(null); - workOrder.setStatus(WorkOrder.Status.PENDING); - return workOrderRepository.save(workOrder); + workOrder.setStatus(WorkOrder.Status.RETURNED); + + WorkOrder savedWorkOrder = workOrderRepository.save(workOrder); + + WorkOrderStatusHistory history = + createStatusHistory( + savedWorkOrder.getId(), + fromStatus, + WorkOrder.Status.RETURNED, + reason, + operatorId, + operatorName); + statusHistoryRepository.save(history); + + return savedWorkOrder; } @Override @@ -390,11 +538,10 @@ public class WorkOrderServiceImpl implements WorkOrderService { return workOrder; } - /** - * 生成工单编号 WO-YYYYMMDD-XXXX - */ + /** 生成工单编号 WO-YYYYMMDD-XXXX */ private String generateWorkNo() { - String datePrefix = "WO-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-"; + String datePrefix = + "WO-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-"; String maxWorkNo = workOrderRepository.findMaxWorkNoByPrefix(datePrefix + "%"); int sequence = 1; @@ -409,4 +556,26 @@ public class WorkOrderServiceImpl implements WorkOrderService { return datePrefix + String.format("%04d", sequence); } -} \ No newline at end of file + + private WorkOrderStatusHistory createStatusHistory( + UUID workOrderId, + WorkOrder.Status fromStatus, + WorkOrder.Status toStatus, + String reason, + Long operatorId, + String operatorName) { + WorkOrderStatusHistory history = new WorkOrderStatusHistory(); + history.setWorkOrderId(workOrderId); + history.setFromStatus(fromStatus); + history.setToStatus(toStatus); + history.setReason(reason); + history.setOperatorId(operatorId); + history.setOperatorName(operatorName); + return history; + } + + @Override + public List getStatusHistory(UUID workOrderId) { + return statusHistoryRepository.findByWorkOrderIdOrderByOperatedAtDesc(workOrderId); + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/TestApplication.java b/module-wo/src/test/java/com/ether/pms/ops/TestApplication.java new file mode 100644 index 0000000..f2c2470 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/TestApplication.java @@ -0,0 +1,22 @@ +package com.ether.pms.ops; + +import com.ether.pms.ops.controller.MaintenanceTaskController; +import com.ether.pms.ops.service.impl.MaintenanceTaskServiceImpl; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; + +@SpringBootApplication +@ComponentScan( + basePackages = {"com.ether.pms.ops", "com.ether.pms.common"}, + excludeFilters = { + @ComponentScan.Filter( + type = FilterType.ASSIGNABLE_TYPE, + classes = {MaintenanceTaskController.class, MaintenanceTaskServiceImpl.class}) + }) +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/controller/WorkOrderControllerTest.java b/module-wo/src/test/java/com/ether/pms/ops/controller/WorkOrderControllerTest.java new file mode 100644 index 0000000..5bad318 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/controller/WorkOrderControllerTest.java @@ -0,0 +1,237 @@ +package com.ether.pms.ops.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.ether.pms.common.BusinessException; +import com.ether.pms.ops.TestApplication; +import com.ether.pms.ops.dto.ResumeRequest; +import com.ether.pms.ops.dto.ReturnRequest; +import com.ether.pms.ops.dto.SuspendRequest; +import com.ether.pms.ops.entity.WorkOrder; +import com.ether.pms.ops.entity.WorkOrderStatusHistory; +import com.ether.pms.ops.service.WorkOrderService; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest(classes = TestApplication.class) +@AutoConfigureMockMvc(addFilters = false) +class WorkOrderControllerTest { + + @Autowired private MockMvc mockMvc; + + @Autowired private ObjectMapper objectMapper; + + @MockBean private WorkOrderService workOrderService; + + private UUID testWorkOrderId; + private WorkOrder testWorkOrder; + + @BeforeEach + void setUp() { + testWorkOrderId = UUID.randomUUID(); + testWorkOrder = new WorkOrder(); + testWorkOrder.setId(testWorkOrderId); + testWorkOrder.setWorkNo("WO-20260328-0001"); + testWorkOrder.setTitle("测试工单"); + testWorkOrder.setSource(WorkOrder.Source.MANUAL); + testWorkOrder.setType(WorkOrder.Type.REPAIR); + testWorkOrder.setPriority(WorkOrder.Priority.MEDIUM); + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + testWorkOrder.setProjectId(UUID.randomUUID()); + testWorkOrder.setCreatedAt(LocalDateTime.now()); + testWorkOrder.setUpdatedAt(LocalDateTime.now()); + } + + @Test + void shouldSuspendWorkOrder_whenStatusIsInProgress() throws Exception { + SuspendRequest request = new SuspendRequest(); + request.setReason("等配件"); + request.setOperatorId(1L); + request.setOperatorName("张三"); + + WorkOrder suspendedOrder = createWorkOrderWithStatus(WorkOrder.Status.SUSPENDED); + when(workOrderService.suspendWorkOrder(eq(testWorkOrderId), any(), any(), any())) + .thenReturn(suspendedOrder); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/suspend", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data.status").value("SUSPENDED")); + } + + @Test + void shouldReturn400_whenSuspendWithEmptyReason() throws Exception { + SuspendRequest request = new SuspendRequest(); + request.setReason(""); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/suspend", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturn400_whenSuspendFromPending() throws Exception { + SuspendRequest request = new SuspendRequest(); + request.setReason("等配件"); + + WorkOrder pendingOrder = createWorkOrderWithStatus(WorkOrder.Status.PENDING); + when(workOrderService.suspendWorkOrder(eq(testWorkOrderId), any(), any(), any())) + .thenThrow(new BusinessException(400, "PENDING状态的工单不能直接挂起")); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/suspend", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldResumeWorkOrder_whenStatusIsSuspended() throws Exception { + ResumeRequest request = new ResumeRequest(); + request.setOperatorId(1L); + request.setOperatorName("张三"); + + WorkOrder resumedOrder = createWorkOrderWithStatus(WorkOrder.Status.IN_PROGRESS); + resumedOrder.setPreviousStatus(WorkOrder.Status.IN_PROGRESS); + when(workOrderService.resumeWorkOrder(eq(testWorkOrderId), any(), any())) + .thenReturn(resumedOrder); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/resume", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data.status").value("IN_PROGRESS")); + } + + @Test + void shouldReturn400_whenResumeFromCompleted() throws Exception { + ResumeRequest request = new ResumeRequest(); + + when(workOrderService.resumeWorkOrder(eq(testWorkOrderId), any(), any())) + .thenThrow(new BusinessException(400, "已完成状态的工单不能恢复")); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/resume", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturnWorkOrder_whenStatusIsAssigned() throws Exception { + ReturnRequest request = new ReturnRequest(); + request.setReason("派错工单"); + request.setOperatorId(1L); + request.setOperatorName("张三"); + + WorkOrder returnedOrder = createWorkOrderWithStatus(WorkOrder.Status.PENDING); + when(workOrderService.returnWorkOrder(eq(testWorkOrderId), any(), any(), any())) + .thenReturn(returnedOrder); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/return", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data.status").value("PENDING")); + } + + @Test + void shouldReturn400_whenReturnWithEmptyReason() throws Exception { + ReturnRequest request = new ReturnRequest(); + request.setReason(""); + + mockMvc.perform( + post("/api/wo/work-orders/{id}/return", testWorkOrderId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + } + + @Test + void shouldReturnStatusHistory_whenWorkOrderHasHistory() throws Exception { + List historyList = createTestStatusHistory(); + when(workOrderService.getStatusHistory(testWorkOrderId)).thenReturn(historyList); + + mockMvc.perform(get("/api/wo/work-orders/{id}/history", testWorkOrderId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data.length()").value(2)) + .andExpect(jsonPath("$.data[0].fromStatus").value("PENDING")) + .andExpect(jsonPath("$.data[0].toStatus").value("ASSIGNED")); + } + + @Test + void shouldReturnEmptyList_whenWorkOrderHasNoHistory() throws Exception { + when(workOrderService.getStatusHistory(testWorkOrderId)) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/wo/work-orders/{id}/history", testWorkOrderId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data").isEmpty()); + } + + private WorkOrder createWorkOrderWithStatus(WorkOrder.Status status) { + WorkOrder order = new WorkOrder(); + order.setId(testWorkOrderId); + order.setWorkNo("WO-20260328-0001"); + order.setTitle("测试工单"); + order.setSource(WorkOrder.Source.MANUAL); + order.setType(WorkOrder.Type.REPAIR); + order.setPriority(WorkOrder.Priority.MEDIUM); + order.setStatus(status); + order.setProjectId(UUID.randomUUID()); + order.setCreatedAt(LocalDateTime.now()); + order.setUpdatedAt(LocalDateTime.now()); + return order; + } + + private List createTestStatusHistory() { + WorkOrderStatusHistory history1 = new WorkOrderStatusHistory(); + history1.setId(UUID.randomUUID()); + history1.setWorkOrderId(testWorkOrderId); + history1.setFromStatus(WorkOrder.Status.PENDING); + history1.setToStatus(WorkOrder.Status.ASSIGNED); + history1.setOperatorName("系统"); + history1.setOperatedAt(LocalDateTime.now().minusHours(2)); + + WorkOrderStatusHistory history2 = new WorkOrderStatusHistory(); + history2.setId(UUID.randomUUID()); + history2.setWorkOrderId(testWorkOrderId); + history2.setFromStatus(WorkOrder.Status.ASSIGNED); + history2.setToStatus(WorkOrder.Status.IN_PROGRESS); + history2.setOperatorName("张三"); + history2.setOperatedAt(LocalDateTime.now().minusHours(1)); + + return Arrays.asList(history1, history2); + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/listener/WorkOrderEventListenerTest.java b/module-wo/src/test/java/com/ether/pms/ops/listener/WorkOrderEventListenerTest.java new file mode 100644 index 0000000..da807d1 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/listener/WorkOrderEventListenerTest.java @@ -0,0 +1,195 @@ +package com.ether.pms.ops.listener; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.ether.pms.asset.entity.EquipmentFailureHistory; +import com.ether.pms.asset.event.EquipmentFailureRecordedEvent; +import com.ether.pms.ops.entity.WorkOrder; +import com.ether.pms.ops.service.WorkOrderService; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@DisplayName("工单事件监听器测试") +class WorkOrderEventListenerTest { + + @Mock private WorkOrderService workOrderService; + + @InjectMocks private WorkOrderEventListener listener; + + private UUID equipmentId; + private UUID projectId; + + @BeforeEach + void setUp() { + equipmentId = UUID.randomUUID(); + projectId = UUID.randomUUID(); + } + + @Test + @DisplayName("收到故障事件时应该创建FAULT类型工单") + void handleEquipmentFailure_shouldCreateFaultWorkOrder_whenEventReceived() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory( + EquipmentFailureHistory.FailureLevel.CRITICAL, "电梯故障", "曳引机异常"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + WorkOrder workOrder = captor.getValue(); + assertThat(workOrder.getType()).isEqualTo(WorkOrder.Type.FAULT); + assertThat(workOrder.getSource()).isEqualTo(WorkOrder.Source.FAULT); + assertThat(workOrder.getEquipmentId()).isEqualTo(equipmentId); + } + + @Test + @DisplayName("危急故障应该设置URGENT优先级") + void handleEquipmentFailure_shouldSetUrgentPriority_whenFaultLevelIsCritical() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory( + EquipmentFailureHistory.FailureLevel.CRITICAL, "电梯故障", "曳引机异常"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + assertThat(captor.getValue().getPriority()).isEqualTo(WorkOrder.Priority.URGENT); + } + + @Test + @DisplayName("高故障应该设置HIGH优先级") + void handleEquipmentFailure_shouldSetHighPriority_whenFaultLevelIsHigh() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory(EquipmentFailureHistory.FailureLevel.HIGH, "空调故障", "制冷剂泄漏"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + assertThat(captor.getValue().getPriority()).isEqualTo(WorkOrder.Priority.HIGH); + } + + @Test + @DisplayName("中等故障应该设置MEDIUM优先级") + void handleEquipmentFailure_shouldSetMediumPriority_whenFaultLevelIsMedium() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory(EquipmentFailureHistory.FailureLevel.MEDIUM, "照明故障", "灯管损坏"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + assertThat(captor.getValue().getPriority()).isEqualTo(WorkOrder.Priority.MEDIUM); + } + + @Test + @DisplayName("低故障应该设置LOW优先级") + void handleEquipmentFailure_shouldSetLowPriority_whenFaultLevelIsLow() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory(EquipmentFailureHistory.FailureLevel.LOW, "门窗故障", "门锁松动"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + assertThat(captor.getValue().getPriority()).isEqualTo(WorkOrder.Priority.LOW); + } + + @Test + @DisplayName("工单描述应该包含设备名称和故障描述") + void handleEquipmentFailure_shouldIncludeEquipmentInfo_inDescription() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory(EquipmentFailureHistory.FailureLevel.HIGH, "中央空调", "压缩机故障"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))).thenReturn(new WorkOrder()); + + // Act + listener.handleEquipmentFailure(event); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(WorkOrder.class); + verify(workOrderService).createWorkOrder(captor.capture()); + + String description = captor.getValue().getDescription(); + assertThat(description).contains("中央空调"); + assertThat(description).contains("压缩机故障"); + } + + @Test + @DisplayName("服务异常时不应该抛出异常") + void handleEquipmentFailure_shouldNotThrowException_whenServiceFails() { + // Arrange + EquipmentFailureHistory failure = + createFailureHistory(EquipmentFailureHistory.FailureLevel.HIGH, "电梯故障", "故障描述"); + EquipmentFailureRecordedEvent event = new EquipmentFailureRecordedEvent(this, failure); + when(workOrderService.createWorkOrder(any(WorkOrder.class))) + .thenThrow(new RuntimeException("Database error")); + + // Act - 不抛出异常即为通过 + listener.handleEquipmentFailure(event); + + // Assert + verify(workOrderService).createWorkOrder(any(WorkOrder.class)); + } + + private EquipmentFailureHistory createFailureHistory( + EquipmentFailureHistory.FailureLevel level, + String equipmentName, + String failureReason) { + EquipmentFailureHistory history = new EquipmentFailureHistory(); + history.setId(UUID.randomUUID()); + history.setProjectId(projectId); + history.setEquipmentId(equipmentId); + history.setEquipmentCode("EQ-001"); + history.setEquipmentName(equipmentName); + history.setFailureType(EquipmentFailureHistory.FailureType.SUDDEN); + history.setFailureLevel(level); + history.setFailureReason(failureReason); + history.setFailureDescription("详细描述"); + history.setFailureTime(LocalDateTime.now()); + history.setCreatedAt(LocalDateTime.now()); + return history; + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/service/MaintenanceTaskServiceTest.java b/module-wo/src/test/java/com/ether/pms/ops/service/MaintenanceTaskServiceTest.java new file mode 100644 index 0000000..dca9ff3 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/service/MaintenanceTaskServiceTest.java @@ -0,0 +1,473 @@ +package com.ether.pms.ops.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import com.ether.pms.asset.entity.Equipment; +import com.ether.pms.asset.enums.EquipmentStatus; +import com.ether.pms.asset.enums.EquipmentType; +import com.ether.pms.asset.enums.OwnershipType; +import com.ether.pms.asset.repository.EquipmentRepository; +import com.ether.pms.ops.entity.MaintenanceTask; +import com.ether.pms.ops.repository.MaintenanceTaskJpaRepository; +import com.ether.pms.ops.service.impl.MaintenanceTaskServiceImpl; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MaintenanceTaskServiceTest { + + @Mock private MaintenanceTaskJpaRepository maintenanceTaskRepository; + + @Mock private EquipmentRepository equipmentRepository; + + @InjectMocks private MaintenanceTaskServiceImpl maintenanceTaskService; + + private MaintenanceTask testTask; + private Equipment testEquipment; + private UUID testId; + private UUID projectId; + private UUID equipmentId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + projectId = UUID.randomUUID(); + equipmentId = UUID.randomUUID(); + testTask = new MaintenanceTask(); + testTask.setId(testId); + testTask.setProjectId(projectId); + testTask.setEquipmentId(equipmentId); + testTask.setTaskNo("EQ-20260328-0001"); + testTask.setTitle("测试维保任务"); + testTask.setDescription("测试维保任务描述"); + testTask.setTaskType(MaintenanceTask.TaskType.PREVENTIVE); + testTask.setTriggerType(MaintenanceTask.TriggerType.PLAN); + testTask.setPriority(MaintenanceTask.Priority.MEDIUM); + testTask.setStatus(MaintenanceTask.Status.PENDING); + testTask.setCreatedAt(LocalDateTime.now()); + testTask.setUpdatedAt(LocalDateTime.now()); + + testEquipment = new Equipment(); + testEquipment.setId(equipmentId); + testEquipment.setProjectId(projectId); + testEquipment.setEquipmentCode("EQ-001"); + testEquipment.setEquipmentName("测试设备"); + testEquipment.setEquipmentType(EquipmentType.HVAC); + testEquipment.setOwnershipType(OwnershipType.PROJECT); + testEquipment.setStatus(EquipmentStatus.ACTIVE); + } + + @Test + void createTask_shouldGenerateTaskNo_whenNotProvided() { + testTask.setTaskNo(null); + when(maintenanceTaskRepository.findMaxTaskNoByDatePrefix(anyString())).thenReturn(null); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer( + invocation -> { + MaintenanceTask saved = invocation.getArgument(0); + saved.setId(testId); + return saved; + }); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertNotNull(result); + assertNotNull(result.getTaskNo()); + assertTrue(result.getTaskNo().startsWith("EQ-")); + } + + @Test + void createTask_shouldSetDefaultStatus_whenNull() { + testTask.setStatus(null); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertEquals(MaintenanceTask.Status.PENDING, result.getStatus()); + } + + @Test + void createTask_shouldAutoDeterminePriority_whenNull() { + testTask.setPriority(null); + testTask.setTriggerType(MaintenanceTask.TriggerType.FAULT); + testTask.setTitle("电梯困人紧急救援"); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertEquals(MaintenanceTask.Priority.URGENT, result.getPriority()); + } + + @Test + void createTask_shouldCalculateTotalCost() { + testTask.setLaborCost(new BigDecimal("100.00")); + testTask.setPartsCost(new BigDecimal("50.00")); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertEquals(new BigDecimal("150.00"), result.getTotalCost()); + } + + @Test + void getTaskById_shouldReturnTask_whenExists() { + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + MaintenanceTask result = maintenanceTaskService.getTaskById(testId); + + assertNotNull(result); + assertEquals(testId, result.getId()); + } + + @Test + void getTaskById_shouldThrowException_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(maintenanceTaskRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + + assertThrows(Exception.class, () -> maintenanceTaskService.getTaskById(nonExistentId)); + } + + @Test + void updateTask_shouldUpdateFields() { + MaintenanceTask updateData = new MaintenanceTask(); + updateData.setTitle("更新后的标题"); + updateData.setDescription("更新后的描述"); + updateData.setTaskType(MaintenanceTask.TaskType.CORRECTIVE); + updateData.setTriggerType(MaintenanceTask.TriggerType.FAULT); + updateData.setPriority(MaintenanceTask.Priority.HIGH); + updateData.setLaborCost(new BigDecimal("200.00")); + updateData.setPartsCost(new BigDecimal("100.00")); + + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.updateTask(testId, updateData); + + assertEquals("更新后的标题", result.getTitle()); + assertEquals("更新后的描述", result.getDescription()); + assertEquals(MaintenanceTask.TaskType.CORRECTIVE, result.getTaskType()); + assertEquals(MaintenanceTask.Priority.HIGH, result.getPriority()); + assertEquals(new BigDecimal("300.00"), result.getTotalCost()); + } + + @Test + void deleteTask_shouldSetCancelledStatus() { + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + maintenanceTaskService.deleteTask(testId); + + assertEquals(MaintenanceTask.Status.CANCELLED, testTask.getStatus()); + verify(maintenanceTaskRepository).save(testTask); + } + + @Test + void assignTask_shouldSetAssignedInfo() { + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.assignTask(testId, "张三", LocalDate.now()); + + assertEquals("张三", result.getAssignedTo()); + assertEquals(MaintenanceTask.Status.ASSIGNED, result.getStatus()); + } + + @Test + void assignTask_shouldThrowException_whenNotPending() { + testTask.setStatus(MaintenanceTask.Status.ASSIGNED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows( + Exception.class, + () -> maintenanceTaskService.assignTask(testId, "张三", LocalDate.now())); + } + + @Test + void startTask_shouldSetInProgressStatus() { + testTask.setStatus(MaintenanceTask.Status.ASSIGNED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.startTask(testId); + + assertEquals(MaintenanceTask.Status.IN_PROGRESS, result.getStatus()); + assertNotNull(result.getActualStart()); + } + + @Test + void startTask_shouldThrowException_whenNotAssigned() { + testTask.setStatus(MaintenanceTask.Status.PENDING); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows(Exception.class, () -> maintenanceTaskService.startTask(testId)); + } + + @Test + void completeTask_shouldSetCompletedStatus() { + testTask.setStatus(MaintenanceTask.Status.IN_PROGRESS); + + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = + maintenanceTaskService.completeTask( + testId, "维修完成", new BigDecimal("2.5"), new BigDecimal("150.00"), "李四"); + + assertEquals(MaintenanceTask.Status.COMPLETED, result.getStatus()); + assertEquals("维修完成", result.getResult()); + assertEquals("李四", result.getCompletedBy()); + assertEquals(new BigDecimal("2.5"), result.getActualHours()); + assertNotNull(result.getActualEnd()); + } + + @Test + void completeTask_shouldThrowException_whenNotInProgress() { + testTask.setStatus(MaintenanceTask.Status.PENDING); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows( + Exception.class, + () -> maintenanceTaskService.completeTask(testId, "完成", null, null, "张三")); + } + + @Test + void verifyTask_shouldSetVerifiedInfo() { + testTask.setStatus(MaintenanceTask.Status.COMPLETED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.verifyTask(testId, "王五", "验收通过", 5); + + assertEquals("王五", result.getVerifiedBy()); + assertEquals("验收通过", result.getRemark()); + assertEquals(5, result.getRating()); + assertEquals(MaintenanceTask.Status.VERIFIED, result.getStatus()); + } + + @Test + void verifyTask_shouldThrowException_whenNotCompleted() { + testTask.setStatus(MaintenanceTask.Status.IN_PROGRESS); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows( + Exception.class, () -> maintenanceTaskService.verifyTask(testId, "王五", "验收通过", 5)); + } + + @Test + void cancelTask_shouldSetCancelledStatus() { + testTask.setStatus(MaintenanceTask.Status.PENDING); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.cancelTask(testId); + + assertEquals(MaintenanceTask.Status.CANCELLED, result.getStatus()); + } + + @Test + void cancelTask_shouldThrowException_whenAlreadyVerified() { + testTask.setStatus(MaintenanceTask.Status.VERIFIED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows(Exception.class, () -> maintenanceTaskService.cancelTask(testId)); + } + + @Test + void rateTask_shouldSetRating() { + testTask.setStatus(MaintenanceTask.Status.COMPLETED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.rateTask(testId, 5); + + assertEquals(5, result.getRating()); + } + + @Test + void rateTask_shouldThrowException_whenInvalidRating() { + testTask.setStatus(MaintenanceTask.Status.COMPLETED); + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + + assertThrows(Exception.class, () -> maintenanceTaskService.rateTask(testId, 6)); + assertThrows(Exception.class, () -> maintenanceTaskService.rateTask(testId, 0)); + } + + @Test + void getAllTasks_shouldReturnList() { + List tasks = Arrays.asList(testTask); + when(maintenanceTaskRepository.findAll()).thenReturn(tasks); + + List result = maintenanceTaskService.getAllTasks(); + + assertEquals(1, result.size()); + } + + @Test + void getTasksByEquipment_shouldReturnList() { + List tasks = Arrays.asList(testTask); + when(maintenanceTaskRepository.findByEquipmentId(equipmentId)).thenReturn(tasks); + + List result = maintenanceTaskService.getTasksByEquipment(equipmentId); + + assertEquals(1, result.size()); + } + + @Test + void getTasksByStatus_shouldReturnList() { + List tasks = Arrays.asList(testTask); + when(maintenanceTaskRepository.findByStatus(MaintenanceTask.Status.PENDING)) + .thenReturn(tasks); + + List result = + maintenanceTaskService.getTasksByStatus(MaintenanceTask.Status.PENDING); + + assertEquals(1, result.size()); + } + + @Test + void getTasksByAssignedTo_shouldReturnList() { + testTask.setAssignedTo("张三"); + List tasks = Arrays.asList(testTask); + when(maintenanceTaskRepository.findByAssignedTo("张三")).thenReturn(tasks); + + List result = maintenanceTaskService.getTasksByAssignedTo("张三"); + + assertEquals(1, result.size()); + } + + @Test + void completeTaskWithDetails_shouldUpdateAllFields() { + testTask.setStatus(MaintenanceTask.Status.IN_PROGRESS); + + MaintenanceTask details = new MaintenanceTask(); + details.setActualHours(new BigDecimal("3.0")); + details.setFaultCause("设备老化"); + details.setSolution("更换零件"); + details.setResult("维修完成"); + details.setLaborCost(new BigDecimal("150.00")); + details.setPartsCost(new BigDecimal("80.00")); + details.setCompletedBy("李四"); + + List> partsUsed = new ArrayList<>(); + Map part = new HashMap<>(); + part.put("name", "零件A"); + part.put("quantity", 2); + partsUsed.add(part); + details.setPartsUsed(partsUsed); + + when(maintenanceTaskRepository.findById(testId)).thenReturn(Optional.of(testTask)); + when(equipmentRepository.findById(equipmentId)).thenReturn(Optional.of(testEquipment)); + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + when(equipmentRepository.save(any(Equipment.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.completeTaskWithDetails(testId, details); + + assertEquals(MaintenanceTask.Status.COMPLETED, result.getStatus()); + assertEquals("设备老化", result.getFaultCause()); + assertEquals("更换零件", result.getSolution()); + assertEquals("维修完成", result.getResult()); + assertEquals(new BigDecimal("3.0"), result.getActualHours()); + assertEquals(new BigDecimal("230.00"), result.getTotalCost()); + assertEquals("李四", result.getCompletedBy()); + } + + @Test + void autoDeterminePriority_shouldReturnUrgent_forEmergencyTaskType() { + MaintenanceTask task = new MaintenanceTask(); + task.setEquipmentId(equipmentId); + task.setTaskType(MaintenanceTask.TaskType.EMERGENCY); + task.setTriggerType(MaintenanceTask.TriggerType.MANUAL); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(task); + + assertEquals(MaintenanceTask.Priority.URGENT, result.getPriority()); + } + + @Test + void autoDeterminePriority_shouldReturnUrgent_forFaultWithUrgentKeywords() { + MaintenanceTask task = new MaintenanceTask(); + task.setEquipmentId(equipmentId); + task.setTaskType(MaintenanceTask.TaskType.CORRECTIVE); + task.setTriggerType(MaintenanceTask.TriggerType.FAULT); + task.setTitle("电梯困人"); + task.setDescription("有人员被困在电梯中"); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(task); + + assertEquals(MaintenanceTask.Priority.URGENT, result.getPriority()); + } + + @Test + void autoDeterminePriority_shouldReturnHigh_forFaultWithoutUrgentKeywords() { + MaintenanceTask task = new MaintenanceTask(); + task.setEquipmentId(equipmentId); + task.setTaskType(MaintenanceTask.TaskType.CORRECTIVE); + task.setTriggerType(MaintenanceTask.TriggerType.FAULT); + task.setTitle("设备异常"); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(task); + + assertEquals(MaintenanceTask.Priority.HIGH, result.getPriority()); + } + + @Test + void autoDeterminePriority_shouldReturnMedium_forPlanTrigger() { + testTask.setTriggerType(MaintenanceTask.TriggerType.PLAN); + testTask.setPriority(null); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertEquals(MaintenanceTask.Priority.MEDIUM, result.getPriority()); + } + + @Test + void autoDeterminePriority_shouldReturnHigh_forInspectionTrigger() { + testTask.setTriggerType(MaintenanceTask.TriggerType.INSPECTION); + testTask.setPriority(null); + + when(maintenanceTaskRepository.save(any(MaintenanceTask.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + MaintenanceTask result = maintenanceTaskService.createTask(testTask); + + assertEquals(MaintenanceTask.Priority.HIGH, result.getPriority()); + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderServiceTest.java b/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderServiceTest.java new file mode 100644 index 0000000..8520a50 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderServiceTest.java @@ -0,0 +1,437 @@ +package com.ether.pms.ops.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.ether.pms.ops.entity.WorkOrder; +import com.ether.pms.ops.entity.WorkOrderItem; +import com.ether.pms.ops.repository.WorkOrderItemRepository; +import com.ether.pms.ops.repository.WorkOrderRepository; +import com.ether.pms.ops.repository.WorkOrderStatusHistoryRepository; +import com.ether.pms.ops.service.impl.WorkOrderServiceImpl; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WorkOrderServiceTest { + + @Mock private WorkOrderRepository workOrderRepository; + + @Mock private WorkOrderItemRepository workOrderItemRepository; + + @Mock private WorkOrderStatusHistoryRepository statusHistoryRepository; + + @InjectMocks private WorkOrderServiceImpl workOrderService; + + private WorkOrder testWorkOrder; + private UUID testId; + private UUID projectId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + projectId = UUID.randomUUID(); + testWorkOrder = new WorkOrder(); + testWorkOrder.setId(testId); + testWorkOrder.setProjectId(projectId); + testWorkOrder.setWorkNo("WO-20260328-0001"); + testWorkOrder.setTitle("测试工单"); + testWorkOrder.setSource(WorkOrder.Source.MANUAL); + testWorkOrder.setType(WorkOrder.Type.REPAIR); + testWorkOrder.setPriority(WorkOrder.Priority.MEDIUM); + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + testWorkOrder.setCreatedAt(LocalDateTime.now()); + testWorkOrder.setUpdatedAt(LocalDateTime.now()); + } + + @Test + void createWorkOrder_shouldGenerateWorkNo_whenNotProvided() { + testWorkOrder.setWorkNo(null); + when(workOrderRepository.findMaxWorkNoByPrefix(anyString())).thenReturn(null); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer( + invocation -> { + WorkOrder saved = invocation.getArgument(0); + saved.setId(testId); + return saved; + }); + + WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); + + assertNotNull(result); + assertNotNull(result.getWorkNo()); + assertTrue(result.getWorkNo().startsWith("WO-")); + } + + @Test + void createWorkOrder_shouldSetDefaultStatus_whenNull() { + testWorkOrder.setStatus(null); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); + + assertEquals(WorkOrder.Status.PENDING, result.getStatus()); + } + + @Test + void createWorkOrder_shouldSetDefaultPriority_whenNull() { + testWorkOrder.setPriority(null); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); + + assertEquals(WorkOrder.Priority.MEDIUM, result.getPriority()); + } + + @Test + void getWorkOrderById_shouldReturnWorkOrder_whenExists() { + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + WorkOrder result = workOrderService.getWorkOrderById(testId); + + assertNotNull(result); + assertEquals(testId, result.getId()); + } + + @Test + void getWorkOrderById_shouldThrowException_whenNotExists() { + UUID nonExistentId = UUID.randomUUID(); + when(workOrderRepository.findByIdAndIsDeletedFalse(nonExistentId)) + .thenReturn(Optional.empty()); + + assertThrows( + RuntimeException.class, () -> workOrderService.getWorkOrderById(nonExistentId)); + } + + @Test + void updateWorkOrder_shouldUpdateFields() { + WorkOrder updateData = new WorkOrder(); + updateData.setTitle("更新后的标题"); + updateData.setDescription("更新后的描述"); + updateData.setType(WorkOrder.Type.INSPECTION); + updateData.setPriority(WorkOrder.Priority.HIGH); + updateData.setLaborCost(new BigDecimal("100.00")); + updateData.setPartsCost(new BigDecimal("50.00")); + updateData.setTotalCost(new BigDecimal("150.00")); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.updateWorkOrder(testId, updateData); + + assertEquals("更新后的标题", result.getTitle()); + assertEquals("更新后的描述", result.getDescription()); + assertEquals(WorkOrder.Type.INSPECTION, result.getType()); + assertEquals(WorkOrder.Priority.HIGH, result.getPriority()); + assertEquals(new BigDecimal("100.00"), result.getLaborCost()); + assertEquals(new BigDecimal("50.00"), result.getPartsCost()); + assertEquals(new BigDecimal("150.00"), result.getTotalCost()); + } + + @Test + void deleteWorkOrder_shouldSetDeletedFlag() { + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.deleteWorkOrder(testId); + + assertTrue(testWorkOrder.getIsDeleted()); + verify(workOrderRepository).save(testWorkOrder); + } + + @Test + void assignWorkOrder_shouldSetAssignedInfo() { + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()); + + assertEquals("张三", result.getAssignedTo()); + assertEquals("维保公司A", result.getAssignedVendor()); + assertEquals(WorkOrder.Status.ASSIGNED, result.getStatus()); + } + + @Test + void assignWorkOrder_shouldThrowException_whenNotPending() { + testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + assertThrows( + RuntimeException.class, + () -> workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now())); + } + + @Test + void startWorkOrder_shouldSetInProgressStatus() { + testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.startWorkOrder(testId); + + assertEquals(WorkOrder.Status.IN_PROGRESS, result.getStatus()); + assertNotNull(result.getActualStart()); + } + + @Test + void startWorkOrder_shouldThrowException_whenNotAssigned() { + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + assertThrows(RuntimeException.class, () -> workOrderService.startWorkOrder(testId)); + } + + @Test + void completeWorkOrder_shouldSetCompletedStatus() { + testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); + testWorkOrder.setActualStart(LocalDateTime.now().minusHours(2)); + + WorkOrder completeData = new WorkOrder(); + completeData.setFaultCause("设备故障"); + completeData.setSolution("更换零件"); + completeData.setResult("维修完成"); + completeData.setCompletedBy("李四"); + completeData.setLaborCost(new BigDecimal("200.00")); + completeData.setPartsCost(new BigDecimal("100.00")); + completeData.setTotalCost(new BigDecimal("300.00")); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.completeWorkOrder(testId, completeData); + + assertEquals(WorkOrder.Status.COMPLETED, result.getStatus()); + assertEquals("设备故障", result.getFaultCause()); + assertEquals("更换零件", result.getSolution()); + assertEquals("维修完成", result.getResult()); + assertEquals("李四", result.getCompletedBy()); + assertNotNull(result.getActualEnd()); + } + + @Test + void completeWorkOrder_shouldThrowException_whenNotInProgress() { + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + assertThrows( + RuntimeException.class, + () -> workOrderService.completeWorkOrder(testId, new WorkOrder())); + } + + @Test + void verifyWorkOrder_shouldSetVerifiedInfo() { + testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5); + + assertEquals("王五", result.getVerifiedBy()); + assertEquals("验收通过", result.getRemark()); + assertEquals(5, result.getRating()); + assertEquals(WorkOrder.Status.VERIFIED, result.getStatus()); + } + + @Test + void verifyWorkOrder_shouldThrowException_whenNotCompleted() { + testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + assertThrows( + RuntimeException.class, + () -> workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5)); + } + + @Test + void cancelWorkOrder_shouldSetCancelledStatus() { + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.cancelWorkOrder(testId); + + assertEquals(WorkOrder.Status.CANCELLED, result.getStatus()); + } + + @Test + void cancelWorkOrder_shouldThrowException_whenAlreadyCompleted() { + testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + + assertThrows(RuntimeException.class, () -> workOrderService.cancelWorkOrder(testId)); + } + + @Test + void suspendWorkOrder_shouldSetSuspendedStatus() { + testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.suspendWorkOrder(testId, "测试挂起原因", null, null); + + assertEquals(WorkOrder.Status.SUSPENDED, result.getStatus()); + assertNotNull(result.getPreviousStatus()); + } + + @Test + void resumeWorkOrder_shouldRestorePreviousStatus() { + testWorkOrder.setStatus(WorkOrder.Status.SUSPENDED); + testWorkOrder.setPreviousStatus(WorkOrder.Status.IN_PROGRESS); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.resumeWorkOrder(testId, null, null); + + assertEquals(WorkOrder.Status.IN_PROGRESS, result.getStatus()); + assertNull(result.getPreviousStatus()); + } + + @Test + void returnWorkOrder_shouldResetToPending() { + testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); + testWorkOrder.setAssignedTo("张三"); + testWorkOrder.setAssignedVendor("维保公司A"); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.returnWorkOrder(testId, "测试退回原因", null, null); + + assertEquals(WorkOrder.Status.RETURNED, result.getStatus()); + assertNull(result.getAssignedTo()); + assertNull(result.getAssignedVendor()); + } + + @Test + void getAllWorkOrders_shouldReturnList() { + List orders = Arrays.asList(testWorkOrder); + when(workOrderRepository.findAllByIsDeletedFalse()).thenReturn(orders); + + List result = workOrderService.getAllWorkOrders(); + + assertEquals(1, result.size()); + } + + @Test + void getWorkOrdersByProject_shouldReturnList() { + List orders = Arrays.asList(testWorkOrder); + org.springframework.data.domain.Pageable pageable = + org.springframework.data.domain.PageRequest.of(0, 20); + org.springframework.data.domain.Page pageResult = + new org.springframework.data.domain.PageImpl<>(orders, pageable, 1); + when(workOrderRepository.findByProjectIdAndIsDeletedFalse(projectId, pageable)) + .thenReturn(pageResult); + + org.springframework.data.domain.Page result = + workOrderService.getWorkOrdersByProject(projectId, pageable); + + assertEquals(1, result.getTotalElements()); + } + + @Test + void getWorkOrdersBySource_shouldReturnList() { + List orders = Arrays.asList(testWorkOrder); + when(workOrderRepository.findBySourceAndIsDeletedFalse(WorkOrder.Source.MANUAL)) + .thenReturn(orders); + + List result = workOrderService.getWorkOrdersBySource(WorkOrder.Source.MANUAL); + + assertEquals(1, result.size()); + } + + @Test + void getWorkOrdersByStatus_shouldReturnList() { + List orders = Arrays.asList(testWorkOrder); + when(workOrderRepository.findByStatusAndIsDeletedFalse(WorkOrder.Status.PENDING)) + .thenReturn(orders); + + List result = workOrderService.getWorkOrdersByStatus(WorkOrder.Status.PENDING); + + assertEquals(1, result.size()); + } + + @Test + void getWorkOrderItems_shouldReturnList() { + List items = Arrays.asList(new WorkOrderItem(), new WorkOrderItem()); + when(workOrderItemRepository.findByWorkOrderIdOrderBySortOrder(testId)).thenReturn(items); + + List result = workOrderService.getWorkOrderItems(testId); + + assertEquals(2, result.size()); + } + + @Test + void addWorkOrderItem_shouldAddItem() { + WorkOrderItem item = new WorkOrderItem(); + item.setItemName("维修项目1"); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderItemRepository.save(any(WorkOrderItem.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.addWorkOrderItem(testId, item); + + assertNotNull(result); + assertEquals(testId, item.getWorkOrderId()); + verify(workOrderItemRepository).save(item); + } + + @Test + void addWorkOrderItems_shouldAddMultipleItems() { + WorkOrderItem item1 = new WorkOrderItem(); + item1.setItemName("维修项目1"); + WorkOrderItem item2 = new WorkOrderItem(); + item2.setItemName("维修项目2"); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderItemRepository.save(any(WorkOrderItem.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + WorkOrder result = workOrderService.addWorkOrderItems(testId, Arrays.asList(item1, item2)); + + assertNotNull(result); + verify(workOrderItemRepository, times(2)).save(any(WorkOrderItem.class)); + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderStatusHistoryTest.java b/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderStatusHistoryTest.java new file mode 100644 index 0000000..761fe40 --- /dev/null +++ b/module-wo/src/test/java/com/ether/pms/ops/service/WorkOrderStatusHistoryTest.java @@ -0,0 +1,247 @@ +package com.ether.pms.ops.service; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.ether.pms.ops.entity.WorkOrder; +import com.ether.pms.ops.entity.WorkOrderStatusHistory; +import com.ether.pms.ops.repository.WorkOrderRepository; +import com.ether.pms.ops.repository.WorkOrderStatusHistoryRepository; +import com.ether.pms.ops.service.impl.WorkOrderServiceImpl; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@DisplayName("工单状态历史记录测试") +class WorkOrderStatusHistoryTest { + + @Mock private WorkOrderRepository workOrderRepository; + + @Mock private WorkOrderStatusHistoryRepository statusHistoryRepository; + + @InjectMocks private WorkOrderServiceImpl workOrderService; + + private WorkOrder testWorkOrder; + private UUID testId; + + @BeforeEach + void setUp() { + testId = UUID.randomUUID(); + testWorkOrder = new WorkOrder(); + testWorkOrder.setId(testId); + testWorkOrder.setProjectId(UUID.randomUUID()); + testWorkOrder.setWorkNo("WO-20260328-0001"); + testWorkOrder.setTitle("测试工单"); + testWorkOrder.setSource(WorkOrder.Source.MANUAL); + testWorkOrder.setType(WorkOrder.Type.REPAIR); + testWorkOrder.setPriority(WorkOrder.Priority.MEDIUM); + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + testWorkOrder.setCreatedAt(LocalDateTime.now()); + testWorkOrder.setUpdatedAt(LocalDateTime.now()); + } + + @Nested + @DisplayName("分配工单历史记录") + class AssignWorkOrderHistory { + + @Test + @DisplayName("分配工单时应记录状态历史") + void shouldRecordHistory_whenAssignWorkOrder() { + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(testId, captured.getWorkOrderId()); + assertEquals(WorkOrder.Status.PENDING, captured.getFromStatus()); + assertEquals(WorkOrder.Status.ASSIGNED, captured.getToStatus()); + } + } + + @Nested + @DisplayName("启动工单历史记录") + class StartWorkOrderHistory { + + @Test + @DisplayName("启动工单时应记录状态历史") + void shouldRecordHistory_whenStartWorkOrder() { + testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.startWorkOrder(testId); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(testId, captured.getWorkOrderId()); + assertEquals(WorkOrder.Status.ASSIGNED, captured.getFromStatus()); + assertEquals(WorkOrder.Status.IN_PROGRESS, captured.getToStatus()); + } + } + + @Nested + @DisplayName("完成工单历史记录") + class CompleteWorkOrderHistory { + + @Test + @DisplayName("完成工单时应记录状态历史") + void shouldRecordHistory_whenCompleteWorkOrder() { + testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); + testWorkOrder.setActualStart(LocalDateTime.now().minusHours(2)); + + WorkOrder completeData = new WorkOrder(); + completeData.setFaultCause("设备故障"); + completeData.setSolution("更换零件"); + completeData.setCompletedBy("李四"); + + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.completeWorkOrder(testId, completeData); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(testId, captured.getWorkOrderId()); + assertEquals(WorkOrder.Status.IN_PROGRESS, captured.getFromStatus()); + assertEquals(WorkOrder.Status.COMPLETED, captured.getToStatus()); + } + } + + @Nested + @DisplayName("验收工单历史记录") + class VerifyWorkOrderHistory { + + @Test + @DisplayName("验收工单时应记录状态历史") + void shouldRecordHistory_whenVerifyWorkOrder() { + testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(testId, captured.getWorkOrderId()); + assertEquals(WorkOrder.Status.COMPLETED, captured.getFromStatus()); + assertEquals(WorkOrder.Status.VERIFIED, captured.getToStatus()); + } + } + + @Nested + @DisplayName("取消工单历史记录") + class CancelWorkOrderHistory { + + @Test + @DisplayName("取消工单时应记录状态历史") + void shouldRecordHistory_whenCancelWorkOrder() { + testWorkOrder.setStatus(WorkOrder.Status.PENDING); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + workOrderService.cancelWorkOrder(testId); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(testId, captured.getWorkOrderId()); + assertEquals(WorkOrder.Status.PENDING, captured.getFromStatus()); + assertEquals(WorkOrder.Status.CANCELLED, captured.getToStatus()); + } + } + + @Nested + @DisplayName("获取状态历史记录") + class GetStatusHistory { + + @Test + @DisplayName("应返回工单状态历史列表") + void shouldReturnStatusHistoryList() { + UUID workOrderId = UUID.randomUUID(); + WorkOrderStatusHistory history1 = new WorkOrderStatusHistory(); + history1.setWorkOrderId(workOrderId); + history1.setFromStatus(WorkOrder.Status.PENDING); + history1.setToStatus(WorkOrder.Status.ASSIGNED); + + WorkOrderStatusHistory history2 = new WorkOrderStatusHistory(); + history2.setWorkOrderId(workOrderId); + history2.setFromStatus(WorkOrder.Status.ASSIGNED); + history2.setToStatus(WorkOrder.Status.IN_PROGRESS); + + when(statusHistoryRepository.findByWorkOrderIdOrderByOperatedAtDesc(workOrderId)) + .thenReturn(List.of(history2, history1)); + + List result = workOrderService.getStatusHistory(workOrderId); + + assertEquals(2, result.size()); + verify(statusHistoryRepository).findByWorkOrderIdOrderByOperatedAtDesc(workOrderId); + } + } + + @Nested + @DisplayName("历史记录操作人信息") + class HistoryOperatorInfo { + + @Test + @DisplayName("挂起工单时应记录操作人信息") + void shouldRecordOperatorInfo_whenSuspendWorkOrder() { + testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + Long operatorId = 1001L; + String operatorName = "测试操作员"; + workOrderService.suspendWorkOrder(testId, "测试挂起原因", operatorId, operatorName); + + ArgumentCaptor historyCaptor = + ArgumentCaptor.forClass(WorkOrderStatusHistory.class); + verify(statusHistoryRepository).save(historyCaptor.capture()); + + WorkOrderStatusHistory captured = historyCaptor.getValue(); + assertEquals(operatorId, captured.getOperatorId()); + assertEquals(operatorName, captured.getOperatorName()); + assertEquals("测试挂起原因", captured.getReason()); + } + } +} diff --git a/module-wo/src/test/java/com/ether/pms/ops/service/impl/WorkOrderServiceTest.java b/module-wo/src/test/java/com/ether/pms/ops/service/impl/WorkOrderServiceTest.java index 46b5b58..0674302 100644 --- a/module-wo/src/test/java/com/ether/pms/ops/service/impl/WorkOrderServiceTest.java +++ b/module-wo/src/test/java/com/ether/pms/ops/service/impl/WorkOrderServiceTest.java @@ -1,8 +1,18 @@ package com.ether.pms.ops.service.impl; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.repository.WorkOrderItemRepository; import com.ether.pms.ops.repository.WorkOrderRepository; +import com.ether.pms.ops.repository.WorkOrderStatusHistoryRepository; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -12,33 +22,22 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - /** * 工单服务测试类 (TDD) * - * 测试原则:RED(红) → GREEN(绿) → REFACTOR(重构) + *

测试原则:RED(红) → GREEN(绿) → REFACTOR(重构) */ @ExtendWith(MockitoExtension.class) @DisplayName("工单服务测试") class WorkOrderServiceTest { - @Mock - private WorkOrderRepository workOrderRepository; + @Mock private WorkOrderRepository workOrderRepository; - @Mock - private WorkOrderItemRepository workOrderItemRepository; + @Mock private WorkOrderItemRepository workOrderItemRepository; - @InjectMocks - private WorkOrderServiceImpl workOrderService; + @Mock private WorkOrderStatusHistoryRepository statusHistoryRepository; + + @InjectMocks private WorkOrderServiceImpl workOrderService; private WorkOrder testWorkOrder; private UUID testId; @@ -66,17 +65,25 @@ class WorkOrderServiceTest { @DisplayName("创建工单时应自动生成工单编号") void shouldGenerateWorkNoOnCreate() { when(workOrderRepository.findMaxWorkNoByPrefix(any())).thenReturn(null); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> { - WorkOrder saved = invocation.getArgument(0); - saved.setId(UUID.randomUUID()); - return saved; - }); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer( + invocation -> { + WorkOrder saved = invocation.getArgument(0); + saved.setId(UUID.randomUUID()); + return saved; + }); WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); assertNotNull(result.getWorkNo()); assertTrue(result.getWorkNo().startsWith("WO-")); - assertTrue(result.getWorkNo().contains(LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")))); + assertTrue( + result.getWorkNo() + .contains( + LocalDate.now() + .format( + java.time.format.DateTimeFormatter.ofPattern( + "yyyyMMdd")))); verify(workOrderRepository).save(any(WorkOrder.class)); } @@ -85,7 +92,8 @@ class WorkOrderServiceTest { void shouldSetDefaultStatusPending() { testWorkOrder.setStatus(null); when(workOrderRepository.findMaxWorkNoByPrefix(any())).thenReturn(null); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); @@ -97,7 +105,8 @@ class WorkOrderServiceTest { void shouldSetDefaultPriorityMedium() { testWorkOrder.setPriority(null); when(workOrderRepository.findMaxWorkNoByPrefix(any())).thenReturn(null); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); @@ -107,12 +116,17 @@ class WorkOrderServiceTest { @Test @DisplayName("创建工单时应递增序号") void shouldIncrementSequence() { - when(workOrderRepository.findMaxWorkNoByPrefix(any())).thenReturn("WO-20260405-0015"); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + String todayStr = + LocalDate.now() + .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd")); + when(workOrderRepository.findMaxWorkNoByPrefix(any())) + .thenReturn("WO-" + todayStr + "-0015"); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.createWorkOrder(testWorkOrder); - assertEquals("WO-20260405-0016", result.getWorkNo()); + assertEquals("WO-" + todayStr + "-0016", result.getWorkNo()); } } @@ -123,10 +137,13 @@ class WorkOrderServiceTest { @Test @DisplayName("派单:PENDING -> ASSIGNED") void assignWorkOrder_shouldChangeStatusToAssigned() { - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); - WorkOrder result = workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()); + WorkOrder result = + workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()); assertEquals(WorkOrder.Status.ASSIGNED, result.getStatus()); assertEquals("张三", result.getAssignedTo()); @@ -138,19 +155,22 @@ class WorkOrderServiceTest { @DisplayName("派单失败:只有PENDING状态才能派单") void assignWorkOrder_shouldFailWhenNotPending() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()) - ); + assertThrows( + RuntimeException.class, + () -> workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now())); } @Test @DisplayName("开始:ASSIGNED -> IN_PROGRESS") void startWorkOrder_shouldChangeStatusToInProgress() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.startWorkOrder(testId); @@ -162,11 +182,10 @@ class WorkOrderServiceTest { @DisplayName("开始失败:只有ASSIGNED状态才能开始") void startWorkOrder_shouldFailWhenNotAssigned() { testWorkOrder.setStatus(WorkOrder.Status.PENDING); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.startWorkOrder(testId) - ); + assertThrows(RuntimeException.class, () -> workOrderService.startWorkOrder(testId)); } @Test @@ -184,8 +203,10 @@ class WorkOrderServiceTest { completeData.setTotalCost(BigDecimal.valueOf(700)); completeData.setCompletedBy("李四"); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.completeWorkOrder(testId, completeData); @@ -202,19 +223,22 @@ class WorkOrderServiceTest { @DisplayName("完成失败:只有IN_PROGRESS状态才能完成") void completeWorkOrder_shouldFailWhenNotInProgress() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.completeWorkOrder(testId, new WorkOrder()) - ); + assertThrows( + RuntimeException.class, + () -> workOrderService.completeWorkOrder(testId, new WorkOrder())); } @Test @DisplayName("验收:COMPLETED -> VERIFIED") void verifyWorkOrder_shouldChangeStatusToVerified() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5); @@ -229,19 +253,22 @@ class WorkOrderServiceTest { @DisplayName("验收失败:只有COMPLETED状态才能验收") void verifyWorkOrder_shouldFailWhenNotCompleted() { testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5) - ); + assertThrows( + RuntimeException.class, + () -> workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5)); } @Test @DisplayName("验收评分应在1-5范围内") void verifyWorkOrder_shouldAcceptValidRating() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", null, 4); @@ -252,8 +279,10 @@ class WorkOrderServiceTest { @DisplayName("取消:PENDING/ASSIGNED/IN_PROGRESS -> CANCELLED") void cancelWorkOrder_shouldChangeStatusToCancelled() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder result = workOrderService.cancelWorkOrder(testId); @@ -264,22 +293,20 @@ class WorkOrderServiceTest { @DisplayName("取消失败:COMPLETED状态不能取消") void cancelWorkOrder_shouldFailWhenCompleted() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.cancelWorkOrder(testId) - ); + assertThrows(RuntimeException.class, () -> workOrderService.cancelWorkOrder(testId)); } @Test @DisplayName("取消失败:VERIFIED状态不能取消") void cancelWorkOrder_shouldFailWhenVerified() { testWorkOrder.setStatus(WorkOrder.Status.VERIFIED); - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); - assertThrows(RuntimeException.class, () -> - workOrderService.cancelWorkOrder(testId) - ); + assertThrows(RuntimeException.class, () -> workOrderService.cancelWorkOrder(testId)); } } @@ -290,7 +317,8 @@ class WorkOrderServiceTest { @Test @DisplayName("根据ID获取工单") void getWorkOrderById_shouldReturnWorkOrder() { - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); WorkOrder result = workOrderService.getWorkOrderById(testId); @@ -301,11 +329,10 @@ class WorkOrderServiceTest { @Test @DisplayName("根据ID获取工单失败时应抛出异常") void getWorkOrderById_shouldThrowExceptionWhenNotFound() { - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.empty()); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.empty()); - assertThrows(RuntimeException.class, () -> - workOrderService.getWorkOrderById(testId) - ); + assertThrows(RuntimeException.class, () -> workOrderService.getWorkOrderById(testId)); } } @@ -316,8 +343,10 @@ class WorkOrderServiceTest { @Test @DisplayName("逻辑删除工单应设置isDeleted为true") void deleteWorkOrder_shouldSetIsDeletedTrue() { - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); workOrderService.deleteWorkOrder(testId); @@ -333,8 +362,10 @@ class WorkOrderServiceTest { @Test @DisplayName("更新工单应保存所有字段") void updateWorkOrder_shouldUpdateAllFields() { - when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); - when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)) + .thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.save(any(WorkOrder.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder updateData = new WorkOrder(); updateData.setTitle("更新标题"); diff --git a/module-wo/src/test/resources/application.yml b/module-wo/src/test/resources/application.yml new file mode 100644 index 0000000..eb181b1 --- /dev/null +++ b/module-wo/src/test/resources/application.yml @@ -0,0 +1,22 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL + username: sa + password: + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create-drop + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + + h2: + console: + enabled: false + + sql: + init: + mode: never diff --git a/pom.xml b/pom.xml index 3eb338d..a9f5da1 100644 --- a/pom.xml +++ b/pom.xml @@ -119,37 +119,51 @@ - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - + + + com.diffplug.spotless + spotless-maven-plugin + 2.43.0 + + + + 1.18.1 + + + + + + + - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - ${java.version} - ${java.version} - true - - - org.projectlombok - lombok - ${lombok.version} - - - org.mapstruct - mapstruct-processor - ${mapstruct.version} - - - - - - + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + true + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + +