feat(bidding): 6张CRUD表当前项目强制过滤——get_*.dspy注入会话级解析(get_session_project_id含产线隔离),强制覆盖project_id传值,无项目报错提示选择或新建;bid_task_tree删「最近进行中项目」第三级兜底(会把别人项目当当前项目),统一走会话级解析;知识库入口加require_project门禁

This commit is contained in:
yumoqing 2026-09-11 18:29:42 +08:00
parent e5516bedf3
commit d0e2ab89a8
8 changed files with 137 additions and 28 deletions

View File

@ -517,7 +517,7 @@ def register_bid_ability():
{"label": "🎯 资质", "icon": "", "url": "/pipeline-bidding/bid_qualifications/index.ui",
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📚 投标知识库", "icon": "", "url": "/pipeline-bidding/bid_kb_docs/index.ui",
"type": "popup", "width": "88%", "height": "82%"},
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📦 标书", "icon": "", "url": "/pipeline-bidding/bid_documents/index.ui",
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📋 任务树", "icon": "", "url": "/pipeline-bidding/bid_task_tree_popup.ui",

View File

@ -42,31 +42,20 @@ _ROLE_GROUPS = [
_ROLE_SET = set(r for r, _ in _ROLE_GROUPS)
async with DBPools().sqlorContext(dbname) as sor:
# ── 解析当前投标项目(三级兜底) ──
# ── 解析当前投标项目2026-09-11 收敛:统一走 get_session_project_id
# 含悬空自愈+产线隔离;删掉「最近一个进行中的投标项目」第三级兜底——
# 把别人的项目当当前项目违反「没项目要提示选择或新建」)──
_sid = (params_kw or {}).get('session_id', '') or ''
pid = ''
# 1) 投标产线默认会话的项目绑定
recs = await sor.sqlExe(
"SELECT current_project_id FROM pipeline_session_settings "
"WHERE user_id=${u}$ AND session_id='default_bidding_general' LIMIT 1", {"u": uid})
if recs:
pid = getattr(recs[0], 'current_project_id', '') or ''
# 2) agent 全局设置
if not pid:
recs = await sor.sqlExe(
"SELECT current_project_id FROM pipeline_agent_settings WHERE user_id=${u}$", {"u": uid})
if recs:
pid = getattr(recs[0], 'current_project_id', '') or ''
# 3) 最近一个进行中的投标项目
if not pid:
recs = await sor.sqlExe(
"SELECT id FROM sd_projects WHERE pipeline_id='bidding_general' "
"AND status='in_progress' ORDER BY updated_at DESC LIMIT 1")
if recs:
pid = getattr(recs[0], 'id', '') or ''
try:
from pipeline_service.workspace import get_session_project_id
pid = await get_session_project_id(sor, uid, _sid, 'bidding_general') or ''
except Exception:
pid = ''
if not pid:
if not node_id:
return json.dumps([{"id": "__root__", "label": "请先在会话中切换/创建投标项目", "is_leaf": True}], ensure_ascii=False)
return json.dumps([{"id": "__root__", "label": "请先选择或新建投标项目", "is_leaf": True}], ensure_ascii=False)
return json.dumps([], ensure_ascii=False)
pname = ''

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_chapters.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.status_text, c.section_text
from (select * from bid_chapters where 1=1 [[filterstr]]) a left join (select k as status,
from (select * from bid_chapters where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as status,
v as status_text from appcodes_kv where parentid='bid_chapter_status') b on a.status = b.status left join (select k as section,
v as section_text from appcodes_kv where parentid='bid_scoring_section') c on a.section = c.section'''

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_documents.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.status_text
from (select * from bid_documents where 1=1 [[filterstr]]) a left join (select k as status,
from (select * from bid_documents where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as status,
v as status_text from appcodes_kv where parentid='bid_doc_status') b on a.status = b.status'''

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_qc_reviews.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.qc_type_text
from (select * from bid_qc_reviews where 1=1 [[filterstr]]) a left join (select k as qc_type,
from (select * from bid_qc_reviews where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as qc_type,
v as qc_type_text from appcodes_kv where parentid='bid_qc_type') b on a.qc_type = b.qc_type'''
filterjson = params_kw.get('data_filter')

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_qualifications.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.match_status_text, c.owner_text
from (select * from bid_qualifications where 1=1 [[filterstr]]) a left join (select k as match_status,
from (select * from bid_qualifications where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as match_status,
v as match_status_text from appcodes_kv where parentid='bid_qual_match_status') b on a.match_status = b.match_status left join (select k as owner,
v as owner_text from appcodes_kv where parentid='bid_qual_owner') c on a.owner = c.owner'''

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_scoring_items.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.section_text
from (select * from bid_scoring_items where 1=1 [[filterstr]]) a left join (select k as section,
from (select * from bid_scoring_items where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as section,
v as section_text from appcodes_kv where parentid='bid_scoring_section') b on a.section = b.section'''

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤2026-09-11 用户要求会话级解析session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'bidding_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline_bidding')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_bid_tender_files.dspy:{ns=}')
if not ns.get('page'):
@ -13,7 +33,7 @@ if not ns.get('sort'):
sql = '''select a.*, b.file_type_text
from (select * from bid_tender_files where 1=1 [[filterstr]]) a left join (select k as file_type,
from (select * from bid_tender_files where project_id = ${_cur_pid}$ [[filterstr]]) a left join (select k as file_type,
v as file_type_text from appcodes_kv where parentid='bid_file_type') b on a.file_type = b.file_type'''