Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/HandoverController.java
T
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:19:15 +08:00

209 lines
8.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.kaidi.oa.web;
import com.kaidi.oa.common.ApiException;
import com.kaidi.oa.common.ApiResp;
import com.kaidi.oa.common.NotFoundException;
import com.kaidi.oa.domain.Handover;
import com.kaidi.oa.repository.HandoverRepository;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.List;
/**
* Work handover center (工作交接). handoverType is one of 离职交接 / 调动交接 /
* 轮岗交接; status flows 进行中 -> 已完成 -> 已确认.
*/
@RestController
@RequestMapping("/api/oa/handovers")
public class HandoverController {
public static final String STATUS_IN_PROGRESS = "进行中";
public static final String STATUS_DONE = "已完成";
public static final String STATUS_CONFIRMED = "已确认";
/** Accepted handover statuses (交接全过程)。 */
public static final List<String> HANDOVER_STATUSES =
List.of(STATUS_IN_PROGRESS, STATUS_DONE, STATUS_CONFIRMED);
private final HandoverRepository handoverRepo;
public HandoverController(HandoverRepository handoverRepo) {
this.handoverRepo = handoverRepo;
}
@GetMapping
public ApiResp<List<Handover>> list(@RequestParam(required = false) String status) {
List<Handover> list;
if (status != null && !status.isBlank()) {
list = handoverRepo.findByStatus(status);
} else {
list = handoverRepo.findAll();
}
return ApiResp.ok(list);
}
@GetMapping("/{id}")
public ApiResp<Handover> get(@PathVariable Long id) {
return ApiResp.ok(handoverRepo.findById(id)
.orElseThrow(() -> new NotFoundException("handover not found: " + id)));
}
/** 新建/更新共用请求体——含结构化字段(需求功能14)。 */
public record HandoverRequest(
String handoverPerson, String receiver, String dept, String handoverType,
String items, String status, String handoverDate,
// 结构化字段(可空)
String jobDuties, String pendingTaskList,
String systemAccounts, String keyContacts, String filePaths) {
}
/** @deprecated 旧签名保留别名,前端升级后可删 */
public record CreateHandoverRequest(
String handoverPerson, String receiver, String dept, String handoverType,
String items, String status, String handoverDate) {
}
@PostMapping
public ApiResp<Handover> create(@RequestBody HandoverRequest req) {
if (req.handoverPerson() == null || req.handoverPerson().isBlank()) {
throw new ApiException(400, "handoverPerson is required");
}
Handover h = new Handover();
applyFields(h, req);
h.setStatus(req.status() == null || req.status().isBlank() ? STATUS_IN_PROGRESS : req.status());
h.setCreatedAt(Instant.now());
return ApiResp.ok(handoverRepo.save(h));
}
/**
* PATCH /{id} -> partial update incl. status progression
* (提交 进行中->已完成 / 确认 已完成->已确认). Only non-null fields are applied;
* a supplied status must belong to {@link #HANDOVER_STATUSES}.
*/
@PatchMapping("/{id}")
public ApiResp<Handover> update(@PathVariable Long id, @RequestBody HandoverRequest req) {
Handover h = handoverRepo.findById(id)
.orElseThrow(() -> new NotFoundException("handover not found: " + id));
if (req.handoverPerson() != null) {
if (req.handoverPerson().isBlank()) {
throw new ApiException(400, "handoverPerson is required");
}
}
applyFields(h, req);
if (req.status() != null && !req.status().isBlank()) {
if (!HANDOVER_STATUSES.contains(req.status())) {
throw new ApiException(400, "invalid status: " + req.status()
+ "; expected one of " + HANDOVER_STATUSES);
}
h.setStatus(req.status());
}
return ApiResp.ok(handoverRepo.save(h));
}
/** DELETE /{id} -> remove a handover record. */
@DeleteMapping("/{id}")
public ApiResp<Void> delete(@PathVariable Long id) {
if (!handoverRepo.existsById(id)) {
throw new NotFoundException("handover not found: " + id);
}
handoverRepo.deleteById(id);
return ApiResp.ok(null);
}
// ====== 一键生成交接清单(需求功能14)======
public record ChecklistSection(String section, java.util.List<String> items) {}
public record ChecklistResult(Long handoverId, String handoverPerson, String receiver,
String handoverDate, java.util.List<ChecklistSection> sections) {}
/**
* POST /{id}/generate-checklist
* 根据结构化字段(岗位职责/在办事项/系统账号/关键联系人/文件路径/遗留 items)
* 一键组装"工作交接清单",返回分节结构,前端可直接渲染或打印。
*/
@PostMapping("/{id}/generate-checklist")
public ApiResp<ChecklistResult> generateChecklist(@PathVariable Long id) {
Handover h = handoverRepo.findById(id)
.orElseThrow(() -> new NotFoundException("handover not found: " + id));
java.util.List<ChecklistSection> sections = new java.util.ArrayList<>();
// 1. 岗位职责
if (h.getJobDuties() != null && !h.getJobDuties().isBlank()) {
sections.add(new ChecklistSection("岗位职责",
splitLines(h.getJobDuties())));
}
// 2. 在办事项清单
if (h.getPendingTaskList() != null && !h.getPendingTaskList().isBlank()) {
sections.add(new ChecklistSection("在办事项清单",
splitLines(h.getPendingTaskList())));
}
// 3. 常用系统账号
if (h.getSystemAccounts() != null && !h.getSystemAccounts().isBlank()) {
sections.add(new ChecklistSection("常用系统账号",
splitLines(h.getSystemAccounts())));
}
// 4. 关键联系人
if (h.getKeyContacts() != null && !h.getKeyContacts().isBlank()) {
sections.add(new ChecklistSection("关键联系人",
splitLines(h.getKeyContacts())));
}
// 5. 重要文件路径
if (h.getFilePaths() != null && !h.getFilePaths().isBlank()) {
sections.add(new ChecklistSection("重要文件路径",
splitLines(h.getFilePaths())));
}
// 6. 其他事项(来自原 items 自由文本)
if (h.getItems() != null && !h.getItems().isBlank()) {
sections.add(new ChecklistSection("其他交接事项",
splitLines(h.getItems())));
}
if (sections.isEmpty()) {
throw new ApiException(409, "交接单尚无任何结构化内容,请先填写岗位职责/在办事项/系统账号等字段");
}
return ApiResp.ok(new ChecklistResult(h.getId(), h.getHandoverPerson(),
h.getReceiver(), h.getHandoverDate(), sections));
}
// ====== 私有方法 ======
private void applyFields(Handover h, HandoverRequest req) {
if (req.handoverPerson() != null && !req.handoverPerson().isBlank()) h.setHandoverPerson(req.handoverPerson());
if (req.receiver() != null) h.setReceiver(req.receiver());
if (req.dept() != null) h.setDept(req.dept());
if (req.handoverType() != null) h.setHandoverType(req.handoverType());
if (req.items() != null) h.setItems(req.items());
if (req.handoverDate() != null) h.setHandoverDate(req.handoverDate());
if (req.jobDuties() != null) h.setJobDuties(req.jobDuties());
if (req.pendingTaskList() != null) h.setPendingTaskList(req.pendingTaskList());
if (req.systemAccounts() != null) h.setSystemAccounts(req.systemAccounts());
if (req.keyContacts() != null) h.setKeyContacts(req.keyContacts());
if (req.filePaths() != null) h.setFilePaths(req.filePaths());
}
/** 按换行或分号拆分为非空行列表。 */
private java.util.List<String> splitLines(String raw) {
java.util.List<String> out = new java.util.ArrayList<>();
for (String part : raw.split("[\\r\\n;]+")) {
String line = part.trim();
if (!line.isEmpty()) out.add(line);
}
return out.isEmpty() ? java.util.List.of(raw.trim()) : out;
}
}