- fp_calc.calc()返回键实锤核对: total_fp/counts/errors(原total_ufp/by_type兜底猜错,已修) - promote门禁修正: resolve_approval会把status置approved,confirmed只是中间态→门禁接受confirmed/approval_initiated/approved(原只认confirmed会导致审批通过后反而不能立项) - _cluster_ctx补项目隔离(对齐_check_batch_visible口径)+全personal类别如实拒绝可行性样本不足 - 八节模板兜底节=需求规格;四节存量解析口径零变化 - opp_approvals CRUD手改(项目过滤)被xls2ui重生成吃掉→已checkout还原,只保留opp_reports新列部分
121 lines
5.5 KiB
Plaintext
121 lines
5.5 KiB
Plaintext
# opp_mining_items.dspy - 挖掘类别的类内需求明细(三级下钻第三级)
|
||
# 入参:cluster_id。可见性经 opp_mining_flow.cluster_detail(含批次项目/org 校验)。
|
||
# 每条带来源(source_text 说明文字),整行点击 → opp_item_detail.dspy(爬虫平台详情弹窗,
|
||
# 快照 src_id 即爬虫平台 tenders.id,直接复用既有详情通道)。
|
||
|
||
import json as _json
|
||
|
||
uid = await get_user()
|
||
if not uid:
|
||
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}}
|
||
|
||
cluster_id = ((params_kw or {}).get("cluster_id") or "").strip()
|
||
if not cluster_id:
|
||
return {"widgettype": "Message", "options": {"title": "缺少参数", "message": "缺少 cluster_id"}}
|
||
try:
|
||
limit = int((params_kw or {}).get("limit") or 50)
|
||
except (TypeError, ValueError):
|
||
limit = 50
|
||
|
||
dbname = get_module_dbname('pipeline-opportunity')
|
||
|
||
session_id = (params_kw or {}).get('session_id', '') or ''
|
||
pipeline_id = (params_kw or {}).get('pipeline_id', '') or 'opportunity_general'
|
||
_cur_pid = ''
|
||
_uorg = ''
|
||
try:
|
||
from pipeline_service.workspace import get_session_project_id
|
||
async with DBPools().sqlorContext(dbname) as sor:
|
||
_cur_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
|
||
_urecs = await sor.sqlExe("SELECT orgid FROM users WHERE id=${u}$", {"u": uid})
|
||
await sor.sqlExe("COMMIT", {})
|
||
if _urecs:
|
||
_uorg = str(getattr(_urecs[0], "orgid", "") or "")
|
||
except Exception:
|
||
pass
|
||
|
||
_ctx = {"project_id": _cur_pid, "user_id": uid, "org_id": _uorg,
|
||
"pipeline_id": pipeline_id, "session_id": session_id}
|
||
|
||
async with DBPools().sqlorContext(dbname) as sor:
|
||
from pipeline_opportunity.opp_mining_flow import cluster_detail
|
||
from pipeline_opportunity.opp_common import source_text
|
||
_ok, _res = await cluster_detail(sor, _ctx, cluster_id, limit=limit)
|
||
await sor.sqlExe("COMMIT", {})
|
||
|
||
detail_url = entire_url("/pipeline-opportunity/api/opp_item_detail.dspy")
|
||
|
||
rows = []
|
||
if not _ok:
|
||
rows.append({"widgettype": "Text", "options": {
|
||
"text": str(_res)[:200], "color": "#dc2626", "padding": "12px", "halign": "left"}})
|
||
cl_name, cl_cnt = "", 0
|
||
else:
|
||
cl = _res.get("cluster") or {}
|
||
cl_name = str(cl.get("name") or "")
|
||
cl_cnt = int(cl.get("doc_count") or 0)
|
||
items = _res.get("samples") or []
|
||
if not items:
|
||
rows.append({"widgettype": "Text", "options": {
|
||
"otext": "该类别暂无明细", "text": "该类别暂无明细", "i18n": True, "color": "#64748b", "padding": "12px", "halign": "left"}})
|
||
for it in items:
|
||
src_id = str(it.get("src_id") or "")
|
||
title = str(it.get("title") or "")
|
||
budget = it.get("budget_wan") or 0
|
||
url = str(it.get("url") or "")
|
||
src_name = source_text(str(it.get("source") or ""), "")
|
||
meta = "%s | %s | 来源:%s" % (
|
||
str(it.get("publish_time") or "")[:10],
|
||
(("预算 %.2f 万" % float(budget)) if budget else "预算面议"),
|
||
src_name)
|
||
row_subs = [
|
||
{"widgettype": "Text", "options": {"text": title, "cfontsize": 0.95,
|
||
"color": "#0f172a", "halign": "left", "wrap": True}},
|
||
{"widgettype": "HBox", "options": {"width": "100%", "gap": "10px", "alignItems": "center"},
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {"text": meta, "cfontsize": 0.75,
|
||
"color": "#94a3b8", "halign": "left", "wrap": True}},
|
||
{"widgettype": "Filler"},
|
||
{"widgettype": "Text", "options": {"text": "›", "cfontsize": 1.0, "color": "#94a3b8"}},
|
||
]},
|
||
]
|
||
row_opts = {"width": "100%", "padding": "8px 12px", "gap": "4px",
|
||
"border": "1px solid #e2e8f0", "borderRadius": "8px",
|
||
"margin": "0 0 6px 0", "bgcolor": "#ffffff"}
|
||
if src_id:
|
||
row_opts["cursor"] = "pointer"
|
||
script = ("var old=bricks.getWidgetById('opp_item_detail_pw',bricks.app);"
|
||
"if(old&&old.destroy){old.destroy();}"
|
||
"var rp=await fetch(" + _json.dumps(detail_url) + "+'?id='+encodeURIComponent(" + _json.dumps(src_id) + "));"
|
||
"var d=await rp.json();if(d){bricks.widgetBuild(d,bricks.app);}")
|
||
rows.append({"widgettype": "VBox", "options": row_opts,
|
||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||
"target": "self", "script": script}],
|
||
"subwidgets": row_subs})
|
||
elif url:
|
||
row_opts["cursor"] = "pointer"
|
||
script = "window.open(" + _json.dumps(url) + ",'_blank');"
|
||
rows.append({"widgettype": "VBox", "options": row_opts,
|
||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||
"target": "self", "script": script}],
|
||
"subwidgets": row_subs})
|
||
else:
|
||
rows.append({"widgettype": "VBox", "options": row_opts, "subwidgets": row_subs})
|
||
|
||
return {
|
||
"widgettype": "PopupWindow",
|
||
"id": "opp_mine_items_pw",
|
||
"options": {"title": "类内需求明细:%s(%d 条,显示前 %d)" % (cl_name, cl_cnt, limit),
|
||
"width": "76%", "height": "80%", "auto_open": True, "resizable": True},
|
||
"subwidgets": [{
|
||
"widgettype": "VBox",
|
||
"options": {"css": "filler", "width": "100%", "height": "100%",
|
||
"padding": "12px 16px", "gap": "0px", "overflow": "auto"},
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {
|
||
"otext": "点击任一条目弹出需求详情(数据来自数据爬取平台快照,每条标注来源平台)。", "text": "点击任一条目弹出需求详情(数据来自数据爬取平台快照,每条标注来源平台)。", "i18n": True,
|
||
"cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}},
|
||
] + rows,
|
||
}],
|
||
}
|