恢复点(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>
212 lines
8.5 KiB
Java
212 lines
8.5 KiB
Java
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.BidTenderCollab;
|
|
import com.kaidi.oa.repository.BidRepository;
|
|
import com.kaidi.oa.repository.BidTenderCollabRepository;
|
|
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.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;
|
|
|
|
/**
|
|
* 标书协作工单管理(BidTenderCollab)。
|
|
*
|
|
* 办事处向总部提报本地价格/资质材料,协助编制标书;总部审阅后标记"资料完备"。
|
|
* collabType: 本地价格提供 / 资质材料提供 / 踏勘报告 / 综合协助
|
|
* status: 待提交 / 已提交 / 总部审阅中 / 资料完备 / 已归档
|
|
*/
|
|
@RestController
|
|
@RequestMapping("/api/oa/bid-tender-collabs")
|
|
public class BidTenderCollabController {
|
|
|
|
private final BidTenderCollabRepository collabRepo;
|
|
private final BidRepository bidRepo;
|
|
|
|
public BidTenderCollabController(BidTenderCollabRepository collabRepo,
|
|
BidRepository bidRepo) {
|
|
this.collabRepo = collabRepo;
|
|
this.bidRepo = bidRepo;
|
|
}
|
|
|
|
/** 合法状态集合(避免客户端写入非法状态)。 */
|
|
private static final List<String> VALID_STATUSES = List.of(
|
|
"待提交", "已提交", "总部审阅中", "资料完备", "已归档");
|
|
|
|
/** 合法协作类型集合。 */
|
|
private static final List<String> VALID_TYPES = List.of(
|
|
"本地价格提供", "资质材料提供", "踏勘报告", "综合协助");
|
|
|
|
/** 状态迁移合法路径(from → allowed nexts)。 */
|
|
private static boolean isValidTransition(String from, String to) {
|
|
if (from == null) return true;
|
|
return switch (from) {
|
|
case "待提交" -> List.of("已提交").contains(to);
|
|
case "已提交" -> List.of("总部审阅中", "待提交").contains(to);
|
|
case "总部审阅中" -> List.of("资料完备", "已提交").contains(to);
|
|
case "资料完备" -> List.of("已归档").contains(to);
|
|
default -> false;
|
|
};
|
|
}
|
|
|
|
@GetMapping
|
|
public ApiResp<List<BidTenderCollab>> list(
|
|
@RequestParam(required = false) String status,
|
|
@RequestParam(required = false) String branchName,
|
|
@RequestParam(required = false) String submitter,
|
|
@RequestParam(required = false) Long bidId) {
|
|
List<BidTenderCollab> result;
|
|
if (bidId != null) {
|
|
result = collabRepo.findByBidId(bidId);
|
|
} else if (status != null && !status.isBlank()) {
|
|
result = collabRepo.findByStatus(status);
|
|
} else if (branchName != null && !branchName.isBlank()) {
|
|
result = collabRepo.findByBranchName(branchName);
|
|
} else if (submitter != null && !submitter.isBlank()) {
|
|
result = collabRepo.findBySubmitter(submitter);
|
|
} else {
|
|
result = collabRepo.findAll();
|
|
}
|
|
return ApiResp.ok(result);
|
|
}
|
|
|
|
@GetMapping("/{id}")
|
|
public ApiResp<BidTenderCollab> get(@PathVariable Long id) {
|
|
return ApiResp.ok(collabRepo.findById(id)
|
|
.orElseThrow(() -> new NotFoundException("标书协作工单不存在: " + id)));
|
|
}
|
|
|
|
public record CreateCollabRequest(
|
|
Long bidId,
|
|
String collabType,
|
|
String subject,
|
|
String branchName,
|
|
String submitter,
|
|
String hqReviewer,
|
|
String materialSummary,
|
|
Long storedFileId,
|
|
String dueDate,
|
|
String status) {
|
|
}
|
|
|
|
@PostMapping
|
|
@Transactional
|
|
public ApiResp<BidTenderCollab> create(@RequestBody CreateCollabRequest req) {
|
|
if (req.subject() == null || req.subject().isBlank()) {
|
|
throw new ApiException(400, "协作主题(subject)不能为空");
|
|
}
|
|
if (req.branchName() == null || req.branchName().isBlank()) {
|
|
throw new ApiException(400, "提报办事处(branchName)不能为空");
|
|
}
|
|
if (req.collabType() != null && !VALID_TYPES.contains(req.collabType())) {
|
|
throw new ApiException(400, "collabType 非法:" + req.collabType());
|
|
}
|
|
String initStatus = (req.status() != null && !req.status().isBlank()) ? req.status() : "待提交";
|
|
if (!VALID_STATUSES.contains(initStatus)) {
|
|
throw new ApiException(400, "status 非法:" + initStatus);
|
|
}
|
|
|
|
// 若关联了 bid,冗余写入项目名称供展示。
|
|
String bidProjectName = null;
|
|
if (req.bidId() != null) {
|
|
bidProjectName = bidRepo.findById(req.bidId())
|
|
.map(b -> b.getProjectName())
|
|
.orElse(null);
|
|
}
|
|
|
|
BidTenderCollab c = new BidTenderCollab();
|
|
c.setBidId(req.bidId());
|
|
c.setCollabType(req.collabType());
|
|
c.setSubject(req.subject());
|
|
c.setBranchName(req.branchName());
|
|
c.setSubmitter(req.submitter());
|
|
c.setHqReviewer(req.hqReviewer());
|
|
c.setMaterialSummary(req.materialSummary());
|
|
c.setStoredFileId(req.storedFileId());
|
|
c.setDueDate(req.dueDate());
|
|
c.setStatus(initStatus);
|
|
c.setBidProjectName(bidProjectName);
|
|
c.setCreatedAt(Instant.now());
|
|
c.setUpdatedAt(Instant.now());
|
|
return ApiResp.ok(collabRepo.save(c));
|
|
}
|
|
|
|
public record UpdateCollabRequest(
|
|
String collabType,
|
|
String subject,
|
|
String branchName,
|
|
String submitter,
|
|
String hqReviewer,
|
|
String materialSummary,
|
|
Long storedFileId,
|
|
String dueDate,
|
|
String status,
|
|
String reviewNote) {
|
|
}
|
|
|
|
/**
|
|
* PATCH /{id} — 部分更新;状态迁移校验合法路径。
|
|
* 总部回填审阅意见(reviewNote)时同步推进状态到"资料完备"。
|
|
*/
|
|
@PatchMapping("/{id}")
|
|
@Transactional
|
|
public ApiResp<BidTenderCollab> update(@PathVariable Long id,
|
|
@RequestBody UpdateCollabRequest req) {
|
|
BidTenderCollab c = collabRepo.findById(id)
|
|
.orElseThrow(() -> new NotFoundException("标书协作工单不存在: " + id));
|
|
|
|
if (req.collabType() != null) {
|
|
if (!VALID_TYPES.contains(req.collabType())) {
|
|
throw new ApiException(400, "collabType 非法:" + req.collabType());
|
|
}
|
|
c.setCollabType(req.collabType());
|
|
}
|
|
if (req.subject() != null) {
|
|
if (req.subject().isBlank()) throw new ApiException(400, "协作主题不能为空");
|
|
c.setSubject(req.subject());
|
|
}
|
|
if (req.branchName() != null) c.setBranchName(req.branchName());
|
|
if (req.submitter() != null) c.setSubmitter(req.submitter());
|
|
if (req.hqReviewer() != null) c.setHqReviewer(req.hqReviewer());
|
|
if (req.materialSummary() != null) c.setMaterialSummary(req.materialSummary());
|
|
if (req.storedFileId() != null) c.setStoredFileId(req.storedFileId());
|
|
if (req.dueDate() != null) c.setDueDate(req.dueDate());
|
|
if (req.reviewNote() != null) c.setReviewNote(req.reviewNote());
|
|
|
|
if (req.status() != null && !req.status().equals(c.getStatus())) {
|
|
if (!VALID_STATUSES.contains(req.status())) {
|
|
throw new ApiException(400, "status 非法:" + req.status());
|
|
}
|
|
if (!isValidTransition(c.getStatus(), req.status())) {
|
|
throw new ApiException(400,
|
|
"非法状态迁移:" + c.getStatus() + " → " + req.status());
|
|
}
|
|
c.setStatus(req.status());
|
|
}
|
|
|
|
c.setUpdatedAt(Instant.now());
|
|
return ApiResp.ok(collabRepo.save(c));
|
|
}
|
|
|
|
@DeleteMapping("/{id}")
|
|
@Transactional
|
|
public ApiResp<Void> delete(@PathVariable Long id) {
|
|
if (!collabRepo.existsById(id)) {
|
|
throw new NotFoundException("标书协作工单不存在: " + id);
|
|
}
|
|
collabRepo.deleteById(id);
|
|
return ApiResp.ok(null);
|
|
}
|
|
}
|