diff --git a/src/main/java/com/yunhai/notice/controller/api/NoticeApprovalController.java b/src/main/java/com/yunhai/notice/controller/api/NoticeApprovalController.java
index d74294f..8511da6 100644
--- a/src/main/java/com/yunhai/notice/controller/api/NoticeApprovalController.java
+++ b/src/main/java/com/yunhai/notice/controller/api/NoticeApprovalController.java
@@ -1,7 +1,11 @@
package com.yunhai.notice.controller.api;
import com.yunhai.notice.config.Config;
+import com.yunhai.notice.entity.Notice;
+import com.yunhai.notice.repository.NoticeRepository;
import com.yunhai.notice.service.flow.NoticeApprovalClient;
+import com.yunhai.notice.service.flow.NoticeFlowEmbedService;
+import com.yunhai.notice.service.flow.NoticeFlowGuard;
import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.model.BoaUser;
@@ -15,15 +19,19 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
+import java.util.List;
import java.util.Map;
/**
- * 应用内审批(第三步 T-C4):"我的待审核"数据面。前端不直调 oapi——
- * operatorId/operatorName 一律取 CAS 登录态(BoaUser),请求参数里不收身份字段,
+ * 应用内审批(第三步 T-C4):"我的待审核"数据面 + v2.1 业务详情卡片/记录/完整办理换址。
+ * 前端不直调 oapi——operatorId/operatorName 一律取 CAS 登录态(BoaUser),请求参数里不收身份字段,
* 防伪造办理人;对端 flow-center 再按任务办理人复核(双层)。
*
* - GET Approval/Todo:待办分页(bizType=notice 固定);
* - GET Approval/Instances:办件分页(status 可选);
+ * - GET Approval/ByBiz:当前人在该通知上的待办(0/1/N,卡片据此决定可写还是只读);
+ * - GET Approval/RecordUrl:审批记录抽屉地址(token 只读页,复用 {@link NoticeFlowEmbedService});
+ * - GET Approval/TaskPageUrl:完整办理任务页地址(先确认 taskId 属于当前人待办);
* - POST Approval/Approve / Reject:办理(body 仅 opinion/edgeName)。
*
*/
@@ -33,6 +41,9 @@ import java.util.Map;
public class NoticeApprovalController {
private final NoticeApprovalClient approvalClient;
+ private final NoticeFlowEmbedService embedService;
+ private final NoticeRepository noticeRepository;
+ private final NoticeFlowGuard noticeFlowGuard;
/** 我的待审核(列表页数据源) */
@GetMapping("Todo")
@@ -72,6 +83,79 @@ public class NoticeApprovalController {
body == null ? null : body.get("opinion"));
}
+ /**
+ * 当前人在该通知上的待办(v2.1 卡片数据源)。身份只取会话;{@code serid} 先过与记录页
+ * 同一套可见性守卫(未发布通知仅提交人/管理员/办理人/被抄送人可见)。
+ * 出参 {@code tasks} 的 0/1/N 由前端据此决定"只读 / 可办理 / 多个待办引导去列表",
+ * 本端不猜选、不合并。办件不存在与"有办件但不是我的待办"同形(空 tasks),不用于探测。
+ */
+ @GetMapping("ByBiz")
+ public Map byBiz(@RequestParam("serid") Integer serid, BoaUser user) {
+ Operator op = requireOperator(user);
+ requireVisible(serid, user);
+ return approvalClient.byBiz(op.schId, op.userId, serid);
+ }
+
+ /**
+ * 审批记录抽屉地址(token 只读页):直接复用 {@link NoticeFlowEmbedService#embedUrl},
+ * 那里已做通知存在性 + 未发布可见性 + "尚未进入流程"三重校验,且 token 现取现发不缓存。
+ * 与 {@code /api/notice/flow/Embed} 同一条实现,避免两个入口对同一 Drawer 漂移出不同守卫口径。
+ */
+ @GetMapping("RecordUrl")
+ public Map recordUrl(@RequestParam("serid") Integer serid, BoaUser user) {
+ requireOperator(user);
+ return embedService.embedUrl(serid, user);
+ }
+
+ /**
+ * 完整办理任务页地址(CAS 会话页,无 token)。
+ * 先打 byBiz 确认 {@code taskId} 确实在当前人该通知的待办里才签发——任务页是写面,
+ * 顺手签发一个别人的 taskId 等于替他人挂一个办理入口。flow-center 侧仍会按 assigneeId
+ * 逐字复核,本处只是不把无效地址散播到前端。
+ */
+ @GetMapping("TaskPageUrl")
+ public Map taskPageUrl(@RequestParam("serid") Integer serid,
+ @RequestParam("taskId") Long taskId, BoaUser user) {
+ Operator op = requireOperator(user);
+ requireVisible(serid, user);
+ if (!isMyPendingTask(serid, taskId, op)) {
+ throw new BizException(ErrorCode.FORBIDDEN, "该任务不是你在本通知上的待办");
+ }
+ return approvalClient.taskPageUrl(op.schId, taskId);
+ }
+
+ /** byBiz 出参里是否含该 taskId(对端 tasks 元素为 {taskId,nodeId,nodeName,arriveTime,outlets}) */
+ @SuppressWarnings("unchecked")
+ private boolean isMyPendingTask(Integer serid, Long taskId, Operator op) {
+ Map data = approvalClient.byBiz(op.schId, op.userId, serid);
+ Object tasks = data == null ? null : data.get("tasks");
+ if (!(tasks instanceof List)) {
+ return false;
+ }
+ for (Object row : (List