diff --git a/MD/test/05_test_multi_role_v2.py b/MD/test/05_test_multi_role_v2.py new file mode 100755 index 000000000..cd5546078 --- /dev/null +++ b/MD/test/05_test_multi_role_v2.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +HealthLink-HIS 三甲医院多角色协作全流程测试 v2 +基于实际Controller端点修正 +""" +import requests, json, sys, os +from datetime import datetime + +BASE_URL = "http://localhost:18082/healthlink-his" +ACCOUNTS = { + "admin": {"user":"admin","pwd":"admin123","role":"超级管理员"}, + "doctor1": {"user":"doctor1","pwd":"123456","role":"医生"}, + "doctor_jz": {"user":"jzys","pwd":"123456","role":"急诊医生"}, + "nurse_jz": {"user":"jzhs","pwd":"123456","role":"急诊护士"}, + "nurse_nk": {"user":"nkhs1","pwd":"123456","role":"内科护士"}, + "nurse_ss": {"user":"ssshs1","pwd":"123456","role":"手术室护士"}, + "pharmacist":{"user":"yjk1","pwd":"123456","role":"药剂科"}, + "tech": {"user":"医技员","pwd":"123456","role":"医技"}, + "finance": {"user":"sfy","pwd":"123456","role":"收费员"}, + "consult": {"user":"hzzj1","pwd":"123456","role":"会诊专家"}, +} + +class S: + def __init__(self): + self.t=self.p=self.f=0; self.r=[] + def ok(self,sc,step,role,name,detail=""): + self.t+=1;self.p+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"✅","d":detail}) + print(f" ✅ [{sc}] {step} ({role}): {name}") + def fail(self,sc,step,role,name,detail=""): + self.t+=1;self.f+=1;self.r.append({"sc":sc,"step":step,"role":role,"name":name,"s":"❌","d":detail}) + print(f" ❌ [{sc}] {step} ({role}): {name}") + if detail: print(f" → {detail}") + def chk(self,sc,step,role,name,resp,code=200,fields=None): + if resp.get("code")!=code: + self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp + if fields: + for f in fields: + if f not in resp: + self.fail(sc,step,role,name,f"缺少字段: {f}"); return resp + self.ok(sc,step,role,name); return resp + def chk_list(self,sc,step,role,name,resp): + if resp.get("code")!=200: + self.fail(sc,step,role,name,f"code={resp.get('code')}, msg={resp.get('msg','')[:100]}"); return resp + rows=resp.get("rows",resp.get("data",[])) + if isinstance(rows,list): + self.ok(sc,step,role,name,f"返回{len(rows)}条"); return resp + elif isinstance(rows,dict): + # 嵌套格式,也算通过但标记格式问题 + self.ok(sc,step,role,name,f"返回dict格式(非标准rows)"); return resp + else: + self.fail(sc,step,role,name,f"rows类型异常: {type(rows)}"); return resp + +s=S(); tokens={} + +def login(k): + a=ACCOUNTS[k] + try: + r=requests.post(f"{BASE_URL}/login",json={"username":a["user"],"password":a["pwd"],"tenantId":"1"}).json() + if r.get("token"): tokens[k]=r["token"]; return True + except: pass + return False + +def H(k): return {"Authorization":f"Bearer {tokens.get(k,'')}","Content-Type":"application/json"} +def G(k,p,params=None): return requests.get(f"{BASE_URL}{p}",headers=H(k),params=params).json() +def P(k,p,d=None): return requests.post(f"{BASE_URL}{p}",headers=H(k),json=d).json() +def U(k,p,d=None): return requests.put(f"{BASE_URL}{p}",headers=H(k),json=d).json() + +# ============================ +# 场景1: 门诊就诊全流程 +# ============================ +def s1_outpatient(): + print(f"\n{'='*60}\n场景1: 门诊就诊全流程\n{'='*60}") + s.chk("门诊","1.1","收费员","挂号初始化",G("finance","/charge-manage/register/init"),200) + s.chk("门诊","1.2","收费员","查询患者",G("finance","/charge-manage/register/patient-metadata",{"searchKey":"测试"}),200) + s.chk("门诊","1.3","收费员","医生列表",G("finance","/charge-manage/register/all-doctors"),200) + s.chk("门诊","1.4","医生","医生站初始化",G("doctor1","/doctor-station/main/init"),200) + s.chk("门诊","1.5","医生","患者信息",G("doctor1","/doctor-station/main/patient-info"),200) + s.chk("门诊","1.6","医生","医嘱基础",G("doctor1","/doctor-station/advice/advice-base-info"),200) + s.chk("门诊","1.7","医生","诊断初始化",G("doctor1","/doctor-station/diagnosis/init"),200) + s.chk("门诊","1.8","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.9","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.10","医技","LIS配置",G("tech","/inspection/lisConfig/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.11","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.12","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.13","医技","影像列表",G("tech","/radiology-image/list"),200) + s.chk("门诊","1.14","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.15","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.16","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.17","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200) + s.chk("门诊","1.18","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200) + s.chk("门诊","1.19","药师","药品追溯",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.20","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.21","药师","追溯扫码",G("pharmacist","/drugtrace/scan/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.22","药师","追溯预警",G("pharmacist","/drugtrace/alert/page",{"pageNum":1,"pageSize":10}),200) + s.chk("门诊","1.23","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200) + s.chk("门诊","1.24","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200) + s.chk("门诊","1.25","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200) + s.chk("门诊","1.26","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200) + s.chk("门诊","1.27","收费员","收费患者",G("finance","/charge-manage/charge/encounter-patient-page"),200) + s.chk("门诊","1.28","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200) + s.chk("门诊","1.29","收费员","退费患者",G("finance","/charge-manage/refund/encounter-patient-page"),200) + s.chk("门诊","1.30","收费员","定价患者",G("finance","/charge-manage/pricing/patient-info"),200) + +# ============================ +# 场景2: 住院入院全流程 +# ============================ +def s2_inpatient(): + print(f"\n{'='*60}\n场景2: 住院入院全流程\n{'='*60}") + s.chk("住院","2.1","收费员","住院收费初始化",G("finance","/charge-manage/inpatient-charge/init"),200) + s.chk("住院","2.2","收费员","住院患者",G("finance","/charge-manage/inpatient-charge/encounter-patient-page"),200) + s.chk("住院","2.3","医生","患者主页",G("doctor1","/patient-home-manage/init"),200) + s.chk("住院","2.4","医生","空床查询",G("doctor1","/patient-home-manage/empty-bed"),200) + s.chk("住院","2.5","医生","科室统计",G("doctor1","/patient-home-manage/caty"),200) + s.chk("住院","2.6","护士","护理评估统计",G("nurse_nk","/nursing-assessment-enhanced/stats"),200) + r=P("nurse_nk","/nursing-assessment-enhanced/braden/assess",{"patientName":"测试患者甲","encounterId":"6006","itemScores":json.dumps({"sensation":2,"moisture":2,"activity":1,"mobility":2,"nutrition":3,"friction":2}),"detail":"压疮高危"}) + s.chk("住院","2.7","护士","Braden评估",r,200) + r=P("nurse_nk","/nursing-assessment-enhanced/morse/assess",{"patientName":"测试患者乙","encounterId":"6007","itemScores":json.dumps({"history":15,"diagnosis":0,"ambulation":15,"iv":20,"gait":0,"mental":15}),"detail":"跌倒高危"}) + s.chk("住院","2.8","护士","Morse评估",r,200) + s.chk("住院","2.9","护士","体征查询",G("nurse_nk","/vital-signs/record-search"),200) + s.chk("住院","2.10","护士","体征图表",G("nurse_nk","/vital-signs-chart/page",{"pageNum":1,"pageSize":10}),200) + s.chk("住院","2.11","护士","交接班",G("nurse_nk","/nursing-handoff/page",{"pageNum":1,"pageSize":10}),200) + s.chk("住院","2.12","药师","待发药",G("pharmacist","/pharmacy-manage/pending-medication/pending-medication-page"),200) + s.chk("住院","2.13","药师","药品详情初始化",G("pharmacist","/pharmacy-manage/medication-details/init"),200) + s.chk("住院","2.14","药师","药品汇总发药",G("pharmacist","/pharmacy-manage/summary-dispense-medicine/init"),200) + s.chk("住院","2.15","药师","住院退药",G("pharmacist","/pharmacy-manage/inHospital-return-medicine/init"),200) + +# ============================ +# 场景3: 手术全流程 +# ============================ +def s3_surgery(): + print(f"\n{'='*60}\n场景3: 手术全流程\n{'='*60}") + s.chk("手术","3.1","医生","手术列表",G("doctor1","/clinical-manage/surgery/surgery-page"),200) + s.chk("手术","3.2","医生","手术排程",G("doctor1","/clinical-manage/surgery-schedule/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.3","医生","手术统计",G("doctor1","/clinical-manage/surgery/statistics"),200) + s.chk("手术","3.4","专家","术前讨论",G("consult","/preop-discussion/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.5","手术室护士","安全核查",G("nurse_ss","/surgery-safety-check/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.6","医生","麻醉标本",G("doctor1","/anesthesia-enhanced/specimen/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.7","医生","麻醉随访",G("doctor1","/anesthesia-enhanced/followup/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.8","医生","麻醉质控",G("doctor1","/anesthesia-enhanced/qc/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.9","医生","知情同意",G("doctor1","/informed-consent/page",{"pageNum":1,"pageSize":10}),200) + s.chk("手术","3.10","医生","CA签名统计",G("doctor1","/api/v1/ca-signature/statistics"),200) + +# ============================ +# 场景4: 检验全流程 +# ============================ +def s4_inspection(): + print(f"\n{'='*60}\n场景4: 检验全流程\n{'='*60}") + s.chk("检验","4.1","医生","检查申请",G("doctor1","/exam/apply/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.2","护士","标本采集",G("nurse_nk","/inspection/collection/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.3","医技","检验结果",G("tech","/inspection/laboratory/init-page"),200) + s.chk("检验","4.4","医技","检验观察",G("tech","/inspection/observation/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.5","医技","标本定义",G("tech","/inspection/specimen/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.6","医技","仪器管理",G("tech","/inspection/instrument/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.7","医技","参考范围",G("tech","/lab-ref-range/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.8","医技","影像列表",G("tech","/radiology-image/list"),200) + s.chk("检验","4.9","医技","影像报告",G("tech","/radiology-image/report/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.10","医技","3D任务",G("tech","/reconstruction/task/page",{"pageNum":1,"pageSize":10}),200) + s.chk("检验","4.11","医技","3D统计",G("tech","/reconstruction/stats"),200) + s.chk("检验","4.12","医技","标本条码",G("tech","/specimen-barcode/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 场景5: 会诊全流程 +# ============================ +def s5_consultation(): + print(f"\n{'='*60}\n场景5: 会诊全流程\n{'='*60}") + s.chk("会诊","5.1","医生","会诊记录",G("doctor1","/consultation/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.2","专家","会诊反馈",G("consult","/cross-module/consult-feedback/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.3","医生","会诊超时",G("doctor1","/cross-module/consulttimeout/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.4","医生","临床路径",G("doctor1","/clinical-pathway/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.5","医生","危急值",G("doctor1","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.6","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200) + s.chk("会诊","5.7","医生","电子病历",G("doctor1","/api/v1/emr/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 场景6: 急诊全流程 +# ============================ +def s6_emergency(): + print(f"\n{'='*60}\n场景6: 急诊全流程\n{'='*60}") + s.chk("急诊","6.1","急诊医生","急诊记录",G("doctor_jz","/emergency/page",{"pageNum":1,"pageSize":10}),200) + s.chk("急诊","6.2","急诊护士","分诊排队",G("nurse_jz","/triage/queue/page",{"pageNum":1,"pageSize":10}),200) + s.chk("急诊","6.3","急诊护士","护理评估统计",G("nurse_jz","/nursing-assessment-enhanced/stats"),200) + r=P("nurse_jz","/nursing-assessment-enhanced/braden/assess",{"patientName":"急诊患者庚","encounterId":"6011","itemScores":json.dumps({"sensation":1,"moisture":1,"activity":1,"mobility":1,"nutrition":1,"friction":1}),"detail":"急诊压疮评估"}) + s.chk("急诊","6.4","急诊护士","Braden评估",r,200) + s.chk("急诊","6.5","急诊护士","体征查询",G("nurse_jz","/vital-signs/record-search"),200) + s.chk("急诊","6.6","急诊护士","危急值",G("nurse_jz","/api/v1/critical-value/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 场景7: 医保结算全流程 +# ============================ +def s7_insurance(): + print(f"\n{'='*60}\n场景7: 医保结算全流程\n{'='*60}") + s.chk("医保","7.1","收费员","收费初始化",G("finance","/charge-manage/charge/init"),200) + s.chk("医保","7.2","收费员","退费初始化",G("finance","/charge-manage/refund/init"),200) + s.chk("医保","7.3","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200) + s.chk("医保","7.4","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200) + s.chk("医保","7.5","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 场景8: 药品全流程 +# ============================ +def s8_pharmacy(): + print(f"\n{'='*60}\n场景8: 药品全流程\n{'='*60}") + s.chk("药品","8.1","药师","库存预警",G("pharmacist","/pharmacy-stock-alert/page",{"pageNum":1,"pageSize":10}),200) + s.chk("药品","8.2","药师","西药发药初始化",G("pharmacist","/pharmacy-manage/western-medicine-dispense/init"),200) + s.chk("药品","8.3","药师","退药初始化",G("pharmacist","/pharmacy-manage/return-medicine/init"),200) + s.chk("药品","8.4","药师","药品追溯码",G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}),200) + s.chk("药品","8.5","药师","追溯批次",G("pharmacist","/drugtrace/batch/page",{"pageNum":1,"pageSize":10}),200) + s.chk("药品","8.6","药师","合理用药统计",G("pharmacist","/api/v1/rational-drug/statistics"),200) + s.chk("药品","8.7","药师","相互作用规则",G("pharmacist","/api/v1/rational-drug/interaction-rules"),200) + s.chk("药品","8.8","药师","剂量规则",G("pharmacist","/api/v1/rational-drug/dosage-rules"),200) + +# ============================ +# 场景9: 院感全流程 +# ============================ +def s9_infection(): + print(f"\n{'='*60}\n场景9: 院感全流程\n{'='*60}") + s.chk("院感","9.1","护士","院感监测",G("nurse_nk","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.2","护士","院感暴发",G("nurse_nk","/infection-enhanced/outbreak/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.3","护士","手卫生",G("nurse_nk","/infection-enhanced/hand-hygiene/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.4","护士","手卫生统计",G("nurse_nk","/infection-enhanced/hand-hygiene/stats"),200) + s.chk("院感","9.5","护士","多重耐药",G("nurse_nk","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.6","护士","环境监测",G("nurse_nk","/infection-enhanced/env-monitor/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.7","护士","环境监测统计",G("nurse_nk","/infection-enhanced/env-monitor/stats"),200) + s.chk("院感","9.8","医生","院感监测",G("doctor1","/infection-enhanced/surveillance/page",{"pageNum":1,"pageSize":10}),200) + s.chk("院感","9.9","医技","多重耐药",G("tech","/infection-enhanced/mdr/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 场景10: 权限隔离测试 +# ============================ +def s10_permission(): + print(f"\n{'='*60}\n场景10: 权限隔离测试\n{'='*60}") + # 医生不能访问收费 + r=G("doctor1","/charge-manage/register/init") + if r.get("code")==200: s.fail("权限","10.1","医生","不应访问挂号",f"code=200") + else: s.ok("权限","10.1","医生","不能访问挂号",f"code={r.get('code')}") + # 护士不能访问西药发药 + r=G("nurse_nk","/pharmacy-manage/western-medicine-dispense/init") + if r.get("code")==200: s.fail("权限","10.2","护士","不应访问西药发药",f"code=200") + else: s.ok("权限","10.2","护士","不能访问西药发药",f"code={r.get('code')}") + # 药师不能访问手术 + r=G("pharmacist","/clinical-manage/surgery/surgery-page") + if r.get("code")==200: s.fail("权限","10.3","药师","不应访问手术",f"code=200") + else: s.ok("权限","10.3","药师","不能访问手术",f"code={r.get('code')}") + # 医技不能访问护理评估 + r=G("tech","/nursing-assessment-enhanced/page",{"pageNum":1,"pageSize":10}) + if r.get("code")==200: s.fail("权限","10.4","医技","不应访问护理评估",f"code=200") + else: s.ok("权限","10.4","医技","不能访问护理评估",f"code={r.get('code')}") + # 收费员不能访问医生站 + r=G("finance","/doctor-station/main/init") + if r.get("code")==200: s.fail("权限","10.5","收费员","不应访问医生站",f"code=200") + else: s.ok("权限","10.5","收费员","不能访问医生站",f"code={r.get('code')}") + # 正向验证 + r=G("doctor1","/clinical-manage/surgery/surgery-page") + if r.get("code")==200: s.ok("权限","10.6","医生","可以访问手术") + else: s.fail("权限","10.6","医生","应能访问手术",f"code={r.get('code')}") + r=G("nurse_nk","/nursing-assessment-enhanced/stats") + if r.get("code")==200: s.ok("权限","10.7","护士","可以访问护理评估") + else: s.fail("权限","10.7","护士","应能访问护理评估",f"code={r.get('code')}") + r=G("pharmacist","/drugtrace/code/page",{"pageNum":1,"pageSize":10}) + if r.get("code")==200: s.ok("权限","10.8","药师","可以访问药品追溯") + else: s.fail("权限","10.8","药师","应能访问药品追溯",f"code={r.get('code')}") + r=G("tech","/radiology-image/list") + if r.get("code")==200: s.ok("权限","10.9","医技","可以访问影像管理") + else: s.fail("权限","10.9","医技","应能访问影像管理",f"code={r.get('code')}") + r=G("finance","/charge-manage/charge/init") + if r.get("code")==200: s.ok("权限","10.10","收费员","可以访问收费管理") + else: s.fail("权限","10.10","收费员","应能访问收费管理",f"code={r.get('code')}") + +# ============================ +# 场景11: 中医+质控 +# ============================ +def s11_tcm_quality(): + print(f"\n{'='*60}\n场景11: 中医+质控\n{'='*60}") + s.chk("中医","11.1","医生","中医方剂",G("doctor1","/api/v1/tcm/prescriptions"),200) + s.chk("中医","11.2","医生","中医统计",G("doctor1","/api/v1/tcm/statistics"),200) + s.chk("中医","11.3","医生","体质辨识(患者6006)",G("doctor1","/api/v1/tcm/constitution/encounter/6006"),200) + s.chk("质控","11.4","医技","质控指标",G("tech","/quality-enhanced/indicator/page",{"pageNum":1,"pageSize":10}),200) + s.chk("质控","11.5","医技","医嘱统计",G("tech","/quality-enhanced/order-stats/page",{"pageNum":1,"pageSize":10}),200) + s.chk("质控","11.6","医技","质控指标汇总",G("tech","/quality-enhanced/indicator/summary"),200) + +# ============================ +# 场景12: 报表+经营 +# ============================ +def s12_reports(): + print(f"\n{'='*60}\n场景12: 报表+经营分析\n{'='*60}") + s.chk("报表","12.1","财务","挂号报表",G("finance","/report-manage/register/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.2","财务","收费报表",G("finance","/report-manage/charge/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.3","财务","月度结算",G("finance","/report-manage/monthly-settlement/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.4","财务","入库报表",G("finance","/report-manage/inbound/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.5","财务","出库报表",G("finance","/report-manage/outbound/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.6","财务","经营分析",G("finance","/business-analytics/page",{"pageNum":1,"pageSize":10}),200) + s.chk("报表","12.7","财务","经营汇总",G("finance","/business-analytics/summary"),200) + s.chk("报表","12.8","医生","知识库",G("doctor1","/knowledge-base/page",{"pageNum":1,"pageSize":10}),200) + +# ============================ +# 主入口 +# ============================ +if __name__=="__main__": + print(f"{'='*70}\nHealthLink-HIS 三甲医院多角色协作测试 v2\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n{'='*70}") + + print("\n>>> 登录所有角色...") + for k,a in ACCOUNTS.items(): + if login(k): print(f" ✅ {a['role']}({a['user']})") + else: print(f" ❌ {a['role']}({a['user']})") + + for fn in [s1_outpatient,s2_inpatient,s3_surgery,s4_inspection,s5_consultation,s6_emergency,s7_insurance,s8_pharmacy,s9_infection,s10_permission,s11_tcm_quality,s12_reports]: + try: fn() + except Exception as e: print(f" ❌ {fn.__name__}: {e}") + + print(f"\n{'='*70}") + print(f"汇总: 总数={s.t}, 通过={s.p}, 失败={s.f}, 通过率={s.p*100/s.t:.1f}%" if s.t else "") + print(f"{'='*70}") + + os.makedirs("MD/test/reports",exist_ok=True) + rp=f"MD/test/reports/multi_role_v2_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md" + with open(rp,"w") as f: + f.write(f"# 多角色协作测试报告 v2\n\n**时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n## 汇总\n\n- 总数: {s.t}\n- 通过: {s.p}\n- 失败: {s.f}\n- 通过率: {s.p*100/s.t:.1f}%\n\n## 详细\n\n| 场景 | 步骤 | 角色 | 测试项 | 状态 | 说明 |\n|------|------|------|--------|------|------|\n") + for r in s.r: f.write(f"| {r['sc']} | {r['step']} | {r['role']} | {r['name']} | {r['s']} | {r['d']} |\n") + print(f"\n📄 报告: {rp}") + sys.exit(0 if s.f==0 else 1) diff --git a/MD/test/reports/multi_role_v2_20260607_220914.md b/MD/test/reports/multi_role_v2_20260607_220914.md new file mode 100644 index 000000000..87a43bfe0 --- /dev/null +++ b/MD/test/reports/multi_role_v2_20260607_220914.md @@ -0,0 +1,144 @@ +# 多角色协作测试报告 v2 + +**时间**: 2026-06-07 22:09:14 + +## 汇总 + +- 总数: 126 +- 通过: 88 +- 失败: 38 +- 通过率: 69.8% + +## 详细 + +| 场景 | 步骤 | 角色 | 测试项 | 状态 | 说明 | +|------|------|------|--------|------|------| +| 门诊 | 1.1 | 收费员 | 挂号初始化 | ✅ | | +| 门诊 | 1.2 | 收费员 | 查询患者 | ✅ | | +| 门诊 | 1.3 | 收费员 | 医生列表 | ✅ | | +| 门诊 | 1.4 | 医生 | 医生站初始化 | ✅ | | +| 门诊 | 1.5 | 医生 | 患者信息 | ✅ | | +| 门诊 | 1.6 | 医生 | 医嘱基础 | ✅ | | +| 门诊 | 1.7 | 医生 | 诊断初始化 | ✅ | | +| 门诊 | 1.8 | 医技 | 检验观察 | ❌ | code=500, msg=No static resource inspection/observation/page for request '/healthlink-his/inspection/observation/p | +| 门诊 | 1.9 | 医技 | 标本定义 | ❌ | code=500, msg=No static resource inspection/specimen/page for request '/healthlink-his/inspection/specimen/page'. | +| 门诊 | 1.10 | 医技 | LIS配置 | ❌ | code=500, msg=No static resource inspection/lisConfig/page for request '/healthlink-his/inspection/lisConfig/page' | +| 门诊 | 1.11 | 医技 | 仪器管理 | ❌ | code=500, msg=No static resource inspection/instrument/page for request '/healthlink-his/inspection/instrument/pag | +| 门诊 | 1.12 | 医技 | 参考范围 | ✅ | | +| 门诊 | 1.13 | 医技 | 影像列表 | ❌ | code=500, msg=Required request parameter 'applyId' for method parameter type Long is not present | +| 门诊 | 1.14 | 医技 | 影像报告 | ✅ | | +| 门诊 | 1.15 | 医技 | 3D任务 | ✅ | | +| 门诊 | 1.16 | 药师 | 库存预警 | ✅ | | +| 门诊 | 1.17 | 药师 | 西药发药初始化 | ✅ | | +| 门诊 | 1.18 | 药师 | 退药初始化 | ✅ | | +| 门诊 | 1.19 | 药师 | 药品追溯 | ✅ | | +| 门诊 | 1.20 | 药师 | 追溯批次 | ✅ | | +| 门诊 | 1.21 | 药师 | 追溯扫码 | ✅ | | +| 门诊 | 1.22 | 药师 | 追溯预警 | ✅ | | +| 门诊 | 1.23 | 药师 | 合理用药统计 | ✅ | | +| 门诊 | 1.24 | 药师 | 相互作用规则 | ✅ | | +| 门诊 | 1.25 | 药师 | 剂量规则 | ❌ | code=500, msg= +### Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: column "create_by" d | +| 门诊 | 1.26 | 收费员 | 收费初始化 | ✅ | | +| 门诊 | 1.27 | 收费员 | 收费患者 | ✅ | | +| 门诊 | 1.28 | 收费员 | 退费初始化 | ✅ | | +| 门诊 | 1.29 | 收费员 | 退费患者 | ✅ | | +| 门诊 | 1.30 | 收费员 | 定价患者 | ✅ | | +| 住院 | 2.1 | 收费员 | 住院收费初始化 | ✅ | | +| 住院 | 2.2 | 收费员 | 住院患者 | ✅ | | +| 住院 | 2.3 | 医生 | 患者主页 | ✅ | | +| 住院 | 2.4 | 医生 | 空床查询 | ✅ | | +| 住院 | 2.5 | 医生 | 科室统计 | ✅ | | +| 住院 | 2.6 | 护士 | 护理评估统计 | ✅ | | +| 住院 | 2.7 | 护士 | Braden评估 | ✅ | | +| 住院 | 2.8 | 护士 | Morse评估 | ✅ | | +| 住院 | 2.9 | 护士 | 体征查询 | ✅ | | +| 住院 | 2.10 | 护士 | 体征图表 | ✅ | | +| 住院 | 2.11 | 护士 | 交接班 | ❌ | code=500, msg=No static resource nursing-handoff/page for request '/healthlink-his/nursing-handoff/page'. | +| 住院 | 2.12 | 药师 | 待发药 | ✅ | | +| 住院 | 2.13 | 药师 | 药品详情初始化 | ✅ | | +| 住院 | 2.14 | 药师 | 药品汇总发药 | ❌ | code=500, msg=No static resource pharmacy-manage/summary-dispense-medicine/init for request '/healthlink-his/pharm | +| 住院 | 2.15 | 药师 | 住院退药 | ✅ | | +| 手术 | 3.1 | 医生 | 手术列表 | ✅ | | +| 手术 | 3.2 | 医生 | 手术排程 | ✅ | | +| 手术 | 3.3 | 医生 | 手术统计 | ✅ | | +| 手术 | 3.4 | 专家 | 术前讨论 | ❌ | code=500, msg= +### Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: column "delete_flag" | +| 手术 | 3.5 | 手术室护士 | 安全核查 | ✅ | | +| 手术 | 3.6 | 医生 | 麻醉标本 | ✅ | | +| 手术 | 3.7 | 医生 | 麻醉随访 | ✅ | | +| 手术 | 3.8 | 医生 | 麻醉质控 | ✅ | | +| 手术 | 3.9 | 医生 | 知情同意 | ✅ | | +| 手术 | 3.10 | 医生 | CA签名统计 | ✅ | | +| 检验 | 4.1 | 医生 | 检查申请 | ❌ | code=500, msg=未找到申请单信息 | +| 检验 | 4.2 | 护士 | 标本采集 | ❌ | code=500, msg=No static resource inspection/collection/page for request '/healthlink-his/inspection/collection/pag | +| 检验 | 4.3 | 医技 | 检验结果 | ❌ | code=500, msg=请求参数类型不匹配,参数[id]要求类型为:'java.lang.Long',但输入值为:'init-page' | +| 检验 | 4.4 | 医技 | 检验观察 | ❌ | code=500, msg=No static resource inspection/observation/page for request '/healthlink-his/inspection/observation/p | +| 检验 | 4.5 | 医技 | 标本定义 | ❌ | code=500, msg=No static resource inspection/specimen/page for request '/healthlink-his/inspection/specimen/page'. | +| 检验 | 4.6 | 医技 | 仪器管理 | ❌ | code=500, msg=No static resource inspection/instrument/page for request '/healthlink-his/inspection/instrument/pag | +| 检验 | 4.7 | 医技 | 参考范围 | ✅ | | +| 检验 | 4.8 | 医技 | 影像列表 | ❌ | code=500, msg=Required request parameter 'applyId' for method parameter type Long is not present | +| 检验 | 4.9 | 医技 | 影像报告 | ✅ | | +| 检验 | 4.10 | 医技 | 3D任务 | ✅ | | +| 检验 | 4.11 | 医技 | 3D统计 | ✅ | | +| 检验 | 4.12 | 医技 | 标本条码 | ✅ | | +| 会诊 | 5.1 | 医生 | 会诊记录 | ❌ | code=500, msg=No static resource consultation/page for request '/healthlink-his/consultation/page'. | +| 会诊 | 5.2 | 专家 | 会诊反馈 | ❌ | code=500, msg=No static resource cross-module/consult-feedback/page for request '/healthlink-his/cross-module/cons | +| 会诊 | 5.3 | 医生 | 会诊超时 | ❌ | code=500, msg=No static resource cross-module/consulttimeout/page for request '/healthlink-his/cross-module/consul | +| 会诊 | 5.4 | 医生 | 临床路径 | ✅ | | +| 会诊 | 5.5 | 医生 | 危急值 | ❌ | code=500, msg=No static resource api/v1/critical-value/page for request '/healthlink-his/api/v1/critical-value/pag | +| 会诊 | 5.6 | 医生 | 知识库 | ✅ | | +| 会诊 | 5.7 | 医生 | 电子病历 | ❌ | code=500, msg=No static resource api/v1/emr/page for request '/healthlink-his/api/v1/emr/page'. | +| 急诊 | 6.1 | 急诊医生 | 急诊记录 | ❌ | code=500, msg=No static resource emergency/page for request '/healthlink-his/emergency/page'. | +| 急诊 | 6.2 | 急诊护士 | 分诊排队 | ❌ | code=500, msg=No static resource index.html for request '/healthlink-his/index.html'. | +| 急诊 | 6.3 | 急诊护士 | 护理评估统计 | ✅ | | +| 急诊 | 6.4 | 急诊护士 | Braden评估 | ✅ | | +| 急诊 | 6.5 | 急诊护士 | 体征查询 | ✅ | | +| 急诊 | 6.6 | 急诊护士 | 危急值 | ❌ | code=500, msg=No static resource api/v1/critical-value/page for request '/healthlink-his/api/v1/critical-value/pag | +| 医保 | 7.1 | 收费员 | 收费初始化 | ✅ | | +| 医保 | 7.2 | 收费员 | 退费初始化 | ✅ | | +| 医保 | 7.3 | 财务 | 收费报表 | ❌ | code=500, msg=No static resource report-manage/charge/page for request '/healthlink-his/report-manage/charge/page' | +| 医保 | 7.4 | 财务 | 经营分析 | ✅ | | +| 医保 | 7.5 | 财务 | 月度结算 | ❌ | code=500, msg=No static resource report-manage/monthly-settlement/page for request '/healthlink-his/report-manage/ | +| 药品 | 8.1 | 药师 | 库存预警 | ✅ | | +| 药品 | 8.2 | 药师 | 西药发药初始化 | ✅ | | +| 药品 | 8.3 | 药师 | 退药初始化 | ✅ | | +| 药品 | 8.4 | 药师 | 药品追溯码 | ✅ | | +| 药品 | 8.5 | 药师 | 追溯批次 | ✅ | | +| 药品 | 8.6 | 药师 | 合理用药统计 | ✅ | | +| 药品 | 8.7 | 药师 | 相互作用规则 | ✅ | | +| 药品 | 8.8 | 药师 | 剂量规则 | ❌ | code=500, msg= +### Error querying database. Cause: org.postgresql.util.PSQLException: ERROR: column "create_by" d | +| 院感 | 9.1 | 护士 | 院感监测 | ✅ | | +| 院感 | 9.2 | 护士 | 院感暴发 | ✅ | | +| 院感 | 9.3 | 护士 | 手卫生 | ✅ | | +| 院感 | 9.4 | 护士 | 手卫生统计 | ✅ | | +| 院感 | 9.5 | 护士 | 多重耐药 | ✅ | | +| 院感 | 9.6 | 护士 | 环境监测 | ✅ | | +| 院感 | 9.7 | 护士 | 环境监测统计 | ✅ | | +| 院感 | 9.8 | 医生 | 院感监测 | ✅ | | +| 院感 | 9.9 | 医技 | 多重耐药 | ✅ | | +| 权限 | 10.1 | 医生 | 不应访问挂号 | ❌ | code=200 | +| 权限 | 10.2 | 护士 | 不应访问西药发药 | ❌ | code=200 | +| 权限 | 10.3 | 药师 | 不应访问手术 | ❌ | code=200 | +| 权限 | 10.4 | 医技 | 不应访问护理评估 | ❌ | code=200 | +| 权限 | 10.5 | 收费员 | 不应访问医生站 | ❌ | code=200 | +| 权限 | 10.6 | 医生 | 可以访问手术 | ✅ | | +| 权限 | 10.7 | 护士 | 可以访问护理评估 | ✅ | | +| 权限 | 10.8 | 药师 | 可以访问药品追溯 | ✅ | | +| 权限 | 10.9 | 医技 | 应能访问影像管理 | ❌ | code=500 | +| 权限 | 10.10 | 收费员 | 可以访问收费管理 | ✅ | | +| 中医 | 11.1 | 医生 | 中医方剂 | ✅ | | +| 中医 | 11.2 | 医生 | 中医统计 | ✅ | | +| 中医 | 11.3 | 医生 | 体质辨识(患者6006) | ✅ | | +| 质控 | 11.4 | 医技 | 质控指标 | ✅ | | +| 质控 | 11.5 | 医技 | 医嘱统计 | ✅ | | +| 质控 | 11.6 | 医技 | 质控指标汇总 | ✅ | | +| 报表 | 12.1 | 财务 | 挂号报表 | ❌ | code=500, msg=No static resource report-manage/register/page for request '/healthlink-his/report-manage/register/p | +| 报表 | 12.2 | 财务 | 收费报表 | ❌ | code=500, msg=No static resource report-manage/charge/page for request '/healthlink-his/report-manage/charge/page' | +| 报表 | 12.3 | 财务 | 月度结算 | ❌ | code=500, msg=No static resource report-manage/monthly-settlement/page for request '/healthlink-his/report-manage/ | +| 报表 | 12.4 | 财务 | 入库报表 | ❌ | code=500, msg=No static resource report-manage/inbound/page for request '/healthlink-his/report-manage/inbound/pag | +| 报表 | 12.5 | 财务 | 出库报表 | ❌ | code=500, msg=No static resource report-manage/outbound/page for request '/healthlink-his/report-manage/outbound/p | +| 报表 | 12.6 | 财务 | 经营分析 | ✅ | | +| 报表 | 12.7 | 财务 | 经营汇总 | ✅ | | +| 报表 | 12.8 | 医生 | 知识库 | ✅ | |