feat: 审批改走钉钉审批(dingdingflow)——发起时推钉钉+回调钩子写回,无配置降级人工; 补.gitignore

This commit is contained in:
yumoqing 2026-08-28 22:31:53 +08:00
parent 4b530212e6
commit 025368d66b
9 changed files with 93 additions and 7 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__/
*.pyc

View File

@ -333,6 +333,32 @@ def register_opp_slash_commands():
register_slash_command(cmd)
OPP_DD_BIZ_TYPE = "opp_dev_approval"
def register_opp_dingtalk_hook():
"""把商机产线审批结论回流挂到钉钉审批回调上。
dingdingflow 已加载时注册成功未加载宿主没装钉钉模块时静默跳过
发起审批会走降级人工通道闭环不受影响
"""
try:
from ahserver.serverenv import ServerEnv
env = ServerEnv()
register_biz_handler = getattr(env, "register_biz_handler", None)
if register_biz_handler is None:
logger.info("[opp] dingdingflow 未加载,跳过钉钉审批钩子注册")
return False
from .opp_report_capability import on_dingtalk_approval_done
register_biz_handler(OPP_DD_BIZ_TYPE, on_dingtalk_approval_done)
logger.info("[opp] 钉钉审批钩子已注册 biz_type=%s", OPP_DD_BIZ_TYPE)
return True
except Exception as e:
logger.warning("[opp] 钉钉审批钩子注册失败(不影响降级人工): %s", str(e)[:200])
return False
# import 即注册(与投标产线一致:宿主 import pipeline_opportunity 即生效)
register_opp_ability()
register_opp_slash_commands()
register_opp_dingtalk_hook()

View File

@ -12,6 +12,8 @@
import logging
from ahserver.serverenv import ServerEnv
from .opp_common import (
get_db, new_id, rec_to_dict, rows_to_dicts, can_transition,
RP_DRAFT, RP_CONFIRMED, RP_APPROVAL_INITIATED, RP_APPROVED, RP_REJECTED,
@ -133,8 +135,17 @@ async def confirm_report(report_id, ok, operator=""):
return True, "已确认" if ok else "已退回草稿"
async def initiate_approval(report_id, note="", created_by="agent.opportunity"):
"""发起研发审批:要求报告已 confirmed人工确认门禁"""
async def initiate_approval(report_id, note="", created_by="agent.opportunity",
applicant_id=""):
"""发起研发审批:要求报告已 confirmed人工确认门禁
审批通道优先级
1. 钉钉审批dda_approval_configs 配了 biz_type=opp_dev_approval 且启用时
dingdingflow.submit_approval 发起审批人节点链由配置的
approvers_config 决定结果经回调写回 opp_common 注册的钩子
2. 降级人工无钉钉配置/发起失败时落平台内人工审批任务兜底
保证产线闭环不因缺少钉钉环境而卡死
"""
db, dbname = get_db()
async with db.sqlorContext(dbname) as sor:
rep = await get_report(sor, report_id)
@ -153,12 +164,40 @@ async def initiate_approval(report_id, note="", created_by="agent.opportunity"):
})
await sor.U("opp_reports", {
"id": report_id, "status": RP_APPROVAL_INITIATED})
# 发人工审批任务
await create_human_task(
sor, rep.get("project_id", ""), HT_DEV_APPROVAL,
"研发审批:%s" % rep.get("title", ""),
note or "请审批《%s》的研发立项。" % rep.get("title", ""))
await sor.sqlExe("COMMIT", {})
# 尝试钉钉审批通道
dd_ok = False
env = ServerEnv()
submit_approval = getattr(env, "submit_approval", None)
if submit_approval is not None:
try:
org_id = rep.get("org_id", "") or "0"
r = await submit_approval(
"opp_dev_approval", aid,
"研发立项审批:%s" % rep.get("title", ""),
applicant_id or created_by, org_id)
if isinstance(r, dict) and r.get("success") and r.get("instance_id"):
dd_ok = True
logger.info("研发审批已推钉钉: report=%s instance=%s",
report_id, r.get("instance_id"))
else:
logger.warning("钉钉审批发起未成功(降级人工): report=%s msg=%s",
report_id, (r or {}).get("message", ""))
except Exception as e:
logger.warning("钉钉审批发起异常(降级人工): report=%s err=%s",
report_id, str(e)[:200])
if not dd_ok:
# 降级:平台内人工审批任务
db, dbname = get_db()
async with db.sqlorContext(dbname) as sor:
await create_human_task(
sor, rep.get("project_id", ""), HT_DEV_APPROVAL,
"研发审批:%s" % rep.get("title", ""),
note or "请审批《%s》的研发立项。" % rep.get("title", ""))
await sor.sqlExe("COMMIT", {})
return True, aid
return True, aid
@ -194,6 +233,25 @@ async def resolve_approval(approval_id, result, operator=""):
return True, result
async def on_dingtalk_approval_done(biz_id, status, approval_id, comment):
"""钉钉审批结果回调钩子dingdingflow register_biz_handler 签名)。
biz_id = opp_approvals.id发起审批时传入
status approved / rejected / cancelledcancelled 按驳回处理
产线不能停在 approval_initiated 永久卡死
"""
if status == "approved":
result = AP_APPROVED
else:
result = AP_REJECTED
ok, msg = await resolve_approval(
biz_id, result, operator="dingtalk:%s" % (approval_id or ""))
if ok:
logger.info("钉钉审批结论已回流: opp_approval=%s -> %s", biz_id, result)
else:
logger.warning("钉钉审批结论回流失败: opp_approval=%s msg=%s", biz_id, msg)
async def list_approvals(project_id="", status=""):
db, dbname = get_db()
async with db.sqlorContext(dbname) as sor: