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 3459537..523ee84 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 @@ -25,17 +25,20 @@ public class EquipmentHealthController { @GetMapping("/{equipmentId}") public ApiResponse getEquipmentHealth(@PathVariable UUID equipmentId) { - return ApiResponse.success(equipmentHealthService.getLatestHealthScore(equipmentId)); + EquipmentHealthScore score = equipmentHealthService.getLatestHealthScore(equipmentId); + return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score); } @GetMapping("/{equipmentId}/history") public ApiResponse> getHealthHistory(@PathVariable UUID equipmentId) { - return ApiResponse.success(equipmentHealthService.getHealthHistory(equipmentId)); + List history = equipmentHealthService.getHealthHistory(equipmentId); + return ApiResponse.success("[Beta] 健康评分数据准确性待验证", history); } @PostMapping("/calculate") public ApiResponse calculateHealthScore(@Valid @RequestBody CalculateHealthRequest request) { - return ApiResponse.success(equipmentHealthService.calculateHealthScore(request.getEquipmentId())); + EquipmentHealthScore score = equipmentHealthService.calculateHealthScore(request.getEquipmentId()); + return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score); } @PostMapping("/failure-history") 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 7eea6a4..a549574 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 @@ -7,6 +7,9 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.UUID; +/** + * 此功能为Beta版本,数据准确性待验证 + */ @Entity @Table(name = "ops_equipment_health_score", indexes = { @Index(name = "idx_health_equipment", columnList = "equipment_id"), 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 ac3786b..52e07f7 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,12 +1,12 @@ 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.mdm.entity.SpaceNode; import com.ether.pms.asset.repository.EquipmentFailureHistoryRepository; import com.ether.pms.asset.repository.EquipmentHealthScoreRepository; -import com.ether.pms.mdm.repository.SpaceNodeRepository; +import com.ether.pms.asset.repository.EquipmentRepository; import com.ether.pms.asset.service.EquipmentHealthService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,7 +29,7 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { private final EquipmentFailureHistoryRepository failureHistoryRepository; private final EquipmentHealthScoreRepository healthScoreRepository; - private final SpaceNodeRepository spaceNodeRepository; + private final EquipmentRepository equipmentRepository; // TODO: 需要改为从 ops 模块查询工单数据 // private final MaintenanceTaskRepository maintenanceTaskRepository; @@ -43,13 +43,9 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { @Override @Transactional public EquipmentHealthScore calculateHealthScore(UUID equipmentId) { - SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(equipmentId) + Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(equipmentId) .orElseThrow(() -> new BusinessException(6001, "设备不存在")); - if (!Boolean.TRUE.equals(equipment.getIsEquipment())) { - throw new BusinessException(6002, "该空间节点不是设备"); - } - // 获取近30天故障次数 LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30); long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo); @@ -86,9 +82,9 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { // 创建健康度记录 EquipmentHealthScore health = new EquipmentHealthScore(); - health.setProjectId(UUID.fromString("00000000-0000-0000-0000-000000000000")); // 需要根据projectCode查询 + health.setProjectId(equipment.getProjectId()); health.setEquipmentId(equipmentId); - health.setEquipmentName(equipment.getName()); + health.setEquipmentName(equipment.getEquipmentName()); health.setHealthScore(healthScore.setScale(2, RoundingMode.HALF_UP)); health.setFailureDeduction(failureDeduction.setScale(2, RoundingMode.HALF_UP)); health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP)); @@ -167,21 +163,17 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { @Transactional public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) { // 校验设备存在 - SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId()) + Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId()) .orElseThrow(() -> new BusinessException(6001, "设备不存在")); - if (!Boolean.TRUE.equals(equipment.getIsEquipment())) { - throw new BusinessException(6002, "该空间节点不是设备"); - } - // 设置项目ID if (failure.getProjectId() == null) { - failure.setProjectId(UUID.fromString("00000000-0000-0000-0000-000000000000")); // 需要根据projectCode查询 + failure.setProjectId(equipment.getProjectId()); } // 设置设备信息 if (failure.getEquipmentName() == null) { - failure.setEquipmentName(equipment.getName()); + failure.setEquipmentName(equipment.getEquipmentName()); } // 计算修复时长 @@ -219,8 +211,8 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService { /** * 计算设备年龄(年) */ - private BigDecimal calculateEquipmentAge(SpaceNode equipment) { - LocalDate installationDate = equipment.getMaintenanceContractStart(); + private BigDecimal calculateEquipmentAge(Equipment equipment) { + LocalDate installationDate = equipment.getInstallationDate(); if (installationDate == null) { // 如果没有安装日期,使用创建日期 if (equipment.getCreatedAt() != null) { 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 index bf0ba09..e4b354d 100644 --- 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 @@ -21,6 +21,7 @@ public class DataAccessController { private final DataAccessService dataAccessService; + @Deprecated @PostMapping public ResponseEntity> grantAccess(@Valid @RequestBody DataAccessRequest request) { UUID currentUserId = SecurityUtils.getCurrentUserId(); @@ -38,12 +39,14 @@ public class DataAccessController { 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, 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 12bb5b1..e59d9db 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 @@ -8,6 +8,7 @@ import com.ether.pms.auth.entity.User; import com.ether.pms.auth.service.RoleService; import com.ether.pms.common.ApiResponse; import jakarta.validation.Valid; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -87,7 +88,7 @@ public class RoleController { * @return 包含该项目角色列表的响应 */ @GetMapping("/project/{projectId}") - public ResponseEntity>> findByProjectId(@PathVariable String projectId) { + public ResponseEntity>> findByProjectId(@PathVariable UUID projectId) { return ResponseEntity.ok(ApiResponse.success(roleService.findByProjectId(projectId))); } 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 index 797154c..ef2718c 100644 --- 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 @@ -8,6 +8,7 @@ import java.util.UUID; @Entity @Table(name = "biz_data_access") @Data +@Deprecated public class DataAccess { @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 5f6a5fc..5aaedcf 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 @@ -98,8 +98,23 @@ public class Permission { * 排序序号 * 用于前端展示时的排序,数字越小越靠前 */ + @Size(max = 200, message = "路由路径长度不能超过200位") + @Column(length = 200) + private String path; + + @Size(max = 200, message = "组件路径长度不能超过200位") + @Column(length = 200) + private String component; + + @Size(max = 100, message = "图标名称长度不能超过100位") + @Column(length = 100) + private String icon; + private Integer sortOrder; + @Column(nullable = false) + private Boolean visible = true; + /** * 权限关联的角色列表 * 多对多关系,通过auth_role_permission关联表维护 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 e355c55..2874a19 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 @@ -33,14 +33,14 @@ class PasswordServiceTest { } @Test - void isPasswordWeak_shouldReturnTrue_forCommonWeakPasswords() { - assertTrue(passwordService.isPasswordWeak("password123")); - assertTrue(passwordService.isPasswordWeak("admin123")); - assertTrue(passwordService.isPasswordWeak("qwerty123")); + void isWeakPassword_shouldReturnTrue_forCommonWeakPasswords() { + assertTrue(passwordService.isWeakPassword("password123")); + assertTrue(passwordService.isWeakPassword("admin123")); + assertTrue(passwordService.isWeakPassword("qwerty123")); } @Test - void isPasswordWeak_shouldReturnFalse_forStrongPasswords() { - assertFalse(passwordService.isPasswordWeak("Str0ng!Pass")); + void isWeakPassword_shouldReturnFalse_forStrongPasswords() { + assertFalse(passwordService.isWeakPassword("Str0ng!Pass")); } } 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 2418b30..db29350 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 @@ -4,6 +4,7 @@ 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.common.BusinessException; +import com.ether.pms.common.ErrorCode; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -86,8 +87,8 @@ class UserServiceTest { newUser.setPassword("weak"); when(userRepository.existsByUsername("newuser")).thenReturn(false); - doThrow(new IllegalArgumentException("密码太弱")) - .when(passwordService).validatePassword("weak"); + doThrow(new BusinessException(ErrorCode.PASSWORD_001)) + .when(passwordService).validateStrength("weak"); assertThrows(BusinessException.class, () -> userService.create(newUser)); } @@ -99,8 +100,8 @@ class UserServiceTest { newUser.setPassword("Valid123!"); when(userRepository.existsByUsername("newuser")).thenReturn(false); - doNothing().when(passwordService).validatePassword("Valid123!"); - when(passwordService.isPasswordWeak("Valid123!")).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); 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 2bcbc80..e2aa8e4 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 @@ -125,7 +125,9 @@ public class SpaceNodeController { /** * 获取设备详情 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @GetMapping("/{id}/equipment") public ResponseEntity> getEquipment(@PathVariable UUID id) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getEquipmentById(id))); @@ -133,7 +135,9 @@ public class SpaceNodeController { /** * 获取设备列表 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @GetMapping("/equipment") public ResponseEntity>> getEquipmentList( @RequestParam UUID projectId) { @@ -142,7 +146,9 @@ public class SpaceNodeController { /** * 获取特种设备列表 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @GetMapping("/special-equipment") public ResponseEntity>> getSpecialEquipment( @RequestParam UUID projectId) { @@ -151,7 +157,9 @@ public class SpaceNodeController { /** * 获取即将年检设备 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @GetMapping("/expiring-inspection") public ResponseEntity>> getExpiringInspection( @RequestParam UUID projectId, @@ -161,7 +169,9 @@ public class SpaceNodeController { /** * 创建设备 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @PostMapping("/equipment") public ResponseEntity> createEquipment(@Valid @RequestBody EquipmentCreateDTO dto) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.createEquipment(dto))); @@ -169,7 +179,9 @@ public class SpaceNodeController { /** * 批量创建设备 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @PostMapping("/equipment/batch") public ResponseEntity>> batchCreateEquipment(@Valid @RequestBody List dtoList) { return ResponseEntity.ok(ApiResponse.success(spaceNodeService.batchCreateEquipment(dtoList))); @@ -177,7 +189,9 @@ public class SpaceNodeController { /** * Excel导入设备 + * @deprecated 请使用 /api/asset/equipment 替代 */ + @Deprecated @PostMapping("/equipment/import") public ResponseEntity> importEquipment( @RequestParam("file") MultipartFile file, 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 5879d29..15b2821 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 @@ -13,6 +13,8 @@ public class SpaceNodeCreateDTO { @NotNull(message = "项目ID不能为空") private UUID projectId; + private String code; + @NotBlank(message = "空间节点名称不能为空") private String name; 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 9985125..3512ff8 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 @@ -1,5 +1,6 @@ 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; @@ -32,6 +33,10 @@ public class SpaceNode { @Column(name = "project_code", nullable = false, length = 50) private UUID projectId; + @JsonIgnore + @Column(name = "code", length = 50) + private String code; + @NotNull(message = "空间节点名称不能为空") @Size(min = 1, max = 100, message = "空间节点名称长度必须在1-100位之间") @Column(nullable = false, length = 100) @@ -137,67 +142,88 @@ public class SpaceNode { @Column(name = "is_deleted") private Boolean isDeleted = false; - // ========== 设备扩展字段 ========== + // ========== 设备扩展字段(@Deprecated:请使用 module-asset 的 Equipment 实体) ========== + @Deprecated @Column(name = "is_equipment") private Boolean isEquipment = false; + @Deprecated @Column(name = "design_life_years") private Integer designLifeYears; + @Deprecated @Column(name = "rated_power", precision = 10, scale = 2) private BigDecimal ratedPower; + @Deprecated @Column(name = "rated_voltage", precision = 10, scale = 2) private BigDecimal ratedVoltage; + @Deprecated @Column(name = "rated_current", precision = 10, scale = 2) private BigDecimal ratedCurrent; + @Deprecated @Column(name = "maintenance_vendor", length = 100) private String maintenanceVendor; + @Deprecated @Column(name = "maintenance_vendor_contact", length = 50) private String maintenanceVendorContact; + @Deprecated @Column(name = "maintenance_vendor_phone", length = 20) private String maintenanceVendorPhone; + @Deprecated @Column(name = "maintenance_contract_no", length = 50) private String maintenanceContractNo; + @Deprecated @Column(name = "maintenance_contract_start") private LocalDate maintenanceContractStart; + @Deprecated @Column(name = "maintenance_contract_end") private LocalDate maintenanceContractEnd; + @Deprecated @Column(name = "special_equipment_type", length = 50) private String specialEquipmentType; + @Deprecated @Column(name = "special_equipment_cert", length = 100) private String specialEquipmentCert; + @Deprecated @Column(name = "inspection_cycle") private Integer inspectionCycle; + @Deprecated @Column(name = "next_inspection_date") private LocalDate nextInspectionDate; + @Deprecated @Column(name = "last_inspection_date") private LocalDate lastInspectionDate; + @Deprecated @Column(name = "last_inspection_result", length = 20) private String lastInspectionResult; + @Deprecated @Column(name = "common_spare_parts", length = 2000) private String commonSpareParts; + @Deprecated @Column(name = "energy_consumption_standard", precision = 12, scale = 2) private BigDecimal energyConsumptionStandard; + @Deprecated @Column(name = "installation_environment", length = 50) private String installationEnvironment; + @Deprecated @Column(name = "protection_level", length = 20) private String protectionLevel; // ========== 设备扩展字段结束 ========== 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 017de54..5fcda96 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 @@ -65,4 +65,7 @@ public interface SpaceNodeRepository extends JpaRepository { * 统计项目下指定类型空间数量 */ 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); } 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 1e94022..0774d87 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 @@ -10,7 +10,9 @@ 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.repository.ProjectRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,6 +29,7 @@ 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; @@ -40,6 +43,7 @@ import com.ether.pms.mdm.dto.EquipmentCreateDTO; public class SpaceNodeService { private final SpaceNodeRepository spaceNodeRepository; + private final ProjectRepository projectRepository; private final ObjectMapper objectMapper; /** @@ -142,6 +146,13 @@ public class SpaceNodeService { node.setNodeCategory(nodeType.getCategory()); node.setNodeType(nodeType); } + + if (dto.getCode() != null && !dto.getCode().isBlank()) { + node.setCode(dto.getCode()); + } else { + node.setCode(generateCode(dto.getProjectId(), node.getNodeType())); + } + node.setUsageType(dto.getUsageType()); node.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0); node.setStatus(dto.getStatus() != null ? dto.getStatus() : "ACTIVE"); @@ -734,6 +745,46 @@ public class SpaceNodeService { /** * 楼栋楼层配置(从JSON解析) */ + private String generateCode(UUID projectId, SpaceNode.NodeType nodeType) { + 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 prefix = getCodePrefix(nodeType); + String codePrefix = prefix + "-" + projSuffix + "-"; + + Optional maxCode = spaceNodeRepository.findMaxCodeByProjectAndTypeAndPrefix( + projectId, nodeType, codePrefix); + + int nextSeq = 1; + if (maxCode.isPresent() && maxCode.get() != null) { + String max = maxCode.get(); + String seqPart = max.substring(codePrefix.length()); + try { + nextSeq = Integer.parseInt(seqPart) + 1; + } catch (NumberFormatException e) { + nextSeq = 1; + } + } + + return codePrefix + String.format("%04d", nextSeq); + } + + private String getCodePrefix(SpaceNode.NodeType nodeType) { + return switch (nodeType) { + case BUILDING -> "BLD"; + case UNIT -> "UNT"; + case FLOOR -> "FLR"; + case ROOM -> "RM"; + default -> nodeType.name().length() >= 3 + ? nodeType.name().substring(0, 3) + : nodeType.name(); + }; + } + @Data public static class BuildingFloorConfig { private Integer totalFloors; diff --git a/module-wo/pom.xml b/module-wo/pom.xml index c5a43a9..fefc1df 100644 --- a/module-wo/pom.xml +++ b/module-wo/pom.xml @@ -65,5 +65,11 @@ org.mapstruct mapstruct + + + org.springframework.boot + spring-boot-starter-test + test + 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 23a0507..83e2b56 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 @@ -2,6 +2,7 @@ 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.WorkOrderStatsDTO; import com.ether.pms.ops.entity.WorkOrder; import com.ether.pms.ops.entity.WorkOrderItem; @@ -31,30 +32,19 @@ public class WorkOrderController { } @GetMapping - public ApiResponse> list( + public ApiResponse> list( @RequestParam(required = false) UUID projectId, @RequestParam(required = false) UUID equipmentId, @RequestParam(required = false) WorkOrder.Source source, @RequestParam(required = false) WorkOrder.Type type, @RequestParam(required = false) WorkOrder.Status status, - @RequestParam(required = false) String assignedTo) { - List list; - if (projectId != null) { - list = workOrderService.getWorkOrdersByProject(projectId); - } else if (equipmentId != null) { - list = workOrderService.getWorkOrdersByEquipment(equipmentId); - } else if (source != null) { - list = workOrderService.getWorkOrdersBySource(source); - } else if (type != null) { - list = workOrderService.getWorkOrdersByType(type); - } else if (status != null) { - list = workOrderService.getWorkOrdersByStatus(status); - } else if (assignedTo != null) { - list = workOrderService.getWorkOrdersByAssignedTo(assignedTo); - } else { - list = workOrderService.getAllWorkOrders(); - } - return ApiResponse.success(list); + @RequestParam(required = false) WorkOrder.Priority priority, + @RequestParam(required = false) String assignedTo, + @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)); } @GetMapping("/{id}") @@ -98,6 +88,21 @@ public class WorkOrderController { return ApiResponse.success(workOrderService.cancelWorkOrder(id)); } + @PostMapping("/{id}/suspend") + public ApiResponse suspend(@PathVariable UUID id) { + return ApiResponse.success(workOrderService.suspendWorkOrder(id)); + } + + @PostMapping("/{id}/resume") + public ApiResponse resume(@PathVariable UUID id) { + return ApiResponse.success(workOrderService.resumeWorkOrder(id)); + } + + @PostMapping("/{id}/return") + public ApiResponse returnOrder(@PathVariable UUID id) { + return ApiResponse.success(workOrderService.returnWorkOrder(id)); + } + @GetMapping("/stats") public ApiResponse stats() { return ApiResponse.success(workOrderService.getWorkOrderStats()); 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 450d30a..4635cbf 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 @@ -16,6 +16,8 @@ public class WorkOrderStatsDTO { private long completed; private long verified; private long cancelled; + private long suspended; + private long returned; private long completedToday; private long createdToday; private long overdue; 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 9101b73..6412bf3 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 @@ -57,9 +57,13 @@ public class WorkOrder { private Status status = Status.PENDING; public enum Status { - PENDING, ASSIGNED, IN_PROGRESS, COMPLETED, VERIFIED, CANCELLED + PENDING, ASSIGNED, IN_PROGRESS, SUSPENDED, RETURNED, COMPLETED, VERIFIED, CANCELLED } + @Column(name = "previous_status", length = 20) + @Enumerated(EnumType.STRING) + private Status previousStatus; + @Column(nullable = false, length = 200) private String title; @@ -147,6 +151,9 @@ public class WorkOrder { @Column(length = 2000) private String signature; + @Column(name = "is_deleted") + private Boolean isDeleted = false; + @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; @@ -160,6 +167,9 @@ public class WorkOrder { public void prePersist() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); + if (isDeleted == null) { + isDeleted = false; + } } @PreUpdate 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 c8209df..3967316 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,7 +1,10 @@ package com.ether.pms.ops.repository; import com.ether.pms.ops.entity.WorkOrder; +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.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -13,51 +16,87 @@ import java.util.Optional; import java.util.UUID; @Repository -public interface WorkOrderRepository extends JpaRepository { - Optional findByWorkNo(String workNo); +public interface WorkOrderRepository extends JpaRepository, JpaSpecificationExecutor { + Optional findByWorkNoAndIsDeletedFalse(String workNo); - List findByProjectId(UUID projectId); + List findByProjectIdAndIsDeletedFalse(UUID projectId); - List findByEquipmentId(UUID equipmentId); + List findByEquipmentIdAndIsDeletedFalse(UUID equipmentId); - List findBySource(WorkOrder.Source source); + List findBySourceAndIsDeletedFalse(WorkOrder.Source source); - List findByType(WorkOrder.Type type); + List findByTypeAndIsDeletedFalse(WorkOrder.Type type); - List findByStatus(WorkOrder.Status status); + List findByStatusAndIsDeletedFalse(WorkOrder.Status status); - List findByAssignedTo(String assignedTo); + List findByAssignedToAndIsDeletedFalse(String assignedTo); - @Query("SELECT w FROM WorkOrder w WHERE 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") String findMaxWorkNoByPrefix(@Param("prefix") String prefix); - long countByStatus(WorkOrder.Status status); + long countByStatusAndIsDeletedFalse(WorkOrder.Status status); - @Query("SELECT COUNT(w) FROM WorkOrder w WHERE 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.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 GROUP BY w.status") + @Query("SELECT w.status, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.status") List countByStatus(); - @Query("SELECT w.source, COUNT(w) FROM WorkOrder w GROUP BY w.source") + @Query("SELECT w.source, COUNT(w) FROM WorkOrder w WHERE w.isDeleted = false GROUP BY w.source") List countBySource(); - @Query("SELECT w.type, COUNT(w) FROM WorkOrder w GROUP BY w.type") + @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 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.createdAt >= :startDate AND w.createdAt < :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 findByPlanId(UUID planId); + List findByPlanIdAndIsDeletedFalse(UUID planId); - List findByPlanIdAndCreatedAtBetween(UUID planId, LocalDateTime startDate, LocalDateTime endDate); + List findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse(UUID planId, LocalDateTime startDate, LocalDateTime endDate); + + Page findByProjectIdAndIsDeletedFalse(UUID projectId, Pageable pageable); + + Page findByEquipmentIdAndIsDeletedFalse(UUID equipmentId, Pageable pageable); + + Page findBySourceAndIsDeletedFalse(WorkOrder.Source source, Pageable pageable); + + Page findByTypeAndIsDeletedFalse(WorkOrder.Type type, Pageable pageable); + + Page findByStatusAndIsDeletedFalse(WorkOrder.Status status, Pageable pageable); + + 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); + + Optional findByIdAndIsDeletedFalse(UUID id); + + List findAllByIsDeletedFalse(); } \ No newline at end of file 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 bab7f04..b9b4d52 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 @@ -108,7 +108,7 @@ public class MaintenanceScheduler { LocalDateTime todayStart = today.atStartOfDay(); LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay(); - List todayTasks = workOrderRepository.findByPlanIdAndCreatedAtBetween( + List todayTasks = workOrderRepository.findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse( planId, todayStart, tomorrowStart); return !todayTasks.isEmpty(); 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 38bb7cd..4098188 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 @@ -1,5 +1,6 @@ package com.ether.pms.ops.service; +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; @@ -23,11 +24,19 @@ public interface WorkOrderService { List getWorkOrdersByAssignedTo(String assignedTo); 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); + WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate); 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); WorkOrderStatsDTO getWorkOrderStats(); 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 9e939df..48a1c8c 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,12 +1,18 @@ package com.ether.pms.ops.service.impl; +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.repository.WorkOrderItemRepository; import com.ether.pms.ops.repository.WorkOrderRepository; 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; @@ -71,12 +77,15 @@ public class WorkOrderServiceImpl implements WorkOrderService { @Override @Transactional public void deleteWorkOrder(UUID id) { - workOrderRepository.deleteById(id); + WorkOrder workOrder = workOrderRepository.findByIdAndIsDeletedFalse(id) + .orElseThrow(() -> new RuntimeException("工单不存在: " + id)); + workOrder.setIsDeleted(true); + workOrderRepository.save(workOrder); } @Override public WorkOrder getWorkOrderById(UUID id) { - return workOrderRepository.findById(id) + return workOrderRepository.findByIdAndIsDeletedFalse(id) .orElseThrow(() -> new RuntimeException("工单不存在: " + id)); } @@ -86,37 +95,37 @@ public class WorkOrderServiceImpl implements WorkOrderService { // 此方法用于管理后台的工单列表展示 // TODO: 必须改为分页查询,建议添加 Pageable 参数 // 临时方案:限制返回最近 1000 条记录 - return workOrderRepository.findAll(); + return workOrderRepository.findAllByIsDeletedFalse(); } @Override public List getWorkOrdersByProject(UUID projectId) { - return workOrderRepository.findByProjectId(projectId); + return workOrderRepository.findByProjectIdAndIsDeletedFalse(projectId); } @Override public List getWorkOrdersByEquipment(UUID equipmentId) { - return workOrderRepository.findByEquipmentId(equipmentId); + return workOrderRepository.findByEquipmentIdAndIsDeletedFalse(equipmentId); } @Override public List getWorkOrdersBySource(WorkOrder.Source source) { - return workOrderRepository.findBySource(source); + return workOrderRepository.findBySourceAndIsDeletedFalse(source); } @Override public List getWorkOrdersByType(WorkOrder.Type type) { - return workOrderRepository.findByType(type); + return workOrderRepository.findByTypeAndIsDeletedFalse(type); } @Override public List getWorkOrdersByStatus(WorkOrder.Status status) { - return workOrderRepository.findByStatus(status); + return workOrderRepository.findByStatusAndIsDeletedFalse(status); } @Override public List getWorkOrdersByAssignedTo(String assignedTo) { - return workOrderRepository.findByAssignedTo(assignedTo); + return workOrderRepository.findByAssignedToAndIsDeletedFalse(assignedTo); } @Override @@ -124,6 +133,24 @@ public class WorkOrderServiceImpl implements WorkOrderService { return workOrderRepository.findOverdueTasks(date); } + @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) { + 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); + return PageResponse.of( + workOrderPage.getContent(), + workOrderPage.getNumber(), + workOrderPage.getSize(), + workOrderPage.getTotalElements() + ); + } + @Override @Transactional public WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate) { @@ -226,6 +253,55 @@ public class WorkOrderServiceImpl implements WorkOrderService { return workOrderRepository.save(workOrder); } + @Override + @Transactional + public WorkOrder suspendWorkOrder(UUID id) { + WorkOrder workOrder = getWorkOrderById(id); + + if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED && + workOrder.getStatus() != WorkOrder.Status.IN_PROGRESS) { + throw new RuntimeException("只能挂起已派单或执行中的工单"); + } + + workOrder.setPreviousStatus(workOrder.getStatus()); + workOrder.setStatus(WorkOrder.Status.SUSPENDED); + return workOrderRepository.save(workOrder); + } + + @Override + @Transactional + public WorkOrder resumeWorkOrder(UUID id) { + 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.setPreviousStatus(null); + workOrder.setStatus(targetStatus); + return workOrderRepository.save(workOrder); + } + + @Override + @Transactional + public WorkOrder returnWorkOrder(UUID id) { + WorkOrder workOrder = getWorkOrderById(id); + + if (workOrder.getStatus() != WorkOrder.Status.ASSIGNED) { + throw new RuntimeException("只能退回已派单的工单"); + } + + workOrder.setPreviousStatus(null); + workOrder.setAssignedTo(null); + workOrder.setAssignedVendor(null); + workOrder.setAssignedDate(null); + workOrder.setStatus(WorkOrder.Status.PENDING); + return workOrderRepository.save(workOrder); + } + @Override public WorkOrderStatsDTO getWorkOrderStats() { LocalDate today = LocalDate.now(); @@ -278,6 +354,8 @@ public class WorkOrderServiceImpl implements WorkOrderService { .completed(byStatus.getOrDefault("COMPLETED", 0L)) .verified(byStatus.getOrDefault("VERIFIED", 0L)) .cancelled(byStatus.getOrDefault("CANCELLED", 0L)) + .suspended(byStatus.getOrDefault("SUSPENDED", 0L)) + .returned(byStatus.getOrDefault("RETURNED", 0L)) .completedToday(workOrderRepository.countCompletedToday(today)) .createdToday(workOrderRepository.countByCreatedAtBetween(startOfToday, endOfToday)) .overdue(workOrderRepository.countOverdue(today)) 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 ccfffa8..46b5b58 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 @@ -123,7 +123,7 @@ class WorkOrderServiceTest { @Test @DisplayName("派单:PENDING -> ASSIGNED") void assignWorkOrder_shouldChangeStatusToAssigned() { - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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()); @@ -138,7 +138,7 @@ class WorkOrderServiceTest { @DisplayName("派单失败:只有PENDING状态才能派单") void assignWorkOrder_shouldFailWhenNotPending() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now()) @@ -149,7 +149,7 @@ class WorkOrderServiceTest { @DisplayName("开始:ASSIGNED -> IN_PROGRESS") void startWorkOrder_shouldChangeStatusToInProgress() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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,7 +162,7 @@ class WorkOrderServiceTest { @DisplayName("开始失败:只有ASSIGNED状态才能开始") void startWorkOrder_shouldFailWhenNotAssigned() { testWorkOrder.setStatus(WorkOrder.Status.PENDING); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.startWorkOrder(testId) @@ -184,7 +184,7 @@ class WorkOrderServiceTest { completeData.setTotalCost(BigDecimal.valueOf(700)); completeData.setCompletedBy("李四"); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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,7 +202,7 @@ class WorkOrderServiceTest { @DisplayName("完成失败:只有IN_PROGRESS状态才能完成") void completeWorkOrder_shouldFailWhenNotInProgress() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.completeWorkOrder(testId, new WorkOrder()) @@ -213,7 +213,7 @@ class WorkOrderServiceTest { @DisplayName("验收:COMPLETED -> VERIFIED") void verifyWorkOrder_shouldChangeStatusToVerified() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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,7 +229,7 @@ class WorkOrderServiceTest { @DisplayName("验收失败:只有COMPLETED状态才能验收") void verifyWorkOrder_shouldFailWhenNotCompleted() { testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5) @@ -240,7 +240,7 @@ class WorkOrderServiceTest { @DisplayName("验收评分应在1-5范围内") void verifyWorkOrder_shouldAcceptValidRating() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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,7 +252,7 @@ class WorkOrderServiceTest { @DisplayName("取消:PENDING/ASSIGNED/IN_PROGRESS -> CANCELLED") void cancelWorkOrder_shouldChangeStatusToCancelled() { testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + 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,7 +264,7 @@ class WorkOrderServiceTest { @DisplayName("取消失败:COMPLETED状态不能取消") void cancelWorkOrder_shouldFailWhenCompleted() { testWorkOrder.setStatus(WorkOrder.Status.COMPLETED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.cancelWorkOrder(testId) @@ -275,7 +275,7 @@ class WorkOrderServiceTest { @DisplayName("取消失败:VERIFIED状态不能取消") void cancelWorkOrder_shouldFailWhenVerified() { testWorkOrder.setStatus(WorkOrder.Status.VERIFIED); - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); assertThrows(RuntimeException.class, () -> workOrderService.cancelWorkOrder(testId) @@ -290,7 +290,7 @@ class WorkOrderServiceTest { @Test @DisplayName("根据ID获取工单") void getWorkOrderById_shouldReturnWorkOrder() { - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); WorkOrder result = workOrderService.getWorkOrderById(testId); @@ -301,7 +301,7 @@ class WorkOrderServiceTest { @Test @DisplayName("根据ID获取工单失败时应抛出异常") void getWorkOrderById_shouldThrowExceptionWhenNotFound() { - when(workOrderRepository.findById(testId)).thenReturn(Optional.empty()); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.empty()); assertThrows(RuntimeException.class, () -> workOrderService.getWorkOrderById(testId) @@ -314,11 +314,15 @@ class WorkOrderServiceTest { class DeleteTests { @Test - @DisplayName("删除工单应调用repository") - void deleteWorkOrder_shouldCallRepository() { + @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)); + workOrderService.deleteWorkOrder(testId); - verify(workOrderRepository).deleteById(testId); + assertTrue(testWorkOrder.getIsDeleted()); + verify(workOrderRepository).save(testWorkOrder); } } @@ -329,7 +333,7 @@ class WorkOrderServiceTest { @Test @DisplayName("更新工单应保存所有字段") void updateWorkOrder_shouldUpdateAllFields() { - when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder)); + when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder)); when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0)); WorkOrder updateData = new WorkOrder();