From 4e621852dae6af5699cf0bdd8aae52c84e6ab9f8 Mon Sep 17 00:00:00 2001 From: ymq Date: Fri, 11 Sep 2026 12:58:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(sdlc):=20=E6=B5=81=E7=A8=8B=E8=A3=81?= =?UTF-8?q?=E5=89=AA=E7=A1=AE=E8=AE=A4UI=E2=80=94=E2=80=94flow=5Fplan=5Fco?= =?UTF-8?q?nfirm.dspy=E7=A1=AE=E8=AE=A4/=E9=A9=B3=E5=9B=9E=E7=AB=AF?= =?UTF-8?q?=E7=82=B9(=E7=94=A8=E6=88=B7=E4=B8=93=E5=B1=9E,agent=E6=97=A0?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E6=9D=83);todo=5Fdetail/my=5Ftodos=5Fpopup?= =?UTF-8?q?=E5=8A=A0flow=5Fplan=5Fconfirm=E5=BE=85=E5=8A=9E=E5=88=86?= =?UTF-8?q?=E6=94=AF(=E8=A3=81=E5=89=AA=E6=96=B9=E6=A1=88=E5=AF=B9?= =?UTF-8?q?=E7=85=A7+=E9=A3=8E=E9=99=A9=E6=8F=90=E7=A4=BA+=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4/=E9=A9=B3=E5=9B=9E=E6=8C=89=E9=92=AE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wwwroot/api/flow_plan_confirm.dspy | 36 +++++++++++++++++++++++ wwwroot/api/my_todos_popup.dspy | 2 ++ wwwroot/api/todo_detail.dspy | 47 ++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 wwwroot/api/flow_plan_confirm.dspy diff --git a/wwwroot/api/flow_plan_confirm.dspy b/wwwroot/api/flow_plan_confirm.dspy new file mode 100644 index 0000000..279a093 --- /dev/null +++ b/wwwroot/api/flow_plan_confirm.dspy @@ -0,0 +1,36 @@ +# flow_plan_confirm.dspy - 产线流程裁剪计划的用户确认/驳回端点 +# +# 入参(form-encoded): +# plan_id 流程计划 id(pipeline_flow_plans.id) +# decision confirm | reject +# comment 驳回意见(reject 时必填;confirm 可空) +# +# 安全:确认动作是用户专属(同机构 + owner.superuser/项目创建者校验在 +# flow_plan_capability._check_confirm_operator);agent 无对应工具,无法代替确认。 +# 驳回意见落 reject_comment,会话 agent 用 get_flow_plan 读到后按意见修订重提(version+1)。 + +user_id = await get_user() +if not user_id: + return json.dumps({"success": False, "error": "未登录"}, ensure_ascii=False) + +plan_id = str((params_kw or {}).get('plan_id') or '').strip() +decision = str((params_kw or {}).get('decision') or '').strip().lower() +comment = str((params_kw or {}).get('comment') or '').strip() + +if not plan_id: + return json.dumps({"success": False, "error": "缺少 plan_id"}, ensure_ascii=False) + +if decision == 'confirm': + ok, msg = await flow_plan_confirm(plan_id, operator_id=user_id) +elif decision == 'reject': + if not comment: + return json.dumps({"success": False, "error": "驳回必须填写意见(助手按意见修订流程)"}, + ensure_ascii=False) + ok, msg = await flow_plan_reject(plan_id, comment, operator_id=user_id) +else: + return json.dumps({"success": False, "error": "decision 必须是 confirm 或 reject"}, + ensure_ascii=False) + +if ok: + return json.dumps({"success": True, "message": msg}, ensure_ascii=False) +return json.dumps({"success": False, "error": msg}, ensure_ascii=False) diff --git a/wwwroot/api/my_todos_popup.dspy b/wwwroot/api/my_todos_popup.dspy index 7229843..ed4fb91 100644 --- a/wwwroot/api/my_todos_popup.dspy +++ b/wwwroot/api/my_todos_popup.dspy @@ -15,11 +15,13 @@ TYPE_LABEL = { 'requirement_confirmation': '需求确认', 'design_confirmation': '设计确认', 'bug_acceptance': 'Bug 验收', + 'flow_plan_confirm': '流程裁剪确认', } TYPE_COLOR = { 'requirement_confirmation': '#2563eb', 'design_confirmation': '#2563eb', 'bug_acceptance': '#f0a040', + 'flow_plan_confirm': '#7c3aed', } detail_url = entire_url("/pipeline-sdlc/api/todo_detail.dspy") diff --git a/wwwroot/api/todo_detail.dspy b/wwwroot/api/todo_detail.dspy index 9607a1e..e849aca 100644 --- a/wwwroot/api/todo_detail.dspy +++ b/wwwroot/api/todo_detail.dspy @@ -22,12 +22,14 @@ TYPE_LABEL = { 'design_confirmation': '设计确认', 'bug_acceptance': 'Bug 验收', 'kb_ingest_confirm': '知识库入库建议', + 'flow_plan_confirm': '流程裁剪确认', } TYPE_COLOR = { 'requirement_confirmation': '#2563eb', 'design_confirmation': '#2563eb', 'bug_acceptance': '#f0a040', 'kb_ingest_confirm': '#10b981', + 'flow_plan_confirm': '#7c3aed', } @@ -527,6 +529,51 @@ async with DBPools().sqlorContext(dbname) as sor: md.append('未找到该项目的标书文件记录。') md.append('') + elif task_type == 'flow_plan_confirm': + # 产线流程裁剪确认:正文(description)已含裁剪方案对照表(propose 时渲染), + # 这里补确认/驳回按钮。plan 由 confirm_task_id 反查。 + fp_url = entire_url("/pipeline-sdlc/api/flow_plan_confirm.dspy") + fprecs = await sor.sqlExe( + "SELECT id, status, version FROM pipeline_flow_plans WHERE confirm_task_id=${t}$ " + "ORDER BY version DESC LIMIT 1", {"t": oid}) + await sor.sqlExe("COMMIT", {}) + if fprecs: + fp = fprecs[0] + plan_id = _s(getattr(fp, 'id', '')) + fp_status = _s(getattr(fp, 'status', '')) + sub_lines.append('计划版本:v' + _s(getattr(fp, 'version', ''))) + if fp_status != 'pending_confirm': + md.append('## 计划状态') + md.append('') + md.append('该计划已处理(当前状态:' + fp_status + '),无需再确认。') + md.append('') + else: + action_widgets.append(_input_box( + 'fp_comment', '驳回意见(驳回时必填,助手按意见修订流程后重新提案)', + '请填写驳回意见')) + ok_script = (_post_js(fp_url, [('plan_id', _json.dumps(plan_id)), + ('decision', "'confirm'"), ('comment', "''")]) + _tail) + no_script = (_read_input_js('fp_comment') + + "if(!cv){var mn=new bricks.Message({title:'请填写驳回意见'," + "message:'驳回前请说明需要怎么改(助手按意见修订重提)'});mn.open();return;}" + + _post_js(fp_url, [('plan_id', _json.dumps(plan_id)), + ('decision', "'reject'"), ('comment', 'cv')]) + _tail) + action_widgets.append({ + "widgettype": "HBox", + "options": {"width": "100%", "gap": "8px", "padding": "12px 14px", "halign": "right"}, + "subwidgets": [ + _btn('确认并启用', 'primary', ok_script, + conform={"title": "确认流程裁剪", "message": "确认后流程按裁剪方案生效,且不可再修改。确认?", + "conform": {"label": "确认启用"}, "discard": {"label": "再看看"}}), + _btn('驳回(按意见修订)', 'small', no_script) + ] + }) + else: + md.append('## 计划状态') + md.append('') + md.append('未找到对应的流程裁剪计划记录。') + md.append('') + elif task_type == 'kb_ingest_confirm': # PM 建议文件入知识库:展示建议详情 + 文件(打开/下载)+ 批准/驳回 _sg = None