feat: 文档整理+代码修复
- 工单分页查询(P0 OOM修复) - 前后端枚举统一(维保触发类型/任务状态) - Permission增加菜单路由属性(path/component/icon/visible) - 工单状态机补全(SUSPENDED/RETURNED/previousStatus) - SpaceNode增加code字段+自动编码规则 - 设备模型统一(SpaceNode设备扩展字段@Deprecated) - WorkOrder逻辑删除(isDeleted) - 健康评分Bug修复(SpaceNode→Equipment, projectId硬编码→动态) - DataAccess降级@Deprecated - 健康评分标注Beta - PasswordServiceTest/UserServiceTest方法名修复 - module-wo pom.xml添加test依赖 - RoleController参数类型修复
This commit is contained in:
parent
680ebe932c
commit
34c51288db
|
|
@ -25,17 +25,20 @@ public class EquipmentHealthController {
|
||||||
|
|
||||||
@GetMapping("/{equipmentId}")
|
@GetMapping("/{equipmentId}")
|
||||||
public ApiResponse<EquipmentHealthScore> getEquipmentHealth(@PathVariable UUID equipmentId) {
|
public ApiResponse<EquipmentHealthScore> getEquipmentHealth(@PathVariable UUID equipmentId) {
|
||||||
return ApiResponse.success(equipmentHealthService.getLatestHealthScore(equipmentId));
|
EquipmentHealthScore score = equipmentHealthService.getLatestHealthScore(equipmentId);
|
||||||
|
return ApiResponse.success("[Beta] 健康评分数据准确性待验证", score);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{equipmentId}/history")
|
@GetMapping("/{equipmentId}/history")
|
||||||
public ApiResponse<List<EquipmentHealthScore>> getHealthHistory(@PathVariable UUID equipmentId) {
|
public ApiResponse<List<EquipmentHealthScore>> getHealthHistory(@PathVariable UUID equipmentId) {
|
||||||
return ApiResponse.success(equipmentHealthService.getHealthHistory(equipmentId));
|
List<EquipmentHealthScore> history = equipmentHealthService.getHealthHistory(equipmentId);
|
||||||
|
return ApiResponse.success("[Beta] 健康评分数据准确性待验证", history);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/calculate")
|
@PostMapping("/calculate")
|
||||||
public ApiResponse<EquipmentHealthScore> calculateHealthScore(@Valid @RequestBody CalculateHealthRequest request) {
|
public ApiResponse<EquipmentHealthScore> 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")
|
@PostMapping("/failure-history")
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 此功能为Beta版本,数据准确性待验证
|
||||||
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "ops_equipment_health_score", indexes = {
|
@Table(name = "ops_equipment_health_score", indexes = {
|
||||||
@Index(name = "idx_health_equipment", columnList = "equipment_id"),
|
@Index(name = "idx_health_equipment", columnList = "equipment_id"),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.ether.pms.asset.service.impl;
|
package com.ether.pms.asset.service.impl;
|
||||||
|
|
||||||
import com.ether.pms.common.BusinessException;
|
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.EquipmentFailureHistory;
|
||||||
import com.ether.pms.asset.entity.EquipmentHealthScore;
|
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.EquipmentFailureHistoryRepository;
|
||||||
import com.ether.pms.asset.repository.EquipmentHealthScoreRepository;
|
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 com.ether.pms.asset.service.EquipmentHealthService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -29,7 +29,7 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
|
||||||
|
|
||||||
private final EquipmentFailureHistoryRepository failureHistoryRepository;
|
private final EquipmentFailureHistoryRepository failureHistoryRepository;
|
||||||
private final EquipmentHealthScoreRepository healthScoreRepository;
|
private final EquipmentHealthScoreRepository healthScoreRepository;
|
||||||
private final SpaceNodeRepository spaceNodeRepository;
|
private final EquipmentRepository equipmentRepository;
|
||||||
// TODO: 需要改为从 ops 模块查询工单数据
|
// TODO: 需要改为从 ops 模块查询工单数据
|
||||||
// private final MaintenanceTaskRepository maintenanceTaskRepository;
|
// private final MaintenanceTaskRepository maintenanceTaskRepository;
|
||||||
|
|
||||||
|
|
@ -43,13 +43,9 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
|
public EquipmentHealthScore calculateHealthScore(UUID equipmentId) {
|
||||||
SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(equipmentId)
|
Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(equipmentId)
|
||||||
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
||||||
|
|
||||||
if (!Boolean.TRUE.equals(equipment.getIsEquipment())) {
|
|
||||||
throw new BusinessException(6002, "该空间节点不是设备");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取近30天故障次数
|
// 获取近30天故障次数
|
||||||
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
|
||||||
long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo);
|
long failureCount30d = failureHistoryRepository.countByEquipmentIdSince(equipmentId, thirtyDaysAgo);
|
||||||
|
|
@ -86,9 +82,9 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
|
||||||
|
|
||||||
// 创建健康度记录
|
// 创建健康度记录
|
||||||
EquipmentHealthScore health = new EquipmentHealthScore();
|
EquipmentHealthScore health = new EquipmentHealthScore();
|
||||||
health.setProjectId(UUID.fromString("00000000-0000-0000-0000-000000000000")); // 需要根据projectCode查询
|
health.setProjectId(equipment.getProjectId());
|
||||||
health.setEquipmentId(equipmentId);
|
health.setEquipmentId(equipmentId);
|
||||||
health.setEquipmentName(equipment.getName());
|
health.setEquipmentName(equipment.getEquipmentName());
|
||||||
health.setHealthScore(healthScore.setScale(2, RoundingMode.HALF_UP));
|
health.setHealthScore(healthScore.setScale(2, RoundingMode.HALF_UP));
|
||||||
health.setFailureDeduction(failureDeduction.setScale(2, RoundingMode.HALF_UP));
|
health.setFailureDeduction(failureDeduction.setScale(2, RoundingMode.HALF_UP));
|
||||||
health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP));
|
health.setMaintenanceDeduction(maintenanceDeduction.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
|
@ -167,21 +163,17 @@ public class EquipmentHealthServiceImpl implements EquipmentHealthService {
|
||||||
@Transactional
|
@Transactional
|
||||||
public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) {
|
public EquipmentFailureHistory recordFailure(EquipmentFailureHistory failure) {
|
||||||
// 校验设备存在
|
// 校验设备存在
|
||||||
SpaceNode equipment = spaceNodeRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId())
|
Equipment equipment = equipmentRepository.findByIdAndIsDeletedFalse(failure.getEquipmentId())
|
||||||
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
.orElseThrow(() -> new BusinessException(6001, "设备不存在"));
|
||||||
|
|
||||||
if (!Boolean.TRUE.equals(equipment.getIsEquipment())) {
|
|
||||||
throw new BusinessException(6002, "该空间节点不是设备");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置项目ID
|
// 设置项目ID
|
||||||
if (failure.getProjectId() == null) {
|
if (failure.getProjectId() == null) {
|
||||||
failure.setProjectId(UUID.fromString("00000000-0000-0000-0000-000000000000")); // 需要根据projectCode查询
|
failure.setProjectId(equipment.getProjectId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置设备信息
|
// 设置设备信息
|
||||||
if (failure.getEquipmentName() == null) {
|
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) {
|
private BigDecimal calculateEquipmentAge(Equipment equipment) {
|
||||||
LocalDate installationDate = equipment.getMaintenanceContractStart();
|
LocalDate installationDate = equipment.getInstallationDate();
|
||||||
if (installationDate == null) {
|
if (installationDate == null) {
|
||||||
// 如果没有安装日期,使用创建日期
|
// 如果没有安装日期,使用创建日期
|
||||||
if (equipment.getCreatedAt() != null) {
|
if (equipment.getCreatedAt() != null) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public class DataAccessController {
|
||||||
|
|
||||||
private final DataAccessService dataAccessService;
|
private final DataAccessService dataAccessService;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<ApiResponse<Void>> grantAccess(@Valid @RequestBody DataAccessRequest request) {
|
public ResponseEntity<ApiResponse<Void>> grantAccess(@Valid @RequestBody DataAccessRequest request) {
|
||||||
UUID currentUserId = SecurityUtils.getCurrentUserId();
|
UUID currentUserId = SecurityUtils.getCurrentUserId();
|
||||||
|
|
@ -38,12 +39,14 @@ public class DataAccessController {
|
||||||
return ResponseEntity.ok(ApiResponse.success());
|
return ResponseEntity.ok(ApiResponse.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity<ApiResponse<Void>> revokeAccess(@PathVariable UUID id) {
|
public ResponseEntity<ApiResponse<Void>> revokeAccess(@PathVariable UUID id) {
|
||||||
dataAccessService.revokeAccess(id);
|
dataAccessService.revokeAccess(id);
|
||||||
return ResponseEntity.ok(ApiResponse.success());
|
return ResponseEntity.ok(ApiResponse.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<ApiResponse<List<DataAccess>>> getDataAccess(
|
public ResponseEntity<ApiResponse<List<DataAccess>>> getDataAccess(
|
||||||
@RequestParam String dataType,
|
@RequestParam String dataType,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.ether.pms.auth.entity.User;
|
||||||
import com.ether.pms.auth.service.RoleService;
|
import com.ether.pms.auth.service.RoleService;
|
||||||
import com.ether.pms.common.ApiResponse;
|
import com.ether.pms.common.ApiResponse;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.UUID;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
@ -87,7 +88,7 @@ public class RoleController {
|
||||||
* @return 包含该项目角色列表的响应
|
* @return 包含该项目角色列表的响应
|
||||||
*/
|
*/
|
||||||
@GetMapping("/project/{projectId}")
|
@GetMapping("/project/{projectId}")
|
||||||
public ResponseEntity<ApiResponse<List<Role>>> findByProjectId(@PathVariable String projectId) {
|
public ResponseEntity<ApiResponse<List<Role>>> findByProjectId(@PathVariable UUID projectId) {
|
||||||
return ResponseEntity.ok(ApiResponse.success(roleService.findByProjectId(projectId)));
|
return ResponseEntity.ok(ApiResponse.success(roleService.findByProjectId(projectId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import java.util.UUID;
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "biz_data_access")
|
@Table(name = "biz_data_access")
|
||||||
@Data
|
@Data
|
||||||
|
@Deprecated
|
||||||
public class DataAccess {
|
public class DataAccess {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
|
|
||||||
|
|
@ -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;
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean visible = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限关联的角色列表
|
* 权限关联的角色列表
|
||||||
* 多对多关系,通过auth_role_permission关联表维护
|
* 多对多关系,通过auth_role_permission关联表维护
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ class PasswordServiceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void isPasswordWeak_shouldReturnTrue_forCommonWeakPasswords() {
|
void isWeakPassword_shouldReturnTrue_forCommonWeakPasswords() {
|
||||||
assertTrue(passwordService.isPasswordWeak("password123"));
|
assertTrue(passwordService.isWeakPassword("password123"));
|
||||||
assertTrue(passwordService.isPasswordWeak("admin123"));
|
assertTrue(passwordService.isWeakPassword("admin123"));
|
||||||
assertTrue(passwordService.isPasswordWeak("qwerty123"));
|
assertTrue(passwordService.isWeakPassword("qwerty123"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void isPasswordWeak_shouldReturnFalse_forStrongPasswords() {
|
void isWeakPassword_shouldReturnFalse_forStrongPasswords() {
|
||||||
assertFalse(passwordService.isPasswordWeak("Str0ng!Pass"));
|
assertFalse(passwordService.isWeakPassword("Str0ng!Pass"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.ether.pms.auth.entity.User;
|
||||||
import com.ether.pms.auth.repository.RoleRepository;
|
import com.ether.pms.auth.repository.RoleRepository;
|
||||||
import com.ether.pms.auth.repository.UserRepository;
|
import com.ether.pms.auth.repository.UserRepository;
|
||||||
import com.ether.pms.common.BusinessException;
|
import com.ether.pms.common.BusinessException;
|
||||||
|
import com.ether.pms.common.ErrorCode;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
|
|
@ -86,8 +87,8 @@ class UserServiceTest {
|
||||||
newUser.setPassword("weak");
|
newUser.setPassword("weak");
|
||||||
|
|
||||||
when(userRepository.existsByUsername("newuser")).thenReturn(false);
|
when(userRepository.existsByUsername("newuser")).thenReturn(false);
|
||||||
doThrow(new IllegalArgumentException("密码太弱"))
|
doThrow(new BusinessException(ErrorCode.PASSWORD_001))
|
||||||
.when(passwordService).validatePassword("weak");
|
.when(passwordService).validateStrength("weak");
|
||||||
|
|
||||||
assertThrows(BusinessException.class, () -> userService.create(newUser));
|
assertThrows(BusinessException.class, () -> userService.create(newUser));
|
||||||
}
|
}
|
||||||
|
|
@ -99,8 +100,8 @@ class UserServiceTest {
|
||||||
newUser.setPassword("Valid123!");
|
newUser.setPassword("Valid123!");
|
||||||
|
|
||||||
when(userRepository.existsByUsername("newuser")).thenReturn(false);
|
when(userRepository.existsByUsername("newuser")).thenReturn(false);
|
||||||
doNothing().when(passwordService).validatePassword("Valid123!");
|
doNothing().when(passwordService).validateStrength("Valid123!");
|
||||||
when(passwordService.isPasswordWeak("Valid123!")).thenReturn(false);
|
when(passwordService.isWeakPassword("Valid123!")).thenReturn(false);
|
||||||
when(passwordService.encode("Valid123!")).thenReturn("encodedPassword");
|
when(passwordService.encode("Valid123!")).thenReturn("encodedPassword");
|
||||||
when(userRepository.save(any(User.class))).thenReturn(newUser);
|
when(userRepository.save(any(User.class))).thenReturn(newUser);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设备详情
|
* 获取设备详情
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@GetMapping("/{id}/equipment")
|
@GetMapping("/{id}/equipment")
|
||||||
public ResponseEntity<ApiResponse<SpaceNodeEquipmentDTO>> getEquipment(@PathVariable UUID id) {
|
public ResponseEntity<ApiResponse<SpaceNodeEquipmentDTO>> getEquipment(@PathVariable UUID id) {
|
||||||
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getEquipmentById(id)));
|
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.getEquipmentById(id)));
|
||||||
|
|
@ -133,7 +135,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取设备列表
|
* 获取设备列表
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@GetMapping("/equipment")
|
@GetMapping("/equipment")
|
||||||
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getEquipmentList(
|
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getEquipmentList(
|
||||||
@RequestParam UUID projectId) {
|
@RequestParam UUID projectId) {
|
||||||
|
|
@ -142,7 +146,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取特种设备列表
|
* 获取特种设备列表
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@GetMapping("/special-equipment")
|
@GetMapping("/special-equipment")
|
||||||
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getSpecialEquipment(
|
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getSpecialEquipment(
|
||||||
@RequestParam UUID projectId) {
|
@RequestParam UUID projectId) {
|
||||||
|
|
@ -151,7 +157,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取即将年检设备
|
* 获取即将年检设备
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@GetMapping("/expiring-inspection")
|
@GetMapping("/expiring-inspection")
|
||||||
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getExpiringInspection(
|
public ResponseEntity<ApiResponse<List<SpaceNodeEquipmentDTO>>> getExpiringInspection(
|
||||||
@RequestParam UUID projectId,
|
@RequestParam UUID projectId,
|
||||||
|
|
@ -161,7 +169,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建设备
|
* 创建设备
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@PostMapping("/equipment")
|
@PostMapping("/equipment")
|
||||||
public ResponseEntity<ApiResponse<SpaceNode>> createEquipment(@Valid @RequestBody EquipmentCreateDTO dto) {
|
public ResponseEntity<ApiResponse<SpaceNode>> createEquipment(@Valid @RequestBody EquipmentCreateDTO dto) {
|
||||||
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.createEquipment(dto)));
|
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.createEquipment(dto)));
|
||||||
|
|
@ -169,7 +179,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量创建设备
|
* 批量创建设备
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@PostMapping("/equipment/batch")
|
@PostMapping("/equipment/batch")
|
||||||
public ResponseEntity<ApiResponse<List<SpaceNode>>> batchCreateEquipment(@Valid @RequestBody List<EquipmentCreateDTO> dtoList) {
|
public ResponseEntity<ApiResponse<List<SpaceNode>>> batchCreateEquipment(@Valid @RequestBody List<EquipmentCreateDTO> dtoList) {
|
||||||
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.batchCreateEquipment(dtoList)));
|
return ResponseEntity.ok(ApiResponse.success(spaceNodeService.batchCreateEquipment(dtoList)));
|
||||||
|
|
@ -177,7 +189,9 @@ public class SpaceNodeController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel导入设备
|
* Excel导入设备
|
||||||
|
* @deprecated 请使用 /api/asset/equipment 替代
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@PostMapping("/equipment/import")
|
@PostMapping("/equipment/import")
|
||||||
public ResponseEntity<ApiResponse<Object>> importEquipment(
|
public ResponseEntity<ApiResponse<Object>> importEquipment(
|
||||||
@RequestParam("file") MultipartFile file,
|
@RequestParam("file") MultipartFile file,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ public class SpaceNodeCreateDTO {
|
||||||
@NotNull(message = "项目ID不能为空")
|
@NotNull(message = "项目ID不能为空")
|
||||||
private UUID projectId;
|
private UUID projectId;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
@NotBlank(message = "空间节点名称不能为空")
|
@NotBlank(message = "空间节点名称不能为空")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.ether.pms.mdm.entity;
|
package com.ether.pms.mdm.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
|
@ -32,6 +33,10 @@ public class SpaceNode {
|
||||||
@Column(name = "project_code", nullable = false, length = 50)
|
@Column(name = "project_code", nullable = false, length = 50)
|
||||||
private UUID projectId;
|
private UUID projectId;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "code", length = 50)
|
||||||
|
private String code;
|
||||||
|
|
||||||
@NotNull(message = "空间节点名称不能为空")
|
@NotNull(message = "空间节点名称不能为空")
|
||||||
@Size(min = 1, max = 100, message = "空间节点名称长度必须在1-100位之间")
|
@Size(min = 1, max = 100, message = "空间节点名称长度必须在1-100位之间")
|
||||||
@Column(nullable = false, length = 100)
|
@Column(nullable = false, length = 100)
|
||||||
|
|
@ -137,67 +142,88 @@ public class SpaceNode {
|
||||||
@Column(name = "is_deleted")
|
@Column(name = "is_deleted")
|
||||||
private Boolean isDeleted = false;
|
private Boolean isDeleted = false;
|
||||||
|
|
||||||
// ========== 设备扩展字段 ==========
|
// ========== 设备扩展字段(@Deprecated:请使用 module-asset 的 Equipment 实体) ==========
|
||||||
|
@Deprecated
|
||||||
@Column(name = "is_equipment")
|
@Column(name = "is_equipment")
|
||||||
private Boolean isEquipment = false;
|
private Boolean isEquipment = false;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "design_life_years")
|
@Column(name = "design_life_years")
|
||||||
private Integer designLifeYears;
|
private Integer designLifeYears;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "rated_power", precision = 10, scale = 2)
|
@Column(name = "rated_power", precision = 10, scale = 2)
|
||||||
private BigDecimal ratedPower;
|
private BigDecimal ratedPower;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "rated_voltage", precision = 10, scale = 2)
|
@Column(name = "rated_voltage", precision = 10, scale = 2)
|
||||||
private BigDecimal ratedVoltage;
|
private BigDecimal ratedVoltage;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "rated_current", precision = 10, scale = 2)
|
@Column(name = "rated_current", precision = 10, scale = 2)
|
||||||
private BigDecimal ratedCurrent;
|
private BigDecimal ratedCurrent;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_vendor", length = 100)
|
@Column(name = "maintenance_vendor", length = 100)
|
||||||
private String maintenanceVendor;
|
private String maintenanceVendor;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_vendor_contact", length = 50)
|
@Column(name = "maintenance_vendor_contact", length = 50)
|
||||||
private String maintenanceVendorContact;
|
private String maintenanceVendorContact;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_vendor_phone", length = 20)
|
@Column(name = "maintenance_vendor_phone", length = 20)
|
||||||
private String maintenanceVendorPhone;
|
private String maintenanceVendorPhone;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_contract_no", length = 50)
|
@Column(name = "maintenance_contract_no", length = 50)
|
||||||
private String maintenanceContractNo;
|
private String maintenanceContractNo;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_contract_start")
|
@Column(name = "maintenance_contract_start")
|
||||||
private LocalDate maintenanceContractStart;
|
private LocalDate maintenanceContractStart;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "maintenance_contract_end")
|
@Column(name = "maintenance_contract_end")
|
||||||
private LocalDate maintenanceContractEnd;
|
private LocalDate maintenanceContractEnd;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "special_equipment_type", length = 50)
|
@Column(name = "special_equipment_type", length = 50)
|
||||||
private String specialEquipmentType;
|
private String specialEquipmentType;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "special_equipment_cert", length = 100)
|
@Column(name = "special_equipment_cert", length = 100)
|
||||||
private String specialEquipmentCert;
|
private String specialEquipmentCert;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "inspection_cycle")
|
@Column(name = "inspection_cycle")
|
||||||
private Integer inspectionCycle;
|
private Integer inspectionCycle;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "next_inspection_date")
|
@Column(name = "next_inspection_date")
|
||||||
private LocalDate nextInspectionDate;
|
private LocalDate nextInspectionDate;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "last_inspection_date")
|
@Column(name = "last_inspection_date")
|
||||||
private LocalDate lastInspectionDate;
|
private LocalDate lastInspectionDate;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "last_inspection_result", length = 20)
|
@Column(name = "last_inspection_result", length = 20)
|
||||||
private String lastInspectionResult;
|
private String lastInspectionResult;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "common_spare_parts", length = 2000)
|
@Column(name = "common_spare_parts", length = 2000)
|
||||||
private String commonSpareParts;
|
private String commonSpareParts;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "energy_consumption_standard", precision = 12, scale = 2)
|
@Column(name = "energy_consumption_standard", precision = 12, scale = 2)
|
||||||
private BigDecimal energyConsumptionStandard;
|
private BigDecimal energyConsumptionStandard;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "installation_environment", length = 50)
|
@Column(name = "installation_environment", length = 50)
|
||||||
private String installationEnvironment;
|
private String installationEnvironment;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@Column(name = "protection_level", length = 20)
|
@Column(name = "protection_level", length = 20)
|
||||||
private String protectionLevel;
|
private String protectionLevel;
|
||||||
// ========== 设备扩展字段结束 ==========
|
// ========== 设备扩展字段结束 ==========
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,7 @@ public interface SpaceNodeRepository extends JpaRepository<SpaceNode, UUID> {
|
||||||
* 统计项目下指定类型空间数量
|
* 统计项目下指定类型空间数量
|
||||||
*/
|
*/
|
||||||
long countByProjectIdAndNodeTypeAndIsDeletedFalse(UUID projectId, SpaceNode.NodeType nodeType);
|
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<String> findMaxCodeByProjectAndTypeAndPrefix(@Param("projectId") UUID projectId, @Param("nodeType") SpaceNode.NodeType nodeType, @Param("prefix") String prefix);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import com.ether.pms.mdm.dto.SpaceNodeUpdateDTO;
|
||||||
import com.ether.pms.mdm.dto.FloorInfoVO;
|
import com.ether.pms.mdm.dto.FloorInfoVO;
|
||||||
import com.ether.pms.mdm.dto.FloorDetailVO;
|
import com.ether.pms.mdm.dto.FloorDetailVO;
|
||||||
import com.ether.pms.mdm.entity.SpaceNode;
|
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.SpaceNodeRepository;
|
||||||
|
import com.ether.pms.mdm.repository.ProjectRepository;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
@ -27,6 +29,7 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ import com.ether.pms.mdm.dto.EquipmentCreateDTO;
|
||||||
public class SpaceNodeService {
|
public class SpaceNodeService {
|
||||||
|
|
||||||
private final SpaceNodeRepository spaceNodeRepository;
|
private final SpaceNodeRepository spaceNodeRepository;
|
||||||
|
private final ProjectRepository projectRepository;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -142,6 +146,13 @@ public class SpaceNodeService {
|
||||||
node.setNodeCategory(nodeType.getCategory());
|
node.setNodeCategory(nodeType.getCategory());
|
||||||
node.setNodeType(nodeType);
|
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.setUsageType(dto.getUsageType());
|
||||||
node.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
|
node.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
|
||||||
node.setStatus(dto.getStatus() != null ? dto.getStatus() : "ACTIVE");
|
node.setStatus(dto.getStatus() != null ? dto.getStatus() : "ACTIVE");
|
||||||
|
|
@ -734,6 +745,46 @@ public class SpaceNodeService {
|
||||||
/**
|
/**
|
||||||
* 楼栋楼层配置(从JSON解析)
|
* 楼栋楼层配置(从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<String> 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
|
@Data
|
||||||
public static class BuildingFloorConfig {
|
public static class BuildingFloorConfig {
|
||||||
private Integer totalFloors;
|
private Integer totalFloors;
|
||||||
|
|
|
||||||
|
|
@ -65,5 +65,11 @@
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
<artifactId>mapstruct</artifactId>
|
<artifactId>mapstruct</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.ether.pms.ops.controller;
|
||||||
|
|
||||||
import com.ether.pms.common.ApiResponse;
|
import com.ether.pms.common.ApiResponse;
|
||||||
import com.ether.pms.common.util.BatchOperationValidator;
|
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.dto.WorkOrderStatsDTO;
|
||||||
import com.ether.pms.ops.entity.WorkOrder;
|
import com.ether.pms.ops.entity.WorkOrder;
|
||||||
import com.ether.pms.ops.entity.WorkOrderItem;
|
import com.ether.pms.ops.entity.WorkOrderItem;
|
||||||
|
|
@ -31,30 +32,19 @@ public class WorkOrderController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ApiResponse<List<WorkOrder>> list(
|
public ApiResponse<PageResponse<WorkOrder>> list(
|
||||||
@RequestParam(required = false) UUID projectId,
|
@RequestParam(required = false) UUID projectId,
|
||||||
@RequestParam(required = false) UUID equipmentId,
|
@RequestParam(required = false) UUID equipmentId,
|
||||||
@RequestParam(required = false) WorkOrder.Source source,
|
@RequestParam(required = false) WorkOrder.Source source,
|
||||||
@RequestParam(required = false) WorkOrder.Type type,
|
@RequestParam(required = false) WorkOrder.Type type,
|
||||||
@RequestParam(required = false) WorkOrder.Status status,
|
@RequestParam(required = false) WorkOrder.Status status,
|
||||||
@RequestParam(required = false) String assignedTo) {
|
@RequestParam(required = false) WorkOrder.Priority priority,
|
||||||
List<WorkOrder> list;
|
@RequestParam(required = false) String assignedTo,
|
||||||
if (projectId != null) {
|
@RequestParam(required = false) String keyword,
|
||||||
list = workOrderService.getWorkOrdersByProject(projectId);
|
@RequestParam(defaultValue = "0") int page,
|
||||||
} else if (equipmentId != null) {
|
@RequestParam(defaultValue = "20") int size) {
|
||||||
list = workOrderService.getWorkOrdersByEquipment(equipmentId);
|
return ApiResponse.success(workOrderService.queryWorkOrders(
|
||||||
} else if (source != null) {
|
projectId, equipmentId, source, type, status, priority, assignedTo, keyword, page, size));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|
@ -98,6 +88,21 @@ public class WorkOrderController {
|
||||||
return ApiResponse.success(workOrderService.cancelWorkOrder(id));
|
return ApiResponse.success(workOrderService.cancelWorkOrder(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/suspend")
|
||||||
|
public ApiResponse<WorkOrder> suspend(@PathVariable UUID id) {
|
||||||
|
return ApiResponse.success(workOrderService.suspendWorkOrder(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/resume")
|
||||||
|
public ApiResponse<WorkOrder> resume(@PathVariable UUID id) {
|
||||||
|
return ApiResponse.success(workOrderService.resumeWorkOrder(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/return")
|
||||||
|
public ApiResponse<WorkOrder> returnOrder(@PathVariable UUID id) {
|
||||||
|
return ApiResponse.success(workOrderService.returnWorkOrder(id));
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/stats")
|
@GetMapping("/stats")
|
||||||
public ApiResponse<WorkOrderStatsDTO> stats() {
|
public ApiResponse<WorkOrderStatsDTO> stats() {
|
||||||
return ApiResponse.success(workOrderService.getWorkOrderStats());
|
return ApiResponse.success(workOrderService.getWorkOrderStats());
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ public class WorkOrderStatsDTO {
|
||||||
private long completed;
|
private long completed;
|
||||||
private long verified;
|
private long verified;
|
||||||
private long cancelled;
|
private long cancelled;
|
||||||
|
private long suspended;
|
||||||
|
private long returned;
|
||||||
private long completedToday;
|
private long completedToday;
|
||||||
private long createdToday;
|
private long createdToday;
|
||||||
private long overdue;
|
private long overdue;
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,13 @@ public class WorkOrder {
|
||||||
private Status status = Status.PENDING;
|
private Status status = Status.PENDING;
|
||||||
|
|
||||||
public enum Status {
|
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)
|
@Column(nullable = false, length = 200)
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
|
|
@ -147,6 +151,9 @@ public class WorkOrder {
|
||||||
@Column(length = 2000)
|
@Column(length = 2000)
|
||||||
private String signature;
|
private String signature;
|
||||||
|
|
||||||
|
@Column(name = "is_deleted")
|
||||||
|
private Boolean isDeleted = false;
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false)
|
@Column(name = "created_at", nullable = false)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
|
@ -160,6 +167,9 @@ public class WorkOrder {
|
||||||
public void prePersist() {
|
public void prePersist() {
|
||||||
createdAt = LocalDateTime.now();
|
createdAt = LocalDateTime.now();
|
||||||
updatedAt = LocalDateTime.now();
|
updatedAt = LocalDateTime.now();
|
||||||
|
if (isDeleted == null) {
|
||||||
|
isDeleted = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreUpdate
|
@PreUpdate
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package com.ether.pms.ops.repository;
|
package com.ether.pms.ops.repository;
|
||||||
|
|
||||||
import com.ether.pms.ops.entity.WorkOrder;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
@ -13,51 +16,87 @@ import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface WorkOrderRepository extends JpaRepository<WorkOrder, UUID> {
|
public interface WorkOrderRepository extends JpaRepository<WorkOrder, UUID>, JpaSpecificationExecutor<WorkOrder> {
|
||||||
Optional<WorkOrder> findByWorkNo(String workNo);
|
Optional<WorkOrder> findByWorkNoAndIsDeletedFalse(String workNo);
|
||||||
|
|
||||||
List<WorkOrder> findByProjectId(UUID projectId);
|
List<WorkOrder> findByProjectIdAndIsDeletedFalse(UUID projectId);
|
||||||
|
|
||||||
List<WorkOrder> findByEquipmentId(UUID equipmentId);
|
List<WorkOrder> findByEquipmentIdAndIsDeletedFalse(UUID equipmentId);
|
||||||
|
|
||||||
List<WorkOrder> findBySource(WorkOrder.Source source);
|
List<WorkOrder> findBySourceAndIsDeletedFalse(WorkOrder.Source source);
|
||||||
|
|
||||||
List<WorkOrder> findByType(WorkOrder.Type type);
|
List<WorkOrder> findByTypeAndIsDeletedFalse(WorkOrder.Type type);
|
||||||
|
|
||||||
List<WorkOrder> findByStatus(WorkOrder.Status status);
|
List<WorkOrder> findByStatusAndIsDeletedFalse(WorkOrder.Status status);
|
||||||
|
|
||||||
List<WorkOrder> findByAssignedTo(String assignedTo);
|
List<WorkOrder> 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<WorkOrder> findOverdueTasks(@Param("date") LocalDate date);
|
List<WorkOrder> findOverdueTasks(@Param("date") LocalDate date);
|
||||||
|
|
||||||
@Query("SELECT MAX(w.workNo) FROM WorkOrder w WHERE w.workNo LIKE :prefix")
|
@Query("SELECT MAX(w.workNo) FROM WorkOrder w WHERE w.workNo LIKE :prefix")
|
||||||
String findMaxWorkNoByPrefix(@Param("prefix") String 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);
|
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);
|
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<Object[]> countByStatus();
|
List<Object[]> 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<Object[]> countBySource();
|
List<Object[]> 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<Object[]> countByType();
|
List<Object[]> 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<Object[]> countByPriority();
|
List<Object[]> 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);
|
long countByCreatedAtBetween(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate);
|
||||||
|
|
||||||
List<WorkOrder> findByPlanId(UUID planId);
|
List<WorkOrder> findByPlanIdAndIsDeletedFalse(UUID planId);
|
||||||
|
|
||||||
List<WorkOrder> findByPlanIdAndCreatedAtBetween(UUID planId, LocalDateTime startDate, LocalDateTime endDate);
|
List<WorkOrder> findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse(UUID planId, LocalDateTime startDate, LocalDateTime endDate);
|
||||||
|
|
||||||
|
Page<WorkOrder> findByProjectIdAndIsDeletedFalse(UUID projectId, Pageable pageable);
|
||||||
|
|
||||||
|
Page<WorkOrder> findByEquipmentIdAndIsDeletedFalse(UUID equipmentId, Pageable pageable);
|
||||||
|
|
||||||
|
Page<WorkOrder> findBySourceAndIsDeletedFalse(WorkOrder.Source source, Pageable pageable);
|
||||||
|
|
||||||
|
Page<WorkOrder> findByTypeAndIsDeletedFalse(WorkOrder.Type type, Pageable pageable);
|
||||||
|
|
||||||
|
Page<WorkOrder> findByStatusAndIsDeletedFalse(WorkOrder.Status status, Pageable pageable);
|
||||||
|
|
||||||
|
Page<WorkOrder> 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<WorkOrder> 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<WorkOrder> findByIdAndIsDeletedFalse(UUID id);
|
||||||
|
|
||||||
|
List<WorkOrder> findAllByIsDeletedFalse();
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +108,7 @@ public class MaintenanceScheduler {
|
||||||
LocalDateTime todayStart = today.atStartOfDay();
|
LocalDateTime todayStart = today.atStartOfDay();
|
||||||
LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
|
LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
|
||||||
|
|
||||||
List<WorkOrder> todayTasks = workOrderRepository.findByPlanIdAndCreatedAtBetween(
|
List<WorkOrder> todayTasks = workOrderRepository.findByPlanIdAndCreatedAtBetweenAndIsDeletedFalse(
|
||||||
planId, todayStart, tomorrowStart);
|
planId, todayStart, tomorrowStart);
|
||||||
|
|
||||||
return !todayTasks.isEmpty();
|
return !todayTasks.isEmpty();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.ether.pms.ops.service;
|
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.dto.WorkOrderStatsDTO;
|
||||||
import com.ether.pms.ops.entity.WorkOrder;
|
import com.ether.pms.ops.entity.WorkOrder;
|
||||||
import com.ether.pms.ops.entity.WorkOrderItem;
|
import com.ether.pms.ops.entity.WorkOrderItem;
|
||||||
|
|
@ -23,11 +24,19 @@ public interface WorkOrderService {
|
||||||
List<WorkOrder> getWorkOrdersByAssignedTo(String assignedTo);
|
List<WorkOrder> getWorkOrdersByAssignedTo(String assignedTo);
|
||||||
List<WorkOrder> getOverdueWorkOrders(LocalDate date);
|
List<WorkOrder> getOverdueWorkOrders(LocalDate date);
|
||||||
|
|
||||||
|
PageResponse<WorkOrder> 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 assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate);
|
||||||
WorkOrder startWorkOrder(UUID id);
|
WorkOrder startWorkOrder(UUID id);
|
||||||
WorkOrder completeWorkOrder(UUID id, WorkOrder workOrderData);
|
WorkOrder completeWorkOrder(UUID id, WorkOrder workOrderData);
|
||||||
WorkOrder verifyWorkOrder(UUID id, String verifiedBy, String remark, Integer rating);
|
WorkOrder verifyWorkOrder(UUID id, String verifiedBy, String remark, Integer rating);
|
||||||
WorkOrder cancelWorkOrder(UUID id);
|
WorkOrder cancelWorkOrder(UUID id);
|
||||||
|
WorkOrder suspendWorkOrder(UUID id);
|
||||||
|
WorkOrder resumeWorkOrder(UUID id);
|
||||||
|
WorkOrder returnWorkOrder(UUID id);
|
||||||
|
|
||||||
WorkOrderStatsDTO getWorkOrderStats();
|
WorkOrderStatsDTO getWorkOrderStats();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
package com.ether.pms.ops.service.impl;
|
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.dto.WorkOrderStatsDTO;
|
||||||
import com.ether.pms.ops.entity.WorkOrder;
|
import com.ether.pms.ops.entity.WorkOrder;
|
||||||
import com.ether.pms.ops.entity.WorkOrderItem;
|
import com.ether.pms.ops.entity.WorkOrderItem;
|
||||||
import com.ether.pms.ops.repository.WorkOrderItemRepository;
|
import com.ether.pms.ops.repository.WorkOrderItemRepository;
|
||||||
import com.ether.pms.ops.repository.WorkOrderRepository;
|
import com.ether.pms.ops.repository.WorkOrderRepository;
|
||||||
import com.ether.pms.ops.service.WorkOrderService;
|
import com.ether.pms.ops.service.WorkOrderService;
|
||||||
|
import com.ether.pms.common.util.PaginationValidator;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -71,12 +77,15 @@ public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteWorkOrder(UUID id) {
|
public void deleteWorkOrder(UUID id) {
|
||||||
workOrderRepository.deleteById(id);
|
WorkOrder workOrder = workOrderRepository.findByIdAndIsDeletedFalse(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("工单不存在: " + id));
|
||||||
|
workOrder.setIsDeleted(true);
|
||||||
|
workOrderRepository.save(workOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WorkOrder getWorkOrderById(UUID id) {
|
public WorkOrder getWorkOrderById(UUID id) {
|
||||||
return workOrderRepository.findById(id)
|
return workOrderRepository.findByIdAndIsDeletedFalse(id)
|
||||||
.orElseThrow(() -> new RuntimeException("工单不存在: " + id));
|
.orElseThrow(() -> new RuntimeException("工单不存在: " + id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,37 +95,37 @@ public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
// 此方法用于管理后台的工单列表展示
|
// 此方法用于管理后台的工单列表展示
|
||||||
// TODO: 必须改为分页查询,建议添加 Pageable 参数
|
// TODO: 必须改为分页查询,建议添加 Pageable 参数
|
||||||
// 临时方案:限制返回最近 1000 条记录
|
// 临时方案:限制返回最近 1000 条记录
|
||||||
return workOrderRepository.findAll();
|
return workOrderRepository.findAllByIsDeletedFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersByProject(UUID projectId) {
|
public List<WorkOrder> getWorkOrdersByProject(UUID projectId) {
|
||||||
return workOrderRepository.findByProjectId(projectId);
|
return workOrderRepository.findByProjectIdAndIsDeletedFalse(projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersByEquipment(UUID equipmentId) {
|
public List<WorkOrder> getWorkOrdersByEquipment(UUID equipmentId) {
|
||||||
return workOrderRepository.findByEquipmentId(equipmentId);
|
return workOrderRepository.findByEquipmentIdAndIsDeletedFalse(equipmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersBySource(WorkOrder.Source source) {
|
public List<WorkOrder> getWorkOrdersBySource(WorkOrder.Source source) {
|
||||||
return workOrderRepository.findBySource(source);
|
return workOrderRepository.findBySourceAndIsDeletedFalse(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersByType(WorkOrder.Type type) {
|
public List<WorkOrder> getWorkOrdersByType(WorkOrder.Type type) {
|
||||||
return workOrderRepository.findByType(type);
|
return workOrderRepository.findByTypeAndIsDeletedFalse(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersByStatus(WorkOrder.Status status) {
|
public List<WorkOrder> getWorkOrdersByStatus(WorkOrder.Status status) {
|
||||||
return workOrderRepository.findByStatus(status);
|
return workOrderRepository.findByStatusAndIsDeletedFalse(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WorkOrder> getWorkOrdersByAssignedTo(String assignedTo) {
|
public List<WorkOrder> getWorkOrdersByAssignedTo(String assignedTo) {
|
||||||
return workOrderRepository.findByAssignedTo(assignedTo);
|
return workOrderRepository.findByAssignedToAndIsDeletedFalse(assignedTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -124,6 +133,24 @@ public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
return workOrderRepository.findOverdueTasks(date);
|
return workOrderRepository.findOverdueTasks(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResponse<WorkOrder> 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<WorkOrder> workOrderPage = workOrderRepository.searchWorkOrders(
|
||||||
|
projectId, equipmentId, source, type, status, priority, assignedTo, keyword, pageable);
|
||||||
|
return PageResponse.of(
|
||||||
|
workOrderPage.getContent(),
|
||||||
|
workOrderPage.getNumber(),
|
||||||
|
workOrderPage.getSize(),
|
||||||
|
workOrderPage.getTotalElements()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate) {
|
public WorkOrder assignWorkOrder(UUID id, String assignedTo, String assignedVendor, LocalDate assignedDate) {
|
||||||
|
|
@ -226,6 +253,55 @@ public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
return workOrderRepository.save(workOrder);
|
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
|
@Override
|
||||||
public WorkOrderStatsDTO getWorkOrderStats() {
|
public WorkOrderStatsDTO getWorkOrderStats() {
|
||||||
LocalDate today = LocalDate.now();
|
LocalDate today = LocalDate.now();
|
||||||
|
|
@ -278,6 +354,8 @@ public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
.completed(byStatus.getOrDefault("COMPLETED", 0L))
|
.completed(byStatus.getOrDefault("COMPLETED", 0L))
|
||||||
.verified(byStatus.getOrDefault("VERIFIED", 0L))
|
.verified(byStatus.getOrDefault("VERIFIED", 0L))
|
||||||
.cancelled(byStatus.getOrDefault("CANCELLED", 0L))
|
.cancelled(byStatus.getOrDefault("CANCELLED", 0L))
|
||||||
|
.suspended(byStatus.getOrDefault("SUSPENDED", 0L))
|
||||||
|
.returned(byStatus.getOrDefault("RETURNED", 0L))
|
||||||
.completedToday(workOrderRepository.countCompletedToday(today))
|
.completedToday(workOrderRepository.countCompletedToday(today))
|
||||||
.createdToday(workOrderRepository.countByCreatedAtBetween(startOfToday, endOfToday))
|
.createdToday(workOrderRepository.countByCreatedAtBetween(startOfToday, endOfToday))
|
||||||
.overdue(workOrderRepository.countOverdue(today))
|
.overdue(workOrderRepository.countOverdue(today))
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ class WorkOrderServiceTest {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("派单:PENDING -> ASSIGNED")
|
@DisplayName("派单:PENDING -> ASSIGNED")
|
||||||
void assignWorkOrder_shouldChangeStatusToAssigned() {
|
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));
|
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());
|
||||||
|
|
@ -138,7 +138,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("派单失败:只有PENDING状态才能派单")
|
@DisplayName("派单失败:只有PENDING状态才能派单")
|
||||||
void assignWorkOrder_shouldFailWhenNotPending() {
|
void assignWorkOrder_shouldFailWhenNotPending() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now())
|
workOrderService.assignWorkOrder(testId, "张三", "维保公司A", LocalDate.now())
|
||||||
|
|
@ -149,7 +149,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("开始:ASSIGNED -> IN_PROGRESS")
|
@DisplayName("开始:ASSIGNED -> IN_PROGRESS")
|
||||||
void startWorkOrder_shouldChangeStatusToInProgress() {
|
void startWorkOrder_shouldChangeStatusToInProgress() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.startWorkOrder(testId);
|
WorkOrder result = workOrderService.startWorkOrder(testId);
|
||||||
|
|
@ -162,7 +162,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("开始失败:只有ASSIGNED状态才能开始")
|
@DisplayName("开始失败:只有ASSIGNED状态才能开始")
|
||||||
void startWorkOrder_shouldFailWhenNotAssigned() {
|
void startWorkOrder_shouldFailWhenNotAssigned() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.PENDING);
|
testWorkOrder.setStatus(WorkOrder.Status.PENDING);
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.startWorkOrder(testId)
|
workOrderService.startWorkOrder(testId)
|
||||||
|
|
@ -184,7 +184,7 @@ class WorkOrderServiceTest {
|
||||||
completeData.setTotalCost(BigDecimal.valueOf(700));
|
completeData.setTotalCost(BigDecimal.valueOf(700));
|
||||||
completeData.setCompletedBy("李四");
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.completeWorkOrder(testId, completeData);
|
WorkOrder result = workOrderService.completeWorkOrder(testId, completeData);
|
||||||
|
|
@ -202,7 +202,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("完成失败:只有IN_PROGRESS状态才能完成")
|
@DisplayName("完成失败:只有IN_PROGRESS状态才能完成")
|
||||||
void completeWorkOrder_shouldFailWhenNotInProgress() {
|
void completeWorkOrder_shouldFailWhenNotInProgress() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.completeWorkOrder(testId, new WorkOrder())
|
workOrderService.completeWorkOrder(testId, new WorkOrder())
|
||||||
|
|
@ -213,7 +213,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("验收:COMPLETED -> VERIFIED")
|
@DisplayName("验收:COMPLETED -> VERIFIED")
|
||||||
void verifyWorkOrder_shouldChangeStatusToVerified() {
|
void verifyWorkOrder_shouldChangeStatusToVerified() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.COMPLETED);
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5);
|
WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5);
|
||||||
|
|
@ -229,7 +229,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("验收失败:只有COMPLETED状态才能验收")
|
@DisplayName("验收失败:只有COMPLETED状态才能验收")
|
||||||
void verifyWorkOrder_shouldFailWhenNotCompleted() {
|
void verifyWorkOrder_shouldFailWhenNotCompleted() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.IN_PROGRESS);
|
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, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5)
|
workOrderService.verifyWorkOrder(testId, "王五", "验收通过", 5)
|
||||||
|
|
@ -240,7 +240,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("验收评分应在1-5范围内")
|
@DisplayName("验收评分应在1-5范围内")
|
||||||
void verifyWorkOrder_shouldAcceptValidRating() {
|
void verifyWorkOrder_shouldAcceptValidRating() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.COMPLETED);
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", null, 4);
|
WorkOrder result = workOrderService.verifyWorkOrder(testId, "王五", null, 4);
|
||||||
|
|
@ -252,7 +252,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("取消:PENDING/ASSIGNED/IN_PROGRESS -> CANCELLED")
|
@DisplayName("取消:PENDING/ASSIGNED/IN_PROGRESS -> CANCELLED")
|
||||||
void cancelWorkOrder_shouldChangeStatusToCancelled() {
|
void cancelWorkOrder_shouldChangeStatusToCancelled() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.ASSIGNED);
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.cancelWorkOrder(testId);
|
WorkOrder result = workOrderService.cancelWorkOrder(testId);
|
||||||
|
|
@ -264,7 +264,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("取消失败:COMPLETED状态不能取消")
|
@DisplayName("取消失败:COMPLETED状态不能取消")
|
||||||
void cancelWorkOrder_shouldFailWhenCompleted() {
|
void cancelWorkOrder_shouldFailWhenCompleted() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.COMPLETED);
|
testWorkOrder.setStatus(WorkOrder.Status.COMPLETED);
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.cancelWorkOrder(testId)
|
workOrderService.cancelWorkOrder(testId)
|
||||||
|
|
@ -275,7 +275,7 @@ class WorkOrderServiceTest {
|
||||||
@DisplayName("取消失败:VERIFIED状态不能取消")
|
@DisplayName("取消失败:VERIFIED状态不能取消")
|
||||||
void cancelWorkOrder_shouldFailWhenVerified() {
|
void cancelWorkOrder_shouldFailWhenVerified() {
|
||||||
testWorkOrder.setStatus(WorkOrder.Status.VERIFIED);
|
testWorkOrder.setStatus(WorkOrder.Status.VERIFIED);
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.cancelWorkOrder(testId)
|
workOrderService.cancelWorkOrder(testId)
|
||||||
|
|
@ -290,7 +290,7 @@ class WorkOrderServiceTest {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("根据ID获取工单")
|
@DisplayName("根据ID获取工单")
|
||||||
void getWorkOrderById_shouldReturnWorkOrder() {
|
void getWorkOrderById_shouldReturnWorkOrder() {
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.of(testWorkOrder));
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.of(testWorkOrder));
|
||||||
|
|
||||||
WorkOrder result = workOrderService.getWorkOrderById(testId);
|
WorkOrder result = workOrderService.getWorkOrderById(testId);
|
||||||
|
|
||||||
|
|
@ -301,7 +301,7 @@ class WorkOrderServiceTest {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("根据ID获取工单失败时应抛出异常")
|
@DisplayName("根据ID获取工单失败时应抛出异常")
|
||||||
void getWorkOrderById_shouldThrowExceptionWhenNotFound() {
|
void getWorkOrderById_shouldThrowExceptionWhenNotFound() {
|
||||||
when(workOrderRepository.findById(testId)).thenReturn(Optional.empty());
|
when(workOrderRepository.findByIdAndIsDeletedFalse(testId)).thenReturn(Optional.empty());
|
||||||
|
|
||||||
assertThrows(RuntimeException.class, () ->
|
assertThrows(RuntimeException.class, () ->
|
||||||
workOrderService.getWorkOrderById(testId)
|
workOrderService.getWorkOrderById(testId)
|
||||||
|
|
@ -314,11 +314,15 @@ class WorkOrderServiceTest {
|
||||||
class DeleteTests {
|
class DeleteTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("删除工单应调用repository")
|
@DisplayName("逻辑删除工单应设置isDeleted为true")
|
||||||
void deleteWorkOrder_shouldCallRepository() {
|
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);
|
workOrderService.deleteWorkOrder(testId);
|
||||||
|
|
||||||
verify(workOrderRepository).deleteById(testId);
|
assertTrue(testWorkOrder.getIsDeleted());
|
||||||
|
verify(workOrderRepository).save(testWorkOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,7 +333,7 @@ class WorkOrderServiceTest {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("更新工单应保存所有字段")
|
@DisplayName("更新工单应保存所有字段")
|
||||||
void updateWorkOrder_shouldUpdateAllFields() {
|
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));
|
when(workOrderRepository.save(any(WorkOrder.class))).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
WorkOrder updateData = new WorkOrder();
|
WorkOrder updateData = new WorkOrder();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue