Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/ExpertController.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

126 lines
5.0 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.Expert;
import com.kaidi.oa.repository.ExpertRepository;
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.ArrayList;
import java.util.List;
/**
* Declaration expert pool (申报专家库). field (专业领域) is one of 给排水 /
* 环境工程 / 市政 / 财务 / 法律 / 水利; expertType is one of 评审专家 / 咨询专家 /
* 行业专家; status is one of 在库 / 暂停.
*/
@RestController
@RequestMapping("/api/oa/experts")
public class ExpertController {
private final ExpertRepository expertRepo;
public ExpertController(ExpertRepository expertRepo) {
this.expertRepo = expertRepo;
}
@GetMapping
public ApiResp<List<Expert>> list(@RequestParam(required = false) String expertType) {
List<Expert> list;
if (expertType != null && !expertType.isBlank()) {
list = expertRepo.findByExpertType(expertType);
} else {
list = expertRepo.findAll();
}
return ApiResp.ok(list);
}
@GetMapping("/{id}")
public ApiResp<Expert> get(@PathVariable Long id) {
return ApiResp.ok(expertRepo.findById(id)
.orElseThrow(() -> new NotFoundException("expert not found: " + id)));
}
public record CreateExpertRequest(
String name, String field, String org, String title, String expertType,
String phone, String status) {
}
@PostMapping
public ApiResp<Expert> create(@RequestBody CreateExpertRequest req) {
if (req.name() == null || req.name().isBlank()) {
throw new ApiException(400, "name is required");
}
Expert e = new Expert();
e.setName(req.name());
e.setField(req.field());
e.setOrg(req.org());
e.setTitle(req.title());
e.setExpertType(req.expertType());
e.setPhone(req.phone());
e.setStatus(req.status() == null || req.status().isBlank() ? "在库" : req.status());
e.setCreatedAt(Instant.now());
return ApiResp.ok(expertRepo.save(e));
}
@PatchMapping("/{id}")
public ApiResp<Expert> update(@PathVariable Long id, @RequestBody CreateExpertRequest req) {
Expert e = expertRepo.findById(id)
.orElseThrow(() -> new NotFoundException("expert not found: " + id));
if (req.name() != null && !req.name().isBlank()) e.setName(req.name());
if (req.field() != null) e.setField(req.field());
if (req.org() != null) e.setOrg(req.org());
if (req.title() != null) e.setTitle(req.title());
if (req.expertType() != null && !req.expertType().isBlank()) e.setExpertType(req.expertType());
if (req.phone() != null) e.setPhone(req.phone());
if (req.status() != null && !req.status().isBlank()) e.setStatus(req.status());
return ApiResp.ok(expertRepo.save(e));
}
@DeleteMapping("/{id}")
public ApiResp<Void> delete(@PathVariable Long id) {
if (!expertRepo.existsById(id)) {
throw new NotFoundException("expert not found: " + id);
}
expertRepo.deleteById(id);
return ApiResp.ok(null);
}
/**
* GET /experts/match?declType=高企 — 按申报类型关键词匹配「在库」专家。
* 规则:declType 用逗号分隔可传多个关键词;专家 field 或 expertType 含任一关键词则命中;
* 仅返回 status=在库 的专家,按 field 字母序排列。
* 补完 Gap 6 缺口:「按擅长项目类型匹配」端点。
*/
@GetMapping("/match")
public ApiResp<List<Expert>> match(@RequestParam(required = false) String declType) {
List<Expert> active = expertRepo.findByStatus("在库");
if (declType == null || declType.isBlank()) {
return ApiResp.ok(active);
}
String[] keywords = declType.split("[,]");
List<Expert> matched = new ArrayList<>();
for (Expert ex : active) {
String haystack = ((ex.getField() == null ? "" : ex.getField()) + " "
+ (ex.getExpertType() == null ? "" : ex.getExpertType())).toLowerCase();
for (String kw : keywords) {
if (kw != null && !kw.isBlank() && haystack.contains(kw.trim().toLowerCase())) {
matched.add(ex);
break;
}
}
}
return ApiResp.ok(matched);
}
}