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 VALID_STATUSES = List.of( "待提交", "已提交", "总部审阅中", "资料完备", "已归档"); /** 合法协作类型集合。 */ private static final List 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( @RequestParam(required = false) String status, @RequestParam(required = false) String branchName, @RequestParam(required = false) String submitter, @RequestParam(required = false) Long bidId) { List 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 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 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 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 delete(@PathVariable Long id) { if (!collabRepo.existsById(id)) { throw new NotFoundException("标书协作工单不存在: " + id); } collabRepo.deleteById(id); return ApiResp.ok(null); } }