package com.kaidi.oa.web;
import com.kaidi.oa.common.ApiException;
import com.kaidi.oa.common.ApiResp;
import com.kaidi.oa.common.Money;
import com.kaidi.oa.common.NotFoundException;
import com.kaidi.oa.domain.DeclarationStep;
import com.kaidi.oa.domain.FormInstance;
import com.kaidi.oa.domain.PolicyApplication;
import com.kaidi.oa.domain.RdDeclPlan;
import com.kaidi.oa.repository.DeclarationStepRepository;
import com.kaidi.oa.repository.FormInstanceRepository;
import com.kaidi.oa.repository.PolicyApplicationRepository;
import com.kaidi.oa.repository.RdDeclPlanRepository;
import com.kaidi.oa.service.WorkflowService;
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.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 创新研发中心·申报服务部 — 年度/季度通用申报计划(需求 §1 政策与申报计划管理)。
*
*
补完缺口:{@link com.kaidi.oa.domain.WmDeclPlan} 是工程管理中心专属的专利/工法申报
* 计划,本控制器面向申报服务部的通用政策申报计划,支持:
*
* - 计划 CRUD:含预算(BigDecimal)、预期收益、责任人/部门、计划时间表;
* - 状态机:草稿 → 审批中 → 已批准 → 执行中 → 已完成 / 已驳回 / 已取消;
* - 提交审批(接 WorkflowService,自动展开审批流);
* - 审批通过后自动发起 PolicyApplication(申报条件自查与进度跟踪联动);
* - 进度回写:从关联的 DeclarationStep 计算完成率自动回写 progress 字段。
*
*
* 端点:/api/oa/rd-decl-plans
* 写口受 AuthInterceptor default-deny(ADMIN/APPROVER) 保护;聚合读已登记到
* SENSITIVE_READ_PREFIXES(含金额聚合)。
*/
@RestController
@RequestMapping("/api/oa/rd-decl-plans")
public class RdDeclPlanController {
/** 申报计划审批流模板 ID(需在 TemplateSeedData 中注册)。 */
private static final String PLAN_TEMPLATE_ID = "rd-decl-plan-approve";
private static final List VALID_STATUS = List.of(
"草稿", "审批中", "已批准", "执行中", "已完成", "已驳回", "已取消");
private static final List PERIOD_TYPES = List.of("年度", "季度");
private static final List POLICY_CATEGORIES = List.of("资质类", "资金类", "荣誉类", "人才类");
private final RdDeclPlanRepository planRepo;
private final DeclarationStepRepository stepRepo;
private final PolicyApplicationRepository appRepo;
private final FormInstanceRepository instanceRepo;
private final WorkflowService workflowService;
public RdDeclPlanController(RdDeclPlanRepository planRepo,
DeclarationStepRepository stepRepo,
PolicyApplicationRepository appRepo,
FormInstanceRepository instanceRepo,
WorkflowService workflowService) {
this.planRepo = planRepo;
this.stepRepo = stepRepo;
this.appRepo = appRepo;
this.instanceRepo = instanceRepo;
this.workflowService = workflowService;
}
// ---------- 台账 CRUD ----------
@GetMapping
public ApiResp> list(@RequestParam(required = false) Integer planYear,
@RequestParam(required = false) String status,
@RequestParam(required = false) String policyCategory) {
if (planYear != null && status != null && !status.isBlank()) {
return ApiResp.ok(planRepo.findByPlanYearAndStatus(planYear, status));
}
if (planYear != null) {
return ApiResp.ok(planRepo.findByPlanYear(planYear));
}
if (status != null && !status.isBlank()) {
return ApiResp.ok(planRepo.findByStatus(status));
}
if (policyCategory != null && !policyCategory.isBlank()) {
return ApiResp.ok(planRepo.findByPolicyCategoryAndStatus(policyCategory, "已批准"));
}
return ApiResp.ok(planRepo.findAll());
}
@GetMapping("/{id}")
public ApiResp get(@PathVariable Long id) {
return ApiResp.ok(find(id));
}
public record PlanRequest(
String name, String periodType, Integer planYear, String planQuarter,
String policyCategory, String programName, String department, String owner,
Double budget, Double expectedBenefit,
String planApplyDate, String planEndDate, String remark) {
}
@PostMapping
@Transactional
public ApiResp create(@RequestBody PlanRequest req) {
validatePlanRequest(req);
RdDeclPlan p = new RdDeclPlan();
applyEditable(p, req);
p.setStatus("草稿");
p.setProgress(0);
p.setCreatedAt(Instant.now());
p.setUpdatedAt(Instant.now());
return ApiResp.ok(planRepo.save(p));
}
@PatchMapping("/{id}")
@Transactional
public ApiResp update(@PathVariable Long id, @RequestBody PlanRequest req) {
RdDeclPlan p = find(id);
if (!List.of("草稿", "已驳回").contains(p.getStatus())) {
throw new ApiException(409, "仅「草稿」或「已驳回」计划可编辑,当前状态:" + p.getStatus());
}
applyEditable(p, req);
p.setUpdatedAt(Instant.now());
return ApiResp.ok(planRepo.save(p));
}
@DeleteMapping("/{id}")
@Transactional
public ApiResp delete(@PathVariable Long id) {
RdDeclPlan p = find(id);
if (!"草稿".equals(p.getStatus())) {
throw new ApiException(409, "仅「草稿」计划可删除,当前状态:" + p.getStatus());
}
planRepo.deleteById(id);
return ApiResp.ok(null);
}
// ---------- 状态机:提交审批 ----------
public record ApprovalSubmitRequest(String applicant) {
}
/**
* 提交审批(草稿/已驳回 → 审批中)。
* 接 WorkflowService 自动展开审批流,同一计划不可重复提交进行中的流程。
*/
@PostMapping("/{id}/submit-approval")
@Transactional
public ApiResp submitApproval(@PathVariable Long id,
@RequestBody(required = false) ApprovalSubmitRequest req) {
RdDeclPlan p = find(id);
if (!List.of("草稿", "已驳回").contains(p.getStatus())) {
throw new ApiException(409, "当前状态不允许提交审批:" + p.getStatus());
}
String applicant = req != null && req.applicant() != null && !req.applicant().isBlank()
? req.applicant() : (p.getOwner() != null ? p.getOwner() : "admin");
// 检查是否已有进行中的审批实例
boolean alreadyPending = instanceRepo.findAll().stream()
.anyMatch(inst -> PLAN_TEMPLATE_ID.equals(inst.getTemplateId())
&& inst.getTitle() != null
&& inst.getTitle().contains(p.getName())
&& !List.of("已驳回", "已撤回", "已办结").contains(inst.getStatus()));
if (alreadyPending) {
throw new ApiException(409, "该申报计划已有进行中的审批流程,请勿重复提交");
}
String dataJson = String.format(
"{\"planName\":\"%s\",\"periodType\":\"%s\",\"planYear\":%d," +
"\"policyCategory\":\"%s\",\"programName\":\"%s\"," +
"\"budget\":%s,\"expectedBenefit\":%s,\"owner\":\"%s\",\"applicant\":\"%s\"}",
nvl(p.getName()), nvl(p.getPeriodType()),
p.getPlanYear() == null ? 0 : p.getPlanYear(),
nvl(p.getPolicyCategory()), nvl(p.getProgramName()),
Money.nz(p.getBudget()).toPlainString(),
Money.nz(p.getExpectedBenefit()).toPlainString(),
nvl(p.getOwner()), applicant);
String title = "申报计划审批-" + p.getName();
FormInstance inst = workflowService.submit(PLAN_TEMPLATE_ID, dataJson, title, applicant);
p.setStatus("审批中");
p.setInstanceId(inst.getId());
p.setUpdatedAt(Instant.now());
planRepo.save(p);
return ApiResp.ok(inst);
}
/**
* 查询计划关联的审批状态(最新实例)。
*/
@GetMapping("/{id}/approval-status")
public ApiResp