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>
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
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.PatentDisclosure;
|
||||
import com.kaidi.oa.repository.PatentDisclosureRepository;
|
||||
import com.kaidi.oa.repository.PatentRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
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.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 技术交底书与专利申请决策管理(工程管理中心·专利工法办,需求功能2·专利挖掘与提案)。
|
||||
*
|
||||
* 独立建模「技术交底书 → 新颖性/创造性评估 → 是否申请决策」的完整决策流,
|
||||
* 弥补旧 PatentController 仅记录申请结果、缺失申请前决策依据的缺口(W3 Gap-1)。
|
||||
*
|
||||
* 状态机:草稿 → 待评估 → 评估中 → 决定申请 → 已立案(关联Patent) | 决定不申请
|
||||
*
|
||||
* 关键端点:
|
||||
* POST /api/oa/patent-disclosures — 提交技术交底书
|
||||
* POST /api/oa/patent-disclosures/{id}/submit — 提交待评估
|
||||
* POST /api/oa/patent-disclosures/{id}/start-assess — 开始评估(专利工程师接单)
|
||||
* POST /api/oa/patent-disclosures/{id}/decide — 评估后做出申请/不申请决策
|
||||
* POST /api/oa/patent-disclosures/{id}/file — 已立案(关联 Patent 记录)
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/oa/patent-disclosures")
|
||||
public class PatentDisclosureController {
|
||||
|
||||
private final PatentDisclosureRepository disclosureRepo;
|
||||
private final PatentRepository patentRepo;
|
||||
|
||||
public PatentDisclosureController(PatentDisclosureRepository disclosureRepo,
|
||||
PatentRepository patentRepo) {
|
||||
this.disclosureRepo = disclosureRepo;
|
||||
this.patentRepo = patentRepo;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResp<List<PatentDisclosure>> list(
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String department,
|
||||
@RequestParam(required = false) String techDomain) {
|
||||
List<PatentDisclosure> result;
|
||||
if (status != null && !status.isBlank()) {
|
||||
result = disclosureRepo.findByDecisionStatus(status);
|
||||
} else if (department != null && !department.isBlank()) {
|
||||
result = disclosureRepo.findByDepartment(department);
|
||||
} else if (techDomain != null && !techDomain.isBlank()) {
|
||||
result = disclosureRepo.findByTechDomain(techDomain);
|
||||
} else {
|
||||
result = disclosureRepo.findAll();
|
||||
}
|
||||
return ApiResp.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResp<PatentDisclosure> get(@PathVariable Long id) {
|
||||
return ApiResp.ok(find(id));
|
||||
}
|
||||
|
||||
public record CreateDisclosureRequest(
|
||||
String title, String inventors, String submittedBy, String department,
|
||||
String submittedDate, String techDomain, String ipcClass,
|
||||
String techProblem, String techSolution, String techEffect, String innovationType) {
|
||||
}
|
||||
|
||||
/** 提交技术交底书(草稿状态,可编辑)。 */
|
||||
@PostMapping
|
||||
public ApiResp<PatentDisclosure> create(@RequestBody CreateDisclosureRequest req) {
|
||||
if (req.title() == null || req.title().isBlank()) {
|
||||
throw new ApiException(400, "技术名称不能为空");
|
||||
}
|
||||
PatentDisclosure d = new PatentDisclosure();
|
||||
applyCreate(d, req);
|
||||
d.setDecisionStatus("草稿");
|
||||
d.setDisclosureNo(genNo());
|
||||
d.setCreatedAt(Instant.now());
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
/** 编辑交底书(仅草稿/退回状态可编辑)。 */
|
||||
@PutMapping("/{id}")
|
||||
@Transactional
|
||||
public ApiResp<PatentDisclosure> update(@PathVariable Long id,
|
||||
@RequestBody CreateDisclosureRequest req) {
|
||||
PatentDisclosure d = find(id);
|
||||
if (!"草稿".equals(d.getDecisionStatus())) {
|
||||
throw new ApiException(409, "仅草稿状态可编辑,当前:" + d.getDecisionStatus());
|
||||
}
|
||||
applyCreate(d, req);
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
/** 提交待评估(草稿 → 待评估)。 */
|
||||
@PostMapping("/{id}/submit")
|
||||
@Transactional
|
||||
public ApiResp<PatentDisclosure> submit(@PathVariable Long id) {
|
||||
PatentDisclosure d = find(id);
|
||||
requireStatus(d, "草稿", "submit");
|
||||
if (d.getTechSolution() == null || d.getTechSolution().isBlank()) {
|
||||
throw new ApiException(400, "提交前请填写技术方案");
|
||||
}
|
||||
d.setDecisionStatus("待评估");
|
||||
if (d.getSubmittedDate() == null || d.getSubmittedDate().isBlank()) {
|
||||
d.setSubmittedDate(LocalDate.now().toString());
|
||||
}
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
public record StartAssessRequest(String assessor) {
|
||||
}
|
||||
|
||||
/** 专利工程师接单开始评估(待评估 → 评估中)。 */
|
||||
@PostMapping("/{id}/start-assess")
|
||||
@Transactional
|
||||
public ApiResp<PatentDisclosure> startAssess(@PathVariable Long id,
|
||||
@RequestBody StartAssessRequest req) {
|
||||
PatentDisclosure d = find(id);
|
||||
requireStatus(d, "待评估", "start-assess");
|
||||
if (req.assessor() == null || req.assessor().isBlank()) {
|
||||
throw new ApiException(400, "评估人不能为空");
|
||||
}
|
||||
d.setDecisionStatus("评估中");
|
||||
d.setAssessor(req.assessor());
|
||||
d.setAssessedDate(LocalDate.now().toString());
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
public record DecideRequest(boolean apply, String noveltyComment, String inventiveComment,
|
||||
String rejectReason) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估后做出申请/不申请决策(评估中 → 决定申请 | 决定不申请)。
|
||||
* apply=true → 决定申请;apply=false → 决定不申请(需填 rejectReason)。
|
||||
*/
|
||||
@PostMapping("/{id}/decide")
|
||||
@Transactional
|
||||
public ApiResp<PatentDisclosure> decide(@PathVariable Long id,
|
||||
@RequestBody DecideRequest req) {
|
||||
PatentDisclosure d = find(id);
|
||||
requireStatus(d, "评估中", "decide");
|
||||
d.setNoveltyComment(req.noveltyComment());
|
||||
d.setInventiveComment(req.inventiveComment());
|
||||
if (req.apply()) {
|
||||
d.setDecisionStatus("决定申请");
|
||||
} else {
|
||||
if (req.rejectReason() == null || req.rejectReason().isBlank()) {
|
||||
throw new ApiException(400, "决定不申请时必须填写原因");
|
||||
}
|
||||
d.setDecisionStatus("决定不申请");
|
||||
d.setRejectReason(req.rejectReason());
|
||||
}
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
public record FileRequest(Long patentId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已立案并关联 Patent 记录(决定申请 → 已立案)。
|
||||
* patentId 为提前在 /api/oa/patents 创建好的专利申请记录 id。
|
||||
*/
|
||||
@PostMapping("/{id}/file")
|
||||
@Transactional
|
||||
public ApiResp<PatentDisclosure> file(@PathVariable Long id,
|
||||
@RequestBody FileRequest req) {
|
||||
PatentDisclosure d = find(id);
|
||||
requireStatus(d, "决定申请", "file");
|
||||
if (req.patentId() == null) {
|
||||
throw new ApiException(400, "请先在专利台账创建申请记录,再回填 patentId");
|
||||
}
|
||||
if (!patentRepo.existsById(req.patentId())) {
|
||||
throw new NotFoundException("专利记录不存在:" + req.patentId());
|
||||
}
|
||||
d.setDecisionStatus("已立案");
|
||||
d.setPatentId(req.patentId());
|
||||
d.setUpdatedAt(Instant.now());
|
||||
return ApiResp.ok(disclosureRepo.save(d));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@Transactional
|
||||
public ApiResp<Void> delete(@PathVariable Long id) {
|
||||
PatentDisclosure d = find(id);
|
||||
if (!"草稿".equals(d.getDecisionStatus())) {
|
||||
throw new ApiException(409, "仅草稿状态可删除");
|
||||
}
|
||||
disclosureRepo.deleteById(id);
|
||||
return ApiResp.ok(null);
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private PatentDisclosure find(Long id) {
|
||||
return disclosureRepo.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("技术交底书不存在:" + id));
|
||||
}
|
||||
|
||||
private void requireStatus(PatentDisclosure d, String expected, String action) {
|
||||
if (!expected.equals(d.getDecisionStatus())) {
|
||||
throw new ApiException(409, "当前状态「" + d.getDecisionStatus()
|
||||
+ "」不允许操作「" + action + "」,需要:" + expected);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyCreate(PatentDisclosure d, CreateDisclosureRequest req) {
|
||||
if (req.title() != null && !req.title().isBlank()) d.setTitle(req.title());
|
||||
if (req.inventors() != null) d.setInventors(req.inventors());
|
||||
if (req.submittedBy() != null) d.setSubmittedBy(req.submittedBy());
|
||||
if (req.department() != null) d.setDepartment(req.department());
|
||||
if (req.submittedDate() != null) d.setSubmittedDate(req.submittedDate());
|
||||
if (req.techDomain() != null) d.setTechDomain(req.techDomain());
|
||||
if (req.ipcClass() != null) d.setIpcClass(req.ipcClass());
|
||||
if (req.techProblem() != null) d.setTechProblem(req.techProblem());
|
||||
if (req.techSolution() != null) d.setTechSolution(req.techSolution());
|
||||
if (req.techEffect() != null) d.setTechEffect(req.techEffect());
|
||||
if (req.innovationType() != null) d.setInnovationType(req.innovationType());
|
||||
}
|
||||
|
||||
private static long disclosureSeq = System.currentTimeMillis() % 10000;
|
||||
|
||||
private static synchronized String genNo() {
|
||||
return "TJB-" + LocalDate.now().getYear() + "-" + String.format("%04d", ++disclosureSeq % 10000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user