Compare commits

...

63 Commits

Author SHA1 Message Date
关羽
6a0e596f84 Merge remote-tracking branch 'origin/关羽' into 关羽 2026-05-13 15:11:55 +08:00
关羽
1f679d68df Fix Bug #435: 门诊手术安排:编辑弹窗中"费用类别"字段数据未回显
根因:OpSchedule 实体缺少 feeType 字段,创建手术安排时费用类别未被持久化到数据库。编辑时详情查询通过复杂 JOIN 链(adm_encounter → adm_account → fin_contract)计算 feeType,链断裂时返回 null。

修复方案:
1. OpSchedule.java 新增 feeType 字段,使创建时费用类别持久化到 op_schedule.fee_type
2. SurgicalScheduleAppMapper.xml 详情查询改用 os.fee_type,移除不可靠的 JOIN 链
3. OpScheduleDto.java 移除重复的 feeType 声明(现从父类继承)
4. 新增 DDL 迁移脚本添加 fee_type 列

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 15:11:17 +08:00
关羽
c012974fd0 Fix Bug #405: 住院医生工作站:临床医嘱保存成功后,医嘱条目仍处于可编辑状态(未锁定展示)
- getListInfo: 增加 getPrescriptionList 的 .catch() 处理,API失败时锁定所有行(isEdit=false)
  防止列表加载静默失败导致医嘱永久处于可编辑状态
- handleSaveBatch: 在 .catch() 中增加 isEdit=false 重置,保存失败时也锁定行
- 与之前的修复配合:保存成功时先设置 isEdit=false 再调用 getListInfo 刷新数据

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 14:27:31 +08:00
赵云
5a83ff2aca Fix Bug #403: 住院医生工作站:应用医嘱组套后,药品明细字段内容丢失未正确引入表格
修复 handleSaveGroup 中 mergedDetail 和 newRow 构建时的空值处理问题:
1. mergedDetail 中 dose/doseQuantity/dispensePerDuration 使用严格 null 检查,
   避免组套中值为 null 时错误回退到 orderDetailInfos
2. newRow 中关键字段增加 mergedDetail 回退(?? 操作符),
   确保当 item 中字段为 null/undefined 时能从 setValue 填充的完整数据中获取

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 14:27:31 +08:00
关羽
92ce2d10a1 Fix Bug #401: 门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
根因:DoctorStationMainAppServiceImpl.completeEncounter() 中获取 pool_id/slot_id 的逻辑有两处缺陷:
1. 当 queueItem 存在但 poolId 或 slotId 为 null 时,else if 分支因 queueItem != null 而跳过,
   导致未进入回退链路,最终 divPoolId 和 divSlotId 均为 null。
   修复:将 else if 拆为独立的 if 判断,确保 queueItem 值缺失时能正确回退。
2. 回退路径中先将 order.getSlotId() 赋给 divSlotId,再查询 ScheduleSlot 获取 poolId。
   若 Slot 查询失败(返回 null),则 divSlotId 有值但 divPoolId 为 null,产生不完整审计记录。
   修复:先查 Slot,成功后同时用 slot.getId() 和 slot.getPoolId() 赋值,保证数据一致性。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 14:06:34 +08:00
赵云
420a5a977a Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
根因分析:
triage_queue_item.status 字段在 DDL 中定义为 VARCHAR(50),但 Java 实体
TriageQueueItem.status 为 Integer 类型,应用层使用 TriageQueueStatus 枚举值
(0/10/20/30/40) 进行读写。数据类型不匹配导致 MyBatis-Plus 无法正确映射 status 字段,
完诊时使用 LambdaUpdateWrapper 更新 status=30 操作失败。

修复方案:
1. 修正 DDL:将 status 字段类型从 VARCHAR(50) 改为 INTEGER
2. 新增迁移 SQL:修改已有数据库表的 status 字段类型为 INTEGER

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 14:06:34 +08:00
关羽
6e0e78cd8d Fix Bug #405: 住院医生工作站:临床医嘱保存成功后,医嘱条目仍处于可编辑状态(未锁定展示)
- handleSaveSign: 新增行点击确定后调用savePrescription持久化到后端,而非仅设置isEdit=false后调用handleAddPrescription
- handleSaveBatch: 修复nextId.value == 1 为赋值操作(=)
- handleSaveBatch: catch块增加错误提示,保存失败时用户可感知

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 13:35:47 +08:00
赵云
763c822574 Fix Bug #408: 门诊医生站检查明细标签页显示暂无数据 - 修复handleRowClick中resp.items被d.data覆盖后丢失的问题
根因:后端 getInfo 返回 { data: ExamApply, items: ExamApplyItem[] },
前端先将 resp 赋值给 d,随后又执行 d = resp.data,导致 d 变成 ExamApply 对象,
后续 d.items 永远为 undefined,明细列表无法加载。
修复:提前保存 resp.items 到 rawItems 变量,后续使用 rawItems 而非 d.items。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 13:35:47 +08:00
Ranyunqiao
9bec956b4f bug 362 428 436 2026-05-13 13:35:47 +08:00
陈琳
65a772c7b2 Fix Bug #496: 【住院医生工作站-检查申请】检查申请列表字段命名不规范及单号生成规则不符合医疗行业标准
1. 前端列标题:处方号 → 申请单号(表格列 + 详情弹窗)
2. 后端单号生成:原用 PAR 处方号前缀 → 改为 JCZ + yyMMdd + 5位顺序号
3. 新增 AssignSeqEnum.CHECK_APPLY_NO 枚举项(JCZ 前缀)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 13:35:47 +08:00
994fd9bacc bug471 手术管理-门诊手术安排:手术申请查询结果中混入住院检验申请单数据(脏数据) 2026-05-13 13:35:47 +08:00
关羽
cec8a44bd9 Fix Bug #496: 【住院医生工作站-检查申请】检查申请列表字段命名不规范及单号生成规则不符合医疗行业标准
1. 前端 examineApplication.vue:列表表头和详情弹窗中"处方号"改为"申请单号"
2. 后端 RequestFormManageAppServiceImpl:检查申请单单号生成规则由 PAR+流水号 改为 JCZ+yyMMdd+5位顺序号(如:JCZ26051300001),其他类型申请单保持原有PAR规则不变

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:42:33 +08:00
关羽
8157063749 Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
根因:完诊时使用 triageQueueItemService.updateById(queueItem) 更新队列状态,
依赖 MyBatis Plus 的实体级更新策略,可能因字段级更新策略导致 status 字段未实际写入数据库。

修复策略:改用 LambdaUpdateWrapper 直接生成 UPDATE SQL,明确指定 SET status=30,
绕过实体级更新策略,确保 status 字段必定写入数据库。同时增加更新失败日志。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:42:33 +08:00
关羽
41562c5bbd Fix Bug #402: 住院医生站诊断录入:点击保存诊断后,列表出现重复记录且部分条目元数据缺失
根因分析:
1. 后端 deleteTcmByEncounterId SQL 过滤条件错误(tcm_flag=0 应为 tcm_flag=1),导致中医诊断记录无法被正确清理
2. 前端 getList() 从服务器加载数据后,未补充缺失的 diagnosisDoctor 和 diagnosisTime 字段
3. 前端 handleSaveDiagnosis() 保存成功后直接更新本地数据而非从服务器刷新,导致数据不一致和重复记录
4. 前端新增诊断时缺少 classification、onsetDate、longTermFlag 等必要字段

修复内容:
- 后端:修复 EncounterDiagnosisMapper.xml 中 deleteTcmByEncounterId 的 tcm_flag 过滤条件
- 前端:getList() 增加缺失字段默认值填充逻辑
- 前端:handleSaveDiagnosis() 改为 async/await,保存成功后调用 getList() 从服务器刷新
- 前端:addDiagnosisItem()/handleImport()/handleNodeClick() 补充完整字段

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:36:05 +08:00
荀彧
51fba4488e Fix Bug #401: 门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
调整完诊时 div_log 的 pool_id/slot_id 获取优先级:优先使用 triage_queue_item
(挂号时录入的号源信息,为权威来源),队列项不存在或值缺失时回退使用
encounter → order → slot → pool 链路

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:35:55 +08:00
关羽
00ff4158a1 Fix Bug #402: 住院医生站诊断录入:点击保存诊断后,列表出现重复记录且部分条目元数据缺失
根因分析:
1. 前端"保存诊断"按钮无防重复点击保护,快速点击两次导致并发请求产生重复记录
2. 前端日期使用 toLocaleString('zh-CN') 格式与后端 @JsonFormat(pattern="yyyy/M/d HH:mm:ss") 不匹配,导致诊断时间解析失败为null
3. EncounterDiagnosisMapper.xml 中 deleteTcmByEncounterId 使用 tcm_flag=0(应为1),导致中医诊断删除逻辑错误

修复内容:
- 前端:添加 saveLoading 状态防止重复点击
- 前端:统一使用 formatDateStr(new Date(), 'YYYY/M/D HH:mm:ss') 确保与后端日期格式一致
- 后端:修正 deleteTcmByEncounterId 的 tcm_flag 条件为 1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:26:10 +08:00
荀彧
a09020a4fd Fix Bug #401: 门诊完诊审计日志错误:div_log 表中 pool_id 与 slot_id 存值与设计规范不符
调整完诊时 div_log 的 pool_id/slot_id 获取优先级:优先使用 triage_queue_item
(挂号时录入的号源信息,为权威来源),队列项不存在或值缺失时回退使用
encounter → order → slot → pool 链路

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:25:48 +08:00
关羽
f7a3f658fb Merge remote-tracking branch 'origin/关羽' - resolve conflicts 2026-05-13 09:39:03 +08:00
关羽
dd36dc49ea Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
根因:完诊时使用 triageQueueItemService.updateById(entity) 更新队列状态,
实体序列化过程中 status 字段为 Integer 类型但数据库列为 VARCHAR(50),
可能导致 MyBatis-Plus 的 UPDATE SQL 未正确包含 status 字段。

修复方案:改用 LambdaUpdateWrapper 直接构造 UPDATE SQL,确保 status 字段被正确更新为 30。
同时增加成功/失败日志,便于后续排查。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 09:38:13 +08:00
关羽
1c9bb92663 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
根因:el-popover 使用 :visible 受控模式,closeAllPopovers() 将 showPopover 设为 false
后,Vue 尚未完成 DOM 更新时 showChargeDialog 已被设为 false,导致弹窗组件被销毁
而 popover 的 visible 状态变更未传播到 DOM,弹窗消失但 popover 残留。

修复:在 closeChargeDialog 中使用 nextTick 等待 Vue 完成 popover 关闭的 DOM 更新后,
再设置 showChargeDialog = false 关闭主弹窗。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:16:01 +08:00
刘备
6ddd9bb2dc Fix Bug #489: 【医嘱闭环】医生站签发单条长期药品医嘱,护士校对界面生成重复(两条)待校对记录
在住院医生站签发流程的 handMedication() 方法中增加去重逻辑,
与门诊医生站保持一致,使用 patientId+encounterId+adviceDefinitionId+dose+methodCode+rateCode
作为唯一键,防止前端重复提交导致数据库产生重复医嘱记录。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:16:01 +08:00
刘备
c8ed4e5856 Fix Bug #491: 【执行科室配置】保存配置时系统报错 - 修复Organization.getName()空指针异常
根因分析:
1. organizationLocationInit() 中 Organization.getName() 未做空值过滤,若数据库存在name为null的科室记录会NPE
2. addOrEditOrgLoc() 中 activityDefinitionMapper.selectById().getName() 未对selectById返回null做防护
3. addOrEditOrgLoc() 缺少organizationId前置校验,空值传入可能导致后续流程异常

修复内容:
- 第74行:stream中增加 .filter(organization -> organization != null && organization.getName() != null)
- 第136-138行:增加organizationId为null时的校验,返回友好错误提示
- 第145-147行:将activityDefinitionMapper.selectById结果先赋值再判空,避免NPE

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:16:01 +08:00
赵云
253ceaffc6 Fix Bug #488: 【临床医嘱】双击编辑待签发医嘱,医嘱类型回显为数字且点击确认报接口错误
修复 clickRowDb 函数中编辑条件过于严格的问题:原条件 `row.statusEnum == 1 && !row.requestId`
只允许"待保存"(无requestId)的医嘱进入编辑,导致"待签发"(有requestId)的医嘱无法编辑。
改为 `row.statusEnum == 1`,允许所有待签发状态的医嘱编辑。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:16:01 +08:00
张飞
ed7c388d11 Fix Bug #477: 住院医生工作站-住院检查申请详情弹窗中"发往科室"字段显示为短横线(-),未正常获取数据
根因分析:
1. 前端组件使用了错误的API获取科室列表:表单使用getDepartmentList(/app-common/department-list)
   保存的targetDepartment ID,但详情弹窗使用getOrgList(/base-data-manage/organization/organization)
   查询,两个接口返回的数据结构和ID不同,导致recursionFun无法匹配到科室名称
2. recursionFun中obj.children可能为null/undefined,直接遍历会抛TypeError
3. getLocationInfo是异步调用,handleViewDetail可能在科室数据加载完成前被调用

修复:
- 统一使用getDepartmentList(@/api/public.js)获取科室数据,与表单组件保持一致
- recursionFun增加children空值保护
- handleViewDetail改为async,打开详情前确保科室数据已加载
2026-05-13 00:16:01 +08:00
刘备
f581f72693 Fix Bug #468: [住院医生工作站-检验申请] 列表页缺失【单据状态】列,无法闭环管理检验医嘱执行进度
前后端完整链路修复:
- 后端 Mapper: LEFT JOIN wor_service_request 表,通过 CASE MIN(status_enum) 映射单据状态
- 后端 Mapper: 新增状态筛选和关键字搜索(申请单号/检验项目模糊匹配)
- 后端 Service/Controller: 新增 status 和 keyword 参数传递
- 前端 Vue: 列表页添加【单据状态】列,绑定 status 字段
- 前端 Vue: 移除中间状态选项(已采集/已收样),与后端 CASE 映射保持一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:16:01 +08:00
荀彧
55b517c8c6 Fix Bug #464: [目录管理-诊疗目录] 新增项目时"零售价"未与"诊疗子项"合计总价自动同步
根本原因: calculateTotalPrice() 中同步零售价的条件只检查了第一个子项 (treatmentItems.value[0]),当第一个子项被清空但其他子项有效时,零售价不会同步。submitForm() 中存在相同问题。

修复内容:
1. calculateTotalPrice(): 使用 Array.some() 检查是否有任何有效子项,而非只检查第一个
2. 当无有效子项时,将 retailPrice 重置为 undefined 避免残留旧值
3. submitForm(): childrenJson 序列化和零售价同步同样改用 some() 检查
4. addItem(): 补充缺失的 name 字段,与初始值结构保持一致

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
荀彧
0c36230158 Fix Bug #457: 门诊收费:已签发的手术类医嘱在门诊收费列表中不显示项目名称
根因分析:门诊医生站处方列表查询(DoctorStationAdviceAppMapper.xml)中,
手术类医嘱(category_enum=4)的 advice_table_name 固定返回 'wor_activity_definition',
而非 'cli_surgery'。当医生通过"签发"按钮处理手术医嘱时,handService() 据此创建
ChargeItem,导致 product_table = 'wor_activity_definition',但 product_id 实际指向
cli_surgery 表中的手术记录。

门诊收费SQL查询的CASE语句仅匹配 product_table = 'cli_surgery' 的手术项,
因此这些手术医嘱无法匹配,item_name 返回 NULL。

修复方案:在 selectEncounterPatientPrescription 和 selectEncounterPatientPrescriptionWithPrice
的 item_name CASE 表达式中新增兜底分支:
  WHEN context_enum = #{activity} AND service_table = 'wor_service_request'
  THEN COALESCE(T9.surgery_name, wsr.content_json->>'surgeryName',
                wsr.content_json->>'adviceName', T2."name")

按优先级回退获取手术名称:cli_surgery表 → content_json手术名称 → content_json医嘱名称 → 诊疗定义名称

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
刘备
351a2fba2e Fix Bug #458: 门诊医生站:诊疗类医嘱保存成功后,列表"医嘱类型"列显示为空值
根因:mapAdviceTypeLabel 函数依赖 drord_doctor_type 字典数据进行类型映射,
当字典中缺少 value=3(诊疗)的条目时,find() 返回 undefined,函数返回空字符串,
导致保存后刷新列表时"医嘱类型"列显示为空白。

修复:在 mapAdviceTypeLabel 中为诊疗/手术类医嘱(wor_activity_definition 表)
添加兜底映射逻辑:type 3→诊疗, 6→手术, 4→手术, 1→检验, 2→检查, 5→其他,
确保即使字典缺失对应条目也能正确显示类型标签。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
赵云
09e5825dab Fix Bug #448: 门诊划价模块-项目分类过滤失效,选择"耗材"类型时仍能检索出药品
根因: adviceBaseList.vue 中 adviceQueryParams 的 watch 在 popoverVisible=false 时
直接 return,未将参数同步到 queryParams。当 handleFocus 同时修改 adviceQueryParams
和 showPopover 时,Vue 的 watch 触发顺序不确定:
- 若 adviceQueryParams watch 先触发(popoverVisible 仍为 false),则 queryParams 保持旧值
- 随后 popoverVisible watch 触发时虽然会同步参数,但存在时序竞态导致查询参数不正确

修复: 将参数同步逻辑移至 early return 之前,确保 queryParams 始终与 adviceQueryParams
保持一致,API 请求仍在 popoverVisible=true 时才触发。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
华佗
1f94efa1d2 Fix Bug #442: 手术计费:点击"删除"待签发耗材时异常报错,导致操作失败
根因:DoctorStationAdviceAppMapper.xml 中 getRequestBaseInfo SQL 的第二个 UNION 查询(手术计费耗材从 adm_charge_item 关联 wor_device_request)中,biz_request_flag 和 requester_id 使用了 CI.enterer_id(计费录入人),而非 DR.requester_id(设备申请创建人)。当录入人与当前操作人不一致时,biz_request_flag 为 '0',导致删除操作被后端拒绝。

修复:将 CI.enterer_id 改为 COALESCE(DR.requester_id, CI.enterer_id),优先使用 DeviceRequest 的 requester_id,确保 biz_request_flag 基于正确的创建人计算。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
陈琳
d4d9ede5d7 Fix Bug #428: 门诊医生站-检查申请:未实现分类联动检查方法及套餐明细展示与勾选逻辑
根因分析:
1. handleCategoryExpand 加载了 cat.methods 但模板从未渲染,用户展开分类后看不到检查方法
2. 缺少 isMethodSelected/handleMethodSelect 函数,无法通过勾选检查方法来联动添加到已选择列表
3. 套餐明细展示缺少 CSS 样式(package-details-list/detail-row/detail-name/detail-info)

修复内容:
- 模板: 在分类折叠区域添加 cat.methods 的渲染(检查方法列表 + 勾选框 + 价格)
- 逻辑: 新增 isMethodSelected 和 handleMethodSelect 函数,支持直接勾选检查方法添加到已选择列表
- 样式: 添加套餐明细列表样式 + 检查方法区域样式

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
刘备
876e46bfea Fix Bug #413: 医生个人报卡编辑/查看界面字段映射与后端DTO不一致导致数据不显示
后端 InfectiousCardDto 字段名与前端 showReport 映射不匹配:
- caseClass 应从 diseaseType 映射 (后端 diseaseType=病例分类)
- diseaseType 应从 diseaseSubtype 映射 (后端 diseaseSubtype=疾病分型)
- correctName 应从 revisedDiseaseName 映射
- withdrawReason 应从 returnReason 映射

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
赵云
7388e01b5d Fix Bug #509: [门诊医生站-手术申请] 提交申请后列表未实时刷新展示数据,且提示语需优化
1. 优化提示语:将"新增成功"/"修改成功"改为"手术申请提交成功!"/"手术申请修改成功!"
2. 优化执行顺序:先emit('saved')通知父组件刷新医嘱列表,再调用getList()刷新手术申请列表,确保数据刷新时序正确

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
赵云
4af2fc5f54 Fix Bug #470: 住院医生工作站-手术申请单加载手术项目耗时过长,影响医生开单效率
策略A-前端优化:为手术项目穿梭框添加 v-loading 加载状态指示器,
解决API查询期间用户看到空白/卡住界面的问题。
同时暴露 getList 方法供父组件调用(之前未暴露但已被父组件调用)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
赵云
c04432e39a Fix Bug #455: 门诊医生站-医嘱:开立诊疗医嘱时执行科室默认获取逻辑有误且显示为原始ID
根因修复:
1. 默认科室逻辑错误:row.orgId来自wor_activity_definition.org_id(项目所属科室),不是执行科室。
   改为优先使用positionId(adm_organization_location配置的执行科室),回退到患者就诊科室。
2. 显示为原始ID:getOrgList()为异步调用但未await,导致findOrgNameById执行时organization树未加载。
   将setValue改为async函数并await getOrgList(),确保科室名称解析时数据已就绪。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
华佗
21e68da15e Fix Bug #469: [住院医生工作站-检验申请] 完善【操作】列临床业务逻辑:支持按状态动态切换修改、删除、撤回等功能
根据单据状态动态显示操作按钮:
- 待签发(状态0):显示修改、删除、详情
- 已签发(状态1):显示撤回、详情
- 其他状态:仅显示详情

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:52 +08:00
Ranyunqiao
6fdf96edab 413 460 513 514 2026-05-13 00:15:52 +08:00
赵云
2ef1ce9f35 Fix Bug #470: 住院医生工作站-手术申请单加载手术项目耗时过长,影响医生开单效率
根因:getAdviceBaseInfo 后端接口在查询手术项目时,仍会执行与手术无关的库存查询
(getAdviceInventory)、全表扫描待发放记录(getAdviceDraftInventory)以及药房科室
配置查询(getMedLocationConfig),其中 getAdviceDraftInventory 对
med_medication_dispense 和 wor_device_dispense 做全表扫描,无任何过滤条件,
导致手术/诊疗场景下的额外数据库开销。

修复:在 DoctorStationAdviceAppServiceImpl.getAdviceBaseInfo() 中增加类型判断,
当 adviceTypes 不包含药品(1)或耗材(2)时跳过所有库存相关查询,因为这些查询对手术/
诊疗(3,6)项目无意义,且下游代码仅在药品/耗材处理分支中使用这些变量。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:51 +08:00
赵云
11fd9b839c Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
完诊API后端要求同时传递 encounterId 和 firstEnum 两个参数:
1. DoctorCallDialog.vue:已有修复只传了 encounterId,缺少 firstEnum,导致后端校验失败
2. patientList.vue:仍传递原始值而非对象,且同样缺少 firstEnum

修复:两处调用均改为传递 { encounterId, firstEnum } 对象,firstEnum 默认值为1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:51 +08:00
赵云
5357ddb220 Fix Bug #400: 门诊医生站点击【完诊】后,triage_queue_item 表 status 字段未按规范更新为 30
队列弹窗【完成】按钮调用完诊API时,传递了原始 Long 值而非对象参数,
导致后端 @RequestBody 反序列化时 encounterId 为 null,队列项状态无法更新。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:51 +08:00
赵云
79bd7c0281 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:15:51 +08:00
Ranyunqiao
4c4f01abd1 429 433 438 476 477 478 2026-05-13 00:15:51 +08:00
关羽
b4d452995f Fix Bug #480: [住院护士站-医嘱执行] 非耗材类医嘱执行报"耗材库存"错误且全选逻辑联动异常
Root cause analysis:
1. 非耗材类医嘱(如口服药"荆防颗粒")执行后,前端调用lotNumberMatch时传入所有患者
   encounterId。该后端函数会查询所有DeviceDispense记录并校验耗材库存,若任一患者存
   在耗材记录但无库存则报错"发耗材单生成失败,请检查耗材库存"。原代码的hasMedOrDevice检
   查包含了med_medication_request(药品医嘱),导致纯药品执行也触发耗材校验。
2. 执行成功后调用handleGetPrescription()刷新列表,触发defaultSelectAllRows()自动全选
   所有行,导致用户看到复选框全部选中的联动异常。

Fix:
1. hasMedOrDevice改为hasDevice,仅当选中医嘱包含device类型时才调用lotNumberMatch
2. handleGetPrescription新增skipAutoSelectAll参数,执行/不执行/取消执行后刷新时不自动全选

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:18:58 +08:00
赵云
23c75b147e Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
根因:关闭计费弹窗时未调用 prescriptionlist 子组件的 closeAllPopovers 方法,
导致 el-popover(项目字典悬浮列表)在父弹窗销毁后仍残留显示。
修复:在 closeChargeDialog 中先调用 prescriptionRef.value.closeAllPopovers() 关闭所有子 popover。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:18:58 +08:00
赵云
1e431d096d Fix Bug #462: [目录管理-诊疗目录] 编辑弹窗中"所需标本"下拉框数据加载失败
根因:sys_dict_type 表中缺少 specimen_code 字典类型定义,sys_dict_data
表中缺少对应标本数据记录,导致前端 useDict('specimen_code') 请求返回空数组。

修复:新增 SQL 迁移脚本,插入 specimen_code 字典类型及7条标本数据
(血液、尿液、粪便、呼吸道、无菌体液、生殖道、其他)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:18:58 +08:00
赵云
e1de467b68 fix: 修复 Bug #441 — 护士角色无权访问卫生机构列表
将租户Controller中4个只读端点的权限从 system:tenant:operate 降级为 system:tenant:list:
- getTenantPage (下拉列表数据源)
- getTenantDetail
- getTenantUserPage
- getUnbindTenantUserList

增删改操作保持 system:tenant:operate 不变。
同步更新 sys_menu 表:menu_id=2048 perms='system:tenant:list'

Root cause: 护士角色进入门诊手术安排页时 onMounted 调用 /system/tenant/page,该接口要求 system:tenant:operate 权限,护士角色无此权限导致卫生机构下拉列表为空,后续所有查询均失败。
2026-05-12 12:18:58 +08:00
53a3460092 调优:测试环境数据库改为 test1 2026-05-12 12:18:58 +08:00
赵云
084d04d518 Fix Bug #441: 门诊手术安排:手术室护士角色进入页面提示"无权限"且"获取卫生机构列表失败"
策略B(优雅降级):将 loadDeptList、loadDoctorList、loadNurseList、loadOperatingRoomList 四个初始化接口中的 proxy.$modal.msgError 改为 console.warn 静默降级,避免权限不足时弹窗阻断页面。配合已有的 loadOrgList 修复,确保手术室护士等角色进入页面时不会因个别字典接口权限拒绝而弹窗报错。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:18:58 +08:00
赵云
d7f8a20d76 Fix Bug #435: 门诊手术安排:编辑弹窗中"费用类别"字段数据未回显
根因:getSurgeryScheduleDetail 查询未关联 fin_contract 表,导致 feeType 始终为 null
修复:SQL 中添加 adm_encounter → adm_account → fin_contract 三表关联,取 fc.contract_name AS feeType;
      OpScheduleDto 新增 feeType 字段用于接收映射

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:18:58 +08:00
331fc532fc bug 486 [住院医生工作站-临床医嘱] 医嘱检索框不支持全局模糊搜索,未选“医嘱类型”时检索结果为空 删去了搜索条件的默认值 2026-05-12 12:18:58 +08:00
关羽
dafa5961c4 Fix Bug #489: 护士校对界面重复待校对记录 - SQL JOIN 乘法修复
根因: AdviceProcessAppMapper.xml 中 LEFT JOIN med_medication_dispense
直接关联时,一条药品医嘱若有多条发药记录会产生多行结果(SQL 笛卡尔积),
导致护士校对界面显示重复的待校对记录。

修复: 将直接 JOIN 改为 ROW_NUMBER() 子查询,每个 med_req_id 只取最新
一条发药记录的状态,避免行倍增。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:30:17 +08:00
赵云
f223192ec5 Fix Bug #502: 【住院护士站-汇总发药申请】顶部医嘱类型(长期/临时)过滤按钮点击无响应
根因:父组件 index.vue 中 therapyEnum 变量未声明为 ref,且未通过 props 传递给子组件 prescriptionList.vue,
导致点击"长期/临时"按钮时数据流断裂,子组件 API 调用始终使用本地未变化的 therapyEnum 值。

修复:
1. index.vue 新增 const therapyEnum = ref(undefined)
2. index.vue 新增 handleTherapyChange() 调用 handleGetPrescription() 刷新列表
3. index.vue 将 therapyEnum 作为 prop 传入 PrescriptionList
4. prescriptionList.vue 将本地 therapyEnum ref 改为 props 接收
2026-05-11 15:30:17 +08:00
赵云
c7956a116b fix: 恢复 Bug #497 的后端修改 + 数据库字段同步 (ALTER TABLE doc_request_form ADD COLUMN status) 2026-05-11 15:30:17 +08:00
关羽
845354e863 fix: 还原 Bug #443/#475/#477/#486/#497 引入的 getRequestForm 编译错误 2026-05-11 15:30:17 +08:00
赵云
56ea4b6af1 fix: 完整回退 Bug #497 的后端修改(SQL/Java接口/Impl/Dto) 2026-05-11 15:30:17 +08:00
赵云
703e9ead43 Fix Bug #480: [住院护士站-医嘱执行] 非耗材类医嘱执行报"耗材库存"错误且全选逻辑联动异常
根因分析:
1. 非耗材类医嘱执行报"耗材库存"错误: handleExecute 中无条件调用 lotNumberMatch,
   后端会校验该就诊下所有待发放耗材库存,即使当前执行的是口服药等非耗材类医嘱
2. 全选联动异常: msgSuccess 在 handleGetPrescription 之前执行,数据刷新后
   defaultSelectAllRows 重新选中所有行,用户关闭弹窗后看到全选效果

修复方案:
1. 增加医嘱类型判断,仅当选中医嘱包含药品(med_medication_request)或耗材(device)
   类型时才调用 lotNumberMatch
2. 调整执行顺序:先刷新数据再显示成功弹窗,避免用户看到数据刷新的副作用

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:30:17 +08:00
赵云
a43f98cc5a fix: 完全回退 Bug #497 引入的 drf.status 字段(数据库不存在) 2026-05-11 15:30:17 +08:00
wangjian963
9215c288d3 506 门诊挂号:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符
CommonConstants.AppointmentOrderStatus 常量 → OrderStatus 枚举重构
   新增枚举:0=患者取消 / 1=有效 / 2=系统取消 / 3=已完成
   退号流程加乐观锁防并发,slot 状态改回待约,退号日志独立事务 修复 XML 中 Integer 比较用字符串的问题
Bug #411 — 诊室过滤栏从科室下拉框改为诊室按钮组
2026-05-11 15:30:17 +08:00
关羽
777ba71c7d Fix Bug #486: [住院医生工作站-临床医嘱] 医嘱检索框不支持全局模糊搜索,未选"医嘱类型"时检索结果为空
根因:handleFocus/handleChange 中 categoryCode 的计算逻辑错误。当新增行未选择
医嘱类型时(row.adviceType 为 undefined),代码回退到 adviceQueryParams 的默认值并
匹配到具体药品分类(如西药 categoryCode='2'),导致搜索被限制在单一分类而非全局药库。

修复:简化 categoryCode 判定为 `row.adviceType !== undefined ? selectedItem.categoryCode : ''`,
未选类型时传空 categoryCode,使 searchKey 在全药库范围内模糊匹配。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:07:50 +08:00
关羽
c3dfd3eb21 Fix Bug #499: 【住院医生工作站-检查申请】检查申请列表缺失查询过滤功能,不符合临床高效检索要求
补充修复:
1. 后端 get-inspection 接口新增 keyword 关键字参数(检验申请关键字搜索)
2. 新增 deleteRequestForm 接口(仅待签发状态可删除)
3. 新增 withdrawRequestForm 接口(已签发状态撤回至待签发)
4. RequestForm 实体新增 status 字段(用于撤回方法状态校验)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:07:50 +08:00
关羽
8093f8acda Fix Bug #477: 住院医生工作站-住院检查申请详情弹窗中"发往科室"字段显示为短横线(-),未正常获取数据
根因:handleViewDetail 为同步方法,点击详情时 getLocationInfo 尚未返回,
orgOptions 为空导致 recursionFun 无法将 targetDepartment ID 解析为科室名称。

修复:
1. 前端(4个申请组件):handleViewDetail 改为 async,解析 descJson 前确保 orgOptions 已加载
2. 前端:watch encounterId 改为 Promise.all 并行加载数据和科室列表
3. 后端:新增 keyword 关键字筛选参数(申请单号/检查项目模糊匹配)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:03:40 +08:00
关羽
8fae6fe3d5 Fix Bug #491: 【执行科室配置】保存配置时系统报错
根因: addOrEditOrgLoc 方法中 organizationService.getById() 返回 null 时
直接调用 .getName() 导致 NullPointerException。当数据库中某条执行科室配置
关联的 organizationId 对应的科室记录已被删除时触发此问题。

修复: 在调用 getName() 前增加 null 检查,返回"未知科室"作为降级提示。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:01:15 +08:00
关羽
5af86494dd Fix Bug #500: 【门诊医生站】检查申请右侧"检查项目分类"切换时,界面出现明显抖动/闪烁
根因分析:
1. el-collapse accordion 模式下快速切换分类时,连续的折叠/展开动画重叠,
   Element Plus 在动画过程中重新计算面板高度,导致高度跳变和白屏闪烁
2. 折叠容器缺少 overflow:hidden,动画过渡期间内容溢出造成闪烁

修复方案:
1. 添加 isAnimating 防抖标志,handleCollapseChange 中 300ms 内忽略后续点击
   (与 CSS 过渡时长一致),让当前动画完整执行后再响应下一次切换
2. .collapse-scroll 添加 overflow-x:hidden,防止水平方向溢出
3. :deep(.el-collapse-item__wrap) 添加 overflow:hidden 替代 will-change:height,
   避免强制 GPU 合成层带来的性能开销

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 13:49:33 +08:00
15 changed files with 202 additions and 94 deletions

View File

@@ -106,9 +106,4 @@ public class OpScheduleDto extends OpSchedule {
* 创建人名称 * 创建人名称
*/ */
private String createByName; private String createByName;
/**
* 费用类别
*/
private String feeType;
} }

View File

@@ -63,4 +63,20 @@ public interface IRequestFormManageAppService {
* @return 申请单 * @return 申请单
*/ */
IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto); IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto);
/**
* 删除申请单(仅待签发状态可删除)
*
* @param requestFormId 申请单ID
* @return 结果
*/
R<?> deleteRequestForm(Long requestFormId);
/**
* 撤回申请单(已签发状态撤回至待签发)
*
* @param requestFormId 申请单ID
* @return 结果
*/
R<?> withdrawRequestForm(Long requestFormId);
} }

View File

@@ -474,4 +474,68 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
return requestFormManageAppMapper.getRequestFormPage(requestFormDto, page); return requestFormManageAppMapper.getRequestFormPage(requestFormDto, page);
} }
/**
* 删除申请单(仅待签发状态可删除)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> deleteRequestForm(Long requestFormId) {
if (requestFormId == null) {
return R.fail("申请单ID不能为空");
}
RequestForm requestForm = iRequestFormService.getById(requestFormId);
if (requestForm == null) {
return R.fail("申请单不存在");
}
if (!Integer.valueOf(0).equals(requestForm.getStatus())) {
return R.fail("仅待签发状态的申请单可删除");
}
// 删除申请单
iRequestFormService.removeById(requestFormId);
// 删除关联的诊疗项目及账单
String prescriptionNo = requestForm.getPrescriptionNo();
List<Long> serviceRequestIds = iServiceRequestService
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo))
.stream().map(ServiceRequest::getId).collect(Collectors.toList());
for (Long serviceRequestId : serviceRequestIds) {
iServiceRequestService.removeById(serviceRequestId);
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_SERVICE_REQUEST, serviceRequestId);
}
return R.ok(null, "删除成功");
}
/**
* 撤回申请单(已签发状态撤回至待签发)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<?> withdrawRequestForm(Long requestFormId) {
if (requestFormId == null) {
return R.fail("申请单ID不能为空");
}
RequestForm requestForm = iRequestFormService.getById(requestFormId);
if (requestForm == null) {
return R.fail("申请单不存在");
}
if (!Integer.valueOf(1).equals(requestForm.getStatus())) {
return R.fail("仅已签发状态的申请单可撤回");
}
// 将申请单状态回滚至待签发
RequestForm updateForm = new RequestForm();
updateForm.setId(requestFormId);
updateForm.setStatus(0);
iRequestFormService.updateById(updateForm);
// 将关联的诊疗项目状态回滚至DRAFT
String prescriptionNo = requestForm.getPrescriptionNo();
List<ServiceRequest> serviceRequests = iServiceRequestService
.list(new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getPrescriptionNo, prescriptionNo));
for (ServiceRequest serviceRequest : serviceRequests) {
ServiceRequest updateService = new ServiceRequest();
updateService.setId(serviceRequest.getId());
updateService.setStatusEnum(RequestStatus.DRAFT.getValue());
iServiceRequestService.updateById(updateService);
}
return R.ok(null, "撤回成功");
}
} }

View File

@@ -99,7 +99,7 @@ public class RequestFormManageController {
* @param startDate 开始日期可选格式yyyy-MM-dd * @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd * @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选) * @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/检验项目模糊匹配) * @param keyword 关键字(可选,申请单号/检验项目名称模糊匹配)
* @return 检验申请单 * @return 检验申请单
*/ */
@GetMapping(value = "/get-inspection") @GetMapping(value = "/get-inspection")

View File

@@ -89,15 +89,12 @@
cs.apply_doctor_name AS apply_doctor_name, cs.apply_doctor_name AS apply_doctor_name,
drf.create_time AS apply_time, drf.create_time AS apply_time,
os.surgery_nature AS surgeryType, os.surgery_nature AS surgeryType,
fc.contract_name AS feeType, os.fee_type AS feeType,
COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo COALESCE(pi.identifier_no, ap.bus_no, '') AS identifierNo
FROM op_schedule os FROM op_schedule os
LEFT JOIN adm_patient ap ON os.patient_id = ap.id LEFT JOIN adm_patient ap ON os.patient_id = ap.id
INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0' INNER JOIN cli_surgery cs ON os.oper_code = cs.surgery_no AND cs.delete_flag = '0'
LEFT JOIN adm_organization o ON cs.org_id = o.id LEFT JOIN adm_organization o ON cs.org_id = o.id
LEFT JOIN adm_encounter ae ON ae.id = cs.encounter_id AND ae.delete_flag = '0'
LEFT JOIN adm_account aa ON aa.encounter_id = ae.id AND aa.delete_flag = '0'
LEFT JOIN fin_contract fc ON fc.bus_no = aa.contract_no AND fc.delete_flag = '0'
LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no LEFT JOIN doc_request_form drf ON drf.prescription_no=cs.surgery_no
LEFT JOIN ( LEFT JOIN (
SELECT patient_id, identifier_no SELECT patient_id, identifier_no

View File

@@ -280,9 +280,17 @@
aa.balance_amount aa.balance_amount
) AS personal_account ) AS personal_account
ON personal_account.encounter_id = ae.id ON personal_account.encounter_id = ae.id
LEFT JOIN med_medication_dispense mmd LEFT JOIN (
SELECT med_req_id, status_enum
FROM (
SELECT med_req_id, status_enum,
ROW_NUMBER() OVER (PARTITION BY med_req_id ORDER BY id DESC) AS rn
FROM med_medication_dispense
WHERE delete_flag = '0'
) t
WHERE rn = 1
) mmd
ON mmd.med_req_id = T1.id ON mmd.med_req_id = T1.id
AND mmd.delete_flag = '0'
WHERE T1.delete_flag = '0' WHERE T1.delete_flag = '0'
AND T1.refund_medicine_id IS NULL AND T1.refund_medicine_id IS NULL
AND T1.generate_source_enum = #{doctorPrescription} AND T1.generate_source_enum = #{doctorPrescription}

View File

@@ -59,4 +59,9 @@ public class RequestForm extends HisBaseEntity {
*/ */
private String typeCode; private String typeCode;
/**
* 单据状态 0=待签发 1=已签发 2=已校对 3=待接收 4=已接收 5=已检查 6=已出报告 7=已作废
*/
private Integer status;
} }

View File

@@ -193,6 +193,9 @@ public class OpSchedule extends HisBaseEntity {
/** 外请专家姓名 */ /** 外请专家姓名 */
private String externalExpertName; private String externalExpertName;
/** 费用类别 */
private String feeType;
/** 备注信息 */ /** 备注信息 */
private String remark; private String remark;

View File

@@ -15,7 +15,7 @@
DELETE DELETE
FROM adm_encounter_diagnosis FROM adm_encounter_diagnosis
WHERE encounter_id = #{encounterId} WHERE encounter_id = #{encounterId}
AND tcm_flag = 0 AND tcm_flag = 1
</delete> </delete>
<select id="getEncounterDiagnosisByEncounterConDefId" <select id="getEncounterDiagnosisByEncounterConDefId"
resultType="com.openhis.administration.domain.EncounterDiagnosis"> resultType="com.openhis.administration.domain.EncounterDiagnosis">

View File

@@ -697,7 +697,6 @@ async function handleCategoryExpand(cat) {
code: m.code, code: m.code,
price: m.price || 0, price: m.price || 0,
packageName: m.packageName || '', packageName: m.packageName || '',
packageId: m.packageId || null,
packagePrice: m.packagePrice || null, packagePrice: m.packagePrice || null,
serviceFee: m.serviceFee || null serviceFee: m.serviceFee || null
})); }));
@@ -1009,16 +1008,11 @@ function handleRowClick(row) {
activeDetailTab.value = 'applyForm'; activeDetailTab.value = 'applyForm';
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => { request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
const resp = res.data || res; const resp = res.data || res;
// Bug #408修复: items 在 AjaxResult 顶层(res.items / resp.items),不在 ExamApply 对象内 // 保存 items 在顶层响应中,避免后面 d.data 赋值后丢失
// 防御性提取:优先取顶层 items兼容嵌套在 resp.data.items 的情况 const rawItems = resp.items;
let rawItems = res.items || resp.items;
if (!rawItems && resp.data && typeof resp.data === 'object') {
rawItems = resp.data.items;
}
rawItems = rawItems || [];
const d = resp.data || resp; const d = resp.data || resp;
if (d) Object.assign(form, d); if (d) Object.assign(form, d);
if (Array.isArray(rawItems) && rawItems.length > 0) { if (rawItems && Array.isArray(rawItems)) {
try { try {
// 为每个项目加载检查方法 // 为每个项目加载检查方法
const itemsWithMethods = await Promise.all(rawItems.map(async m => { const itemsWithMethods = await Promise.all(rawItems.map(async m => {
@@ -1049,18 +1043,12 @@ function handleRowClick(row) {
code: md.code, code: md.code,
price: m.itemFee || 0, // fallback 到已保存的价格 price: m.itemFee || 0, // fallback 到已保存的价格
packageName: md.packageName || '', packageName: md.packageName || '',
packageId: md.packageId || null,
packagePrice: md.packagePrice || null, // Bug #384修复: 套餐价格 packagePrice: md.packagePrice || null, // Bug #384修复: 套餐价格
serviceFee: md.serviceFee || null serviceFee: md.serviceFee || null
})); }));
// 如果有已保存的检查方法信息,尝试匹配 // 如果有已保存的检查方法信息,尝试匹配
if (m.checkMethodId) { if (m.checkMethodId) {
item.selectedMethod = item.methods.find(md => md.id === m.checkMethodId) || null; item.selectedMethod = item.methods.find(md => md.id === m.checkMethodId) || null;
// 从已保存的方法中获取套餐信息
if (item.selectedMethod?.packageId) {
item.isPackage = true;
item.packageId = item.selectedMethod.packageId;
}
} }
} }
} catch (err) { } catch (err) {
@@ -1121,13 +1109,6 @@ async function handleMethodSelect(checked, method, cat) {
const existingItem = selectedItems.value.find(s => s.id === targetItem.id); const existingItem = selectedItems.value.find(s => s.id === targetItem.id);
if (existingItem) { if (existingItem) {
existingItem.selectedMethod = method; existingItem.selectedMethod = method;
// 从方法中获取套餐信息
if (method.packageId) {
existingItem.isPackage = true;
existingItem.packageId = method.packageId;
// 预加载套餐明细
loadPackageDetailsForItem(existingItem);
}
updateMethodDisplay(); updateMethodDisplay();
return; return;
} }
@@ -1142,7 +1123,7 @@ async function handleMethodSelect(checked, method, cat) {
} }
} }
const newItem = { selectedItems.value.push({
id: targetItem.id, name: targetItem.name, id: targetItem.id, name: targetItem.name,
price: targetItem.price, quantity: 1, price: targetItem.price, quantity: 1,
serviceFee: targetItem.serviceFee || 0, serviceFee: targetItem.serviceFee || 0,
@@ -1154,16 +1135,9 @@ async function handleMethodSelect(checked, method, cat) {
methods: [method], methods: [method],
selectedMethod: method, selectedMethod: method,
expanded: false, expanded: false,
// 从方法中获取套餐信息(优先级高于项目本身的 packageName isPackage: !!targetItem.packageName,
isPackage: !!method.packageId || !!targetItem.packageName, packageId: targetItem.packageId || null
packageId: method.packageId || targetItem.packageId || null });
};
selectedItems.value.push(newItem);
// 如果是套餐,预加载套餐明细
if (newItem.isPackage && newItem.packageId) {
loadPackageDetailsForItem(newItem);
}
// 自动回填执行科室 // 自动回填执行科室
if (selectedItems.value.length === 1 && cat?.performDeptName) { if (selectedItems.value.length === 1 && cat?.performDeptName) {
@@ -1208,7 +1182,6 @@ async function handleItemSelect(checked, item, cat) {
code: m.code, code: m.code,
price: m.price || item.price, // fallback 到项目价格 price: m.price || item.price, // fallback 到项目价格
packageName: m.packageName || '', packageName: m.packageName || '',
packageId: m.packageId || null,
packagePrice: m.packagePrice || null, // Bug #384修复: 套餐价格 packagePrice: m.packagePrice || null, // Bug #384修复: 套餐价格
serviceFee: m.serviceFee || null // Bug #384修复: 服务费 serviceFee: m.serviceFee || null // Bug #384修复: 服务费
})); }));

View File

@@ -169,6 +169,7 @@
<script setup> <script setup>
import {getCurrentInstance} from 'vue'; // 添加 nextTick 导入 import {getCurrentInstance} from 'vue'; // 添加 nextTick 导入
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { formatDateStr } from '@/utils';
import { import {
delEncounterDiagnosis, delEncounterDiagnosis,
deleteDiagnosisBind, deleteDiagnosisBind,
@@ -286,10 +287,22 @@ function getList() {
if (obj.diagSrtNo == null) { if (obj.diagSrtNo == null) {
obj.diagSrtNo = '1'; obj.diagSrtNo = '1';
} }
// 补充缺失的元数据字段
if (!obj.diagnosisDoctor) {
obj.diagnosisDoctor = props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name;
}
if (!obj.diagnosisTime) {
obj.diagnosisTime = item.diagnosisTime || getCurrentDate();
}
if (!obj.classification) {
obj.classification = item.typeName === '中医诊断' ? '中医' : '西医';
}
if (obj.longTermFlag == null) {
obj.longTermFlag = 0;
}
return obj; return obj;
}); });
form.value.diagnosisList = datas; form.value.diagnosisList = datas;
// form.value.diagnosisList = res.data;
emits('diagnosisSave', false); emits('diagnosisSave', false);
} }
}); });
@@ -305,13 +318,16 @@ function getList() {
ybNo: item.ybNo, ybNo: item.ybNo,
medTypeCode: item.medTypeCode, medTypeCode: item.medTypeCode,
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name, diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: new Date().toLocaleString('zh-CN') diagnosisTime: item.diagnosisTime || getCurrentDate(),
diagSrtNo: item.diagSrtNo,
classification: '中医',
longTermFlag: item.longTermFlag || 0
}); });
}); });
// 将新数据添加到现有列表中 // 将新数据添加到现有列表中
form.value.diagnosisList.push(...newList); form.value.diagnosisList.push(...newList);
// 重新排序整个列表 // 重新排序整个列表
form.value.diagnosisList.sort((a, b) => { form.value.diagnosisList.sort((a, b) => {
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999; const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
@@ -322,7 +338,7 @@ function getList() {
emits('diagnosisSave', false); emits('diagnosisSave', false);
} }
}); });
getTree(); getTree();
} }
@@ -339,11 +355,12 @@ function handleImport() {
if (!props.patientInfo || !props.patientInfo.encounterId) { if (!props.patientInfo || !props.patientInfo.encounterId) {
return; return;
} }
if (props.patientInfo.contractName != '自费') { if (props.patientInfo.contractName != '自费') {
// 获取患者慢性病信息 // 获取患者慢性病信息
getChronicDisease({ encounterId: props.patientInfo.encounterId }).then((res) => { getChronicDisease({ encounterId: props.patientInfo.encounterId }).then((res) => {
if (res.data && res.data.length > 0) { if (res.data && res.data.length > 0) {
const currentDate = getCurrentDate();
res.data.forEach((item, index) => { res.data.forEach((item, index) => {
form.value.diagnosisList.push({ form.value.diagnosisList.push({
...item, ...item,
@@ -355,7 +372,10 @@ function handleImport() {
iptDiseTypeCode: 2, iptDiseTypeCode: 2,
diagnosisDesc: '', diagnosisDesc: '',
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name, diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: new Date().toLocaleString('zh-CN'), diagnosisTime: currentDate,
onsetDate: currentDate,
classification: '西医',
longTermFlag: 0,
//添加 patientId //添加 patientId
patientId: props.patientInfo.patientId patientId: props.patientInfo.patientId
}, },
@@ -470,6 +490,7 @@ function handleAddDiagnosis() {
* 添加诊断项 * 添加诊断项
*/ */
function addDiagnosisItem() { function addDiagnosisItem() {
const currentDate = getCurrentDate();
form.value.diagnosisList.push({ form.value.diagnosisList.push({
showPopover: false, showPopover: false,
name: undefined, name: undefined,
@@ -479,9 +500,10 @@ function addDiagnosisItem() {
iptDiseTypeCode: 2, iptDiseTypeCode: 2,
diagnosisDesc: '', diagnosisDesc: '',
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name, diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: new Date().toLocaleString('zh-CN'), diagnosisTime: currentDate,
classification: '西医',
// 新增这一行:为每个诊断项添加 patientId onsetDate: currentDate,
longTermFlag: 0,
patientId: props.patientInfo.patientId patientId: props.patientInfo.patientId
}); });
@@ -558,16 +580,7 @@ function handleMaindise(value, index) {
/** /**
* 保存诊断 * 保存诊断
*/ */
/** async function handleSaveDiagnosis() {
* 保存诊断
*/
/**
* 保存诊断
*/
/**
* 保存诊断
*/
function handleSaveDiagnosis() {
for (let index = 0; index < (form.value.diagnosisList || []).length; index++) { for (let index = 0; index < (form.value.diagnosisList || []).length; index++) {
const item = form.value.diagnosisList[index]; const item = form.value.diagnosisList[index];
if (!item.diagSrtNo) { if (!item.diagSrtNo) {
@@ -578,7 +591,7 @@ function handleSaveDiagnosis() {
break; break;
} }
} }
proxy.$refs.formRef.validate((valid) => { proxy.$refs.formRef.validate(async (valid) => {
if (valid) { if (valid) {
if (form.value.diagnosisList.length === 0) { if (form.value.diagnosisList.length === 0) {
proxy.$modal.msgWarning('诊断不能为空'); proxy.$modal.msgWarning('诊断不能为空');
@@ -591,43 +604,51 @@ function handleSaveDiagnosis() {
// 设置保存标志避免触发watch监听器 // 设置保存标志避免触发watch监听器
isSaving.value = true; isSaving.value = true;
// 步骤1深拷贝并按 diagSrtNo 排序 try {
const sortedList = [...form.value.diagnosisList].sort((a, b) => { // 步骤1深拷贝并按 diagSrtNo 排序
const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999; const sortedList = [...form.value.diagnosisList].sort((a, b) => {
const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999; const aNo = typeof a.diagSrtNo === 'number' ? a.diagSrtNo : 9999;
return aNo - bNo; const bNo = typeof b.diagSrtNo === 'number' ? b.diagSrtNo : 9999;
}); return aNo - bNo;
});
// 步骤2重新分配连续的序号从1开始 // 步骤2转换日期格式为后端期望的格式
sortedList.forEach((item, index) => { const diagnosisChildList = sortedList.map(item => ({
item.diagSrtNo = index + 1; // 这里是关键!把“诊断排序”改成新顺序 ...item,
}); onsetDate: item.onsetDate ? formatDateStr(item.onsetDate, 'YYYY/M/D HH:mm:ss') : null,
diagnosisTime: item.diagnosisTime ? formatDateStr(item.diagnosisTime, 'YYYY/M/D HH:mm:ss') : null
}));
// 步骤3提交排序后的数据
const res = await saveDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: diagnosisChildList,
});
// 步骤3提交排序后的数据
saveDiagnosis({
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
diagnosisChildList: sortedList,
}).then((res) => {
if (res.code === 200) { if (res.code === 200) {
// 步骤4更新本地数据,使用全新对象防止响应式问题 // 步骤4从服务器刷新数据,确保获取最新的诊断信息(避免重复记录)
form.value.diagnosisList = sortedList.map(item => ({ ...item })); await getList();
getTree();
emits('diagnosisSave', false); emits('diagnosisSave', false);
proxy.$modal.msgSuccess('诊断已保存'); proxy.$modal.msgSuccess('诊断已保存');
// 食源性疾病逻辑 // 食源性疾病逻辑
isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => { isFoodDiseasesNew({ encounterId: props.patientInfo.encounterId }).then((res2) => {
if (res2.code === 20 && res2.data) { if (res2.code === 200 && res2.data) {
window.open(res2.data, '_blank'); window.open(res2.data, '_blank');
} }
}); });
} }
}).finally(() => { } catch (error) {
console.error('保存诊断失败:', error);
const errorMsg = error?.response?.data?.msg || error?.message || '保存诊断失败,请稍后重试';
proxy.$modal.msgError(errorMsg);
} finally {
setTimeout(() => { setTimeout(() => {
isSaving.value = false; isSaving.value = false;
}, 100); }, 100);
}); }
} }
}); });
} }
@@ -687,6 +708,7 @@ function handleNodeClick(data) {
proxy.$modal.msgWarning('该诊断项已存在'); proxy.$modal.msgWarning('该诊断项已存在');
return; return;
} }
const currentDate = getCurrentDate();
form.value.diagnosisList.push({ form.value.diagnosisList.push({
ybNo: data.ybNo, ybNo: data.ybNo,
name: data.name, name: data.name,
@@ -694,9 +716,12 @@ function handleNodeClick(data) {
medTypeCode: undefined, medTypeCode: undefined,
diagSrtNo: form.value.diagnosisList.length + 1, diagSrtNo: form.value.diagnosisList.length + 1,
definitionId: data.definitionId, definitionId: data.definitionId,
classification: '西医',
onsetDate: currentDate,
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name, diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: new Date().toLocaleString('zh-CN'), diagnosisTime: currentDate,
// 添加 patientId longTermFlag: 0,
selectedDiseases: data.ybNo ? [data.ybNo] : [],
patientId: props.patientInfo.patientId patientId: props.patientInfo.patientId
}); });
if (form.value.diagnosisList.length == 1) { if (form.value.diagnosisList.length == 1) {

View File

@@ -164,7 +164,7 @@ onMounted(() => {
* type(1watch监听类型 2:点击保存类型) * type(1watch监听类型 2:点击保存类型)
* selectProjectIds(选中项目的id数组) * selectProjectIds(选中项目的id数组)
* */ * */
const projectWithDepartment = (selectProjectIds, type) => { const projectWithDepartment = (selectProjectIds) => {
//1.获取选中的项目 2.判断项目的执行科室是否相同 3.判断执行科室是否配置 4.将项目的执行科室复值到执行科室下拉选位置 //1.获取选中的项目 2.判断项目的执行科室是否相同 3.判断执行科室是否配置 4.将项目的执行科室复值到执行科室下拉选位置
let isRelease = true; let isRelease = true;
// 选中项目的数组 // 选中项目的数组

View File

@@ -483,7 +483,7 @@ const submit = () => {
encounterId: patientInfo.value.encounterId, encounterId: patientInfo.value.encounterId,
organizationId: patientInfo.value.inHospitalOrgId, organizationId: patientInfo.value.inHospitalOrgId,
requestFormId: '', requestFormId: '',
name: applicationListAllFilter.map(item => item.adviceName).join('、'), name: '检查申请单',
descJson: JSON.stringify(submitForm), descJson: JSON.stringify(submitForm),
categoryEnum: '2', categoryEnum: '2',
}).then((res) => { }).then((res) => {

View File

@@ -631,6 +631,12 @@ function getListInfo(addNewRow) {
handleAddPrescription(); handleAddPrescription();
} }
}); });
}).catch((err) => {
console.error('处方列表加载失败:', err);
// 🔧 Bug #405 修复列表加载失败时确保所有行被锁定isEdit=false
// 防止行永久处于可编辑状态
prescriptionList.value.forEach(item => { item.isEdit = false; });
loadingInstance.close();
}); });
getContract({ encounterId: patientInfo.value.encounterId }).then((res) => { getContract({ encounterId: patientInfo.value.encounterId }).then((res) => {
contractList.value = res.data; contractList.value = res.data;
@@ -1443,6 +1449,12 @@ function handleSaveBatch() {
}) })
.catch((error) => { .catch((error) => {
isSaving.value = false; isSaving.value = false;
// 🔧 Bug #405 修复:保存失败时也锁定行,防止行永久处于可编辑状态
filterPrescriptionList.value.forEach(item => {
if (item.statusEnum == 1 && !item.requestId) {
item.isEdit = false;
}
});
proxy.$modal.msgError(error?.msg || '保存失败,请重试'); proxy.$modal.msgError(error?.msg || '保存失败,请重试');
}); });
} }

View File

@@ -0,0 +1,10 @@
-- Bug #435: 门诊手术安排编辑弹窗中"费用类别"字段数据未回显
-- 原因op_schedule 表缺少 fee_type 字段,导致手术安排创建时费用类别未被持久化
ALTER TABLE op_schedule ADD COLUMN IF NOT EXISTS fee_type VARCHAR(50);
COMMENT ON COLUMN op_schedule.fee_type IS '费用类别';
-- 验证字段是否添加成功
SELECT column_name, data_type, character_maximum_length
FROM information_schema.columns
WHERE table_name = 'op_schedule'
AND column_name = 'fee_type';