[tag] 代码尚未完全实现,目前准备加入登录功能

This commit is contained in:
LYC 2025-04-18 17:29:02 +08:00
parent db6e3f77a1
commit 914ab87beb
18 changed files with 570 additions and 1 deletions

View File

@ -74,6 +74,10 @@
<artifactId>jts-core</artifactId>
<version>1.18.0</version> <!-- 请使用最新版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,33 @@
package com.waterquality.projectmanagement.controller;
import com.waterquality.projectmanagement.Response;
import com.waterquality.projectmanagement.dto.plan.InspectionPlanCreateDTO;
import com.waterquality.projectmanagement.dto.plan.InspectionPlanVO;
import com.waterquality.projectmanagement.service.InspectionPlanService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
// InspectionPlanController.java
@RestController
@RequestMapping("/api/inspection-plans")
@RequiredArgsConstructor
public class InspectionPlanController {
private final InspectionPlanService planService;
@PostMapping
public ResponseEntity<Response<InspectionPlanVO>> createPlan(
@Valid @RequestBody InspectionPlanCreateDTO dto) {
return ResponseEntity.ok(Response.newSuccess(planService.createPlan(dto)));
}
@GetMapping("/employee/{employeeId}")
public ResponseEntity<Response<List<InspectionPlanVO>>> getByEmployee(
@PathVariable Integer employeeId) {
return ResponseEntity.ok(Response.newSuccess(planService.getPlansByEmployee(employeeId)));
}
}

View File

@ -0,0 +1,65 @@
package com.waterquality.projectmanagement.controller;
import com.waterquality.projectmanagement.Response;
import com.waterquality.projectmanagement.dto.order.*;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import com.waterquality.projectmanagement.service.WorkOrderService;
import lombok.*;
import org.apache.catalina.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
// WorkOrderController.java
@RestController
@RequestMapping("/api/work-orders")
@RequiredArgsConstructor
public class WorkOrderController {
private final WorkOrderService workOrderService;
@PostMapping
@PreAuthorize("hasRole('MAINTENANCE')")
public ResponseEntity<Response<WorkOrderVO>> createOrder(
@Valid @RequestBody WorkOrderCreateDTO dto,
@AuthenticationPrincipal User user) {
return ResponseEntity.ok(Response.newSuccess(
workOrderService.createOrder(dto, user.getId())));
}
@PatchMapping("/{id}/status")
@PreAuthorize("hasAnyRole('MAINTENANCE', 'ADMIN')")
public ResponseEntity<Response<WorkOrderVO>> updateStatus(
@PathVariable Integer id,
@Valid @RequestBody WorkOrderUpdateDTO dto) {
return ResponseEntity.ok(Response.newSuccess(
workOrderService.updateOrderStatus(id, dto)));
}
@GetMapping("/my-orders")
public ResponseEntity<Response<Page<WorkOrderVO>>> getMyOrders(
@RequestParam(required = false) Set<WorkOrderStatus> statuses,
@AuthenticationPrincipal User user,
@PageableDefault Pageable pageable) {
return ResponseEntity.ok(Response.newSuccess(
workOrderService.getOrdersByAssignee(user.getId(),
statuses != null ? statuses : EnumSet.allOf(WorkOrderStatus.class),
pageable)));
}
@GetMapping("/stats")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Response<Map<String, Long>>> getStats() {
return ResponseEntity.ok(Response.newSuccess(
workOrderService.getStatusStatistics()));
}
}

View File

@ -0,0 +1,22 @@
package com.waterquality.projectmanagement.dto.order;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
// WorkOrderCreateDTO.java
@Data
public class WorkOrderCreateDTO {
@NotNull(message = "设施ID不能为空")
private Integer facilityId;
@NotBlank(message = "问题描述不能为空")
@Size(max = 1000, message = "描述不能超过1000字符")
private String description;
@NotNull(message = "指派人不能为空")
private Integer assigneeId;
}

View File

@ -0,0 +1,18 @@
package com.waterquality.projectmanagement.dto.order;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
// WorkOrderUpdateDTO.java
@Data
public class WorkOrderUpdateDTO {
@NotNull(message = "新状态不能为空")
private WorkOrderStatus newStatus;
@DecimalMin(value = "0.0", message = "费用不能为负数")
private BigDecimal cost;
}

View File

@ -0,0 +1,28 @@
package com.waterquality.projectmanagement.dto.order;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
// WorkOrderVO.java
@Data
public class WorkOrderVO {
private Integer id;
private String facilityCode;
private String facilityType;
private String creatorName;
private String assigneeName;
private String description;
private WorkOrderStatus status;
private LocalDateTime createTime;
private LocalDateTime completeTime;
private BigDecimal cost;
@Data
public static class FacilityInfo {
private String code;
private String type;
}
}

View File

@ -0,0 +1,23 @@
package com.waterquality.projectmanagement.dto.plan;
import com.waterquality.projectmanagement.entity.plan.PlanStatus;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
// InspectionPlanCreateDTO.java
@Data
public class InspectionPlanCreateDTO {
@NotNull(message = "员工ID不能为空")
private Integer employeeId;
@NotBlank(message = "巡检区域不能为空")
private String area;
@Future(message = "计划时间必须是将来的时间")
private LocalDateTime plannedTime;
}

View File

@ -0,0 +1,17 @@
package com.waterquality.projectmanagement.dto.plan;
import com.waterquality.projectmanagement.entity.plan.PlanStatus;
import lombok.Data;
import java.time.LocalDateTime;
// InspectionPlanVO.java
@Data
public class InspectionPlanVO {
private Integer planId;
private String employeeName;
private String area;
private LocalDateTime plannedTime;
private PlanStatus status;
private LocalDateTime createdAt;
}

View File

@ -0,0 +1,63 @@
package com.waterquality.projectmanagement.entity.order;
import com.waterquality.projectmanagement.entity.employee.Employee;
import com.waterquality.projectmanagement.entity.facility.Facility;
import jakarta.persistence.*;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
// WorkOrder.java
@Entity
@Table(name = "work_orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WorkOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "order_id")
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "facility_id", nullable = false)
private Facility facility;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_id", nullable = false)
private Employee creator;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "assignee_id")
private Employee assignee;
@Column(nullable = false, columnDefinition = "TEXT")
private String description;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private WorkOrderStatus status = WorkOrderStatus.PENDING;
@Column(name = "create_time", nullable = false)
private LocalDateTime createTime = LocalDateTime.now();
@Column(name = "complete_time")
private LocalDateTime completeTime;
@Column(precision = 10, scale = 2)
private BigDecimal cost;
// 状态变更校验
public void changeStatus(WorkOrderStatus newStatus) {
if (!status.getNextStatuses().contains(newStatus)) {
throw new IllegalStateException("非法状态变更: " + status + " -> " + newStatus);
}
this.status = newStatus;
if (newStatus == WorkOrderStatus.COMPLETED) {
this.completeTime = LocalDateTime.now();
}
}
}

View File

@ -0,0 +1,27 @@
package com.waterquality.projectmanagement.entity.order;
import lombok.Getter;
import java.util.Collections;
import java.util.Set;
// 工单状态机枚举
@Getter
public enum WorkOrderStatus {
PENDING,
IN_PROGRESS,
COMPLETED,
CANCELLED,
FAILED;
private Set<WorkOrderStatus> nextStatuses;
// 静态初始化块用于设置每个状态的下一个可能状态
static {
PENDING.nextStatuses = Set.of(IN_PROGRESS, CANCELLED);
IN_PROGRESS.nextStatuses = Set.of(COMPLETED, FAILED);
COMPLETED.nextStatuses = Collections.emptySet();
CANCELLED.nextStatuses = Collections.emptySet();
FAILED.nextStatuses = Set.of(IN_PROGRESS);
}
}

View File

@ -0,0 +1,39 @@
package com.waterquality.projectmanagement.entity.plan;
import com.waterquality.projectmanagement.entity.employee.Employee;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
// InspectionPlan.java
@Entity
@Table(name = "inspection_plans")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InspectionPlan {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer planId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "employee_id", nullable = false)
private Employee employee;
@Column(nullable = false, length = 100)
private String area;
@Column(name = "planned_time", nullable = false)
private LocalDateTime plannedTime;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PlanStatus status = PlanStatus.SCHEDULED;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt = LocalDateTime.now();
}

View File

@ -0,0 +1,9 @@
package com.waterquality.projectmanagement.entity.plan;
// 计划状态枚举
public enum PlanStatus {
SCHEDULED, // 已排期
IN_PROGRESS, // 进行中
COMPLETED, // 已完成
CANCELLED // 已取消
}

View File

@ -14,7 +14,7 @@ public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
// 根据工号查询
Optional<Employee> findByEmployeeNo(String employeeNo);
Employee findEmployeeByEmployeeId(Integer employeeId);
// 按部门查询在职员工
List<Employee> findByDepartmentAndStatus(Department department, Status status);

View File

@ -0,0 +1,25 @@
package com.waterquality.projectmanagement.repository;
import com.waterquality.projectmanagement.entity.plan.InspectionPlan;
import com.waterquality.projectmanagement.entity.plan.PlanStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDateTime;
import java.util.List;
// InspectionPlanRepository.java
public interface InspectionPlanRepository extends JpaRepository<InspectionPlan, Integer> {
// 按员工查询计划
List<InspectionPlan> findByEmployeeEmployeeId(Integer employeeId);
// 按区域和状态查询
List<InspectionPlan> findByAreaAndStatus(String area, PlanStatus status);
// 查询指定时间范围内的计划
@Query("SELECT p FROM InspectionPlan p WHERE p.plannedTime BETWEEN :start AND :end")
List<InspectionPlan> findBetweenDates(@Param("start") LocalDateTime start,
@Param("end") LocalDateTime end);
}

View File

@ -0,0 +1,42 @@
package com.waterquality.projectmanagement.repository;
import com.waterquality.projectmanagement.entity.order.WorkOrder;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Map;
import java.util.Set;
// WorkOrderRepository.java
public interface WorkOrderRepository extends JpaRepository<WorkOrder, Integer> {
// 分页查询待处理工单
Page<WorkOrder> findByStatus(WorkOrderStatus status, Pageable pageable);
// 根据指派人查询
@Query("SELECT w FROM WorkOrder w WHERE " +
"w.assignee.employeeId = :assigneeId AND " +
"w.status IN :statuses")
Page<WorkOrder> findByAssigneeAndStatuses(
@Param("assigneeId") Integer assigneeId,
@Param("statuses") Set<WorkOrderStatus> statuses,
Pageable pageable
);
// 统计各状态工单数量
@Query("SELECT w.status as status, COUNT(w) as count " +
"FROM WorkOrder w GROUP BY w.status")
List<Map<String, Object>> countByStatus();
// 关联设施和创建人的复杂查询
@Query("SELECT w FROM WorkOrder w " +
"JOIN FETCH w.facility f " +
"JOIN FETCH w.creator c " +
"WHERE f.responsibleDept.departmentId = :deptId")
List<WorkOrder> findByDepartment(@Param("deptId") Integer deptId);
}

View File

@ -39,4 +39,8 @@ public class EmployeeService {
pageable
);
}
public Employee getEmployeeById(Integer assigneeId) {
return employeeRepository.findEmployeeByEmployeeId(assigneeId);
}
}

View File

@ -0,0 +1,53 @@
package com.waterquality.projectmanagement.service;
import com.waterquality.projectmanagement.dto.plan.InspectionPlanCreateDTO;
import com.waterquality.projectmanagement.dto.plan.InspectionPlanVO;
import com.waterquality.projectmanagement.entity.employee.Employee;
import com.waterquality.projectmanagement.entity.plan.InspectionPlan;
import com.waterquality.projectmanagement.repository.InspectionPlanRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
// InspectionPlanService.java
@Service
@RequiredArgsConstructor
public class InspectionPlanService {
private final InspectionPlanRepository planRepository;
private final EmployeeService employeeService;
@Transactional
public InspectionPlanVO createPlan(InspectionPlanCreateDTO dto) {
Employee employee = employeeService.getEmployeeById(dto.getEmployeeId());
InspectionPlan plan = new InspectionPlan();
plan.setEmployee(employee);
plan.setArea(dto.getArea());
plan.setPlannedTime(dto.getPlannedTime());
InspectionPlan saved = planRepository.save(plan);
return convertToVO(saved);
}
public List<InspectionPlanVO> getPlansByEmployee(Integer employeeId) {
return planRepository.findByEmployeeEmployeeId(employeeId)
.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
}
private InspectionPlanVO convertToVO(InspectionPlan plan) {
InspectionPlanVO vo = new InspectionPlanVO();
vo.setPlanId(plan.getPlanId());
vo.setEmployeeName(plan.getEmployee().getName());
vo.setArea(plan.getArea());
vo.setPlannedTime(plan.getPlannedTime());
vo.setStatus(plan.getStatus());
vo.setCreatedAt(plan.getCreatedAt());
return vo;
}
}

View File

@ -0,0 +1,97 @@
package com.waterquality.projectmanagement.service;
import com.waterquality.projectmanagement.dto.order.WorkOrderCreateDTO;
import com.waterquality.projectmanagement.dto.order.WorkOrderUpdateDTO;
import com.waterquality.projectmanagement.dto.order.WorkOrderVO;
import com.waterquality.projectmanagement.entity.employee.Employee;
import com.waterquality.projectmanagement.entity.facility.Facility;
import com.waterquality.projectmanagement.entity.order.WorkOrder;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import com.waterquality.projectmanagement.exception.ResourceNotFoundException;
import com.waterquality.projectmanagement.repository.WorkOrderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
// WorkOrderService.java
@Service
@RequiredArgsConstructor
@Slf4j
public class WorkOrderService {
private final WorkOrderRepository workOrderRepo;
private final FacilityService facilityService;
private final EmployeeService employeeService;
@Transactional
public WorkOrderVO createOrder(WorkOrderCreateDTO dto, Integer creatorId) {
Facility facility = facilityService.getFacilityById(dto.getFacilityId());
Employee creator = employeeService.getEmployeeById(creatorId);
Employee assignee = employeeService.getEmployeeById(dto.getAssigneeId());
WorkOrder order = new WorkOrder();
order.setFacility(facility);
order.setCreator(creator);
order.setAssignee(assignee);
order.setDescription(dto.getDescription());
WorkOrder saved = workOrderRepo.save(order);
log.info("创建工单:{}", saved.getId());
return convertToVO(saved);
}
@Transactional
public WorkOrderVO updateOrderStatus(Integer orderId, WorkOrderUpdateDTO dto) {
WorkOrder order = workOrderRepo.findById(orderId)
.orElseThrow(() -> new ResourceNotFoundException("工单不存在"));
order.changeStatus(dto.getNewStatus());
if (dto.getCost() != null) {
order.setCost(dto.getCost());
}
WorkOrder updated = workOrderRepo.save(order);
log.info("工单状态更新:{} -> {}", orderId, dto.getNewStatus());
return convertToVO(updated);
}
@Transactional(readOnly = true)
public Page<WorkOrderVO> getOrdersByAssignee(Integer assigneeId,
Set<WorkOrderStatus> statuses,
Pageable pageable) {
return workOrderRepo.findByAssigneeAndStatuses(assigneeId, statuses, pageable)
.map(this::convertToVO);
}
@Transactional(readOnly = true)
public Map<String, Long> getStatusStatistics() {
return workOrderRepo.countByStatus().stream()
.collect(Collectors.toMap(
m -> ((WorkOrderStatus) m.get("status")).name(),
m -> (Long) m.get("count")
));
}
private WorkOrderVO convertToVO(WorkOrder order) {
WorkOrderVO vo = new WorkOrderVO();
vo.setId(order.getId());
vo.setFacilityCode(order.getFacility().getCode());
vo.setFacilityType(order.getFacility().getType().name());
vo.setCreatorName(order.getCreator().getName());
vo.setAssigneeName(order.getAssignee() != null ?
order.getAssignee().getName() : null);
vo.setDescription(order.getDescription());
vo.setStatus(order.getStatus());
vo.setCreateTime(order.getCreateTime());
vo.setCompleteTime(order.getCompleteTime());
vo.setCost(order.getCost());
return vo;
}
}