Compare commits

..

44 Commits

Author SHA1 Message Date
赵云
c7a6578094 Fix Bug #493: 【住院医生工作站-临床医嘱-检验申请】项目未维护执行科室时,医生手动选择发往科室后仍报错且数据被清空
根因:projectWithDepartment 函数定义时遗漏了 type 参数,导致函数体内引用 type 变量时报 ReferenceError(未定义),type === 2 的判断永远无法正确执行。用户在提交时手动选择的发往科室被清空且无法提交。

修复:在函数签名中添加 type 参数,与 working 版本 medicalExaminations.vue 保持一致。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 00:13:44 +08:00
刘备
a5ed6eaea9 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:13:44 +08:00
赵云
31504f3260 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:13:44 +08:00
张飞
8bd687581c 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:13:44 +08:00
刘备
2d16d6695c 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:13:44 +08:00
荀彧
d04f998ae7 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:13:44 +08:00
荀彧
2d5d01bedc 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:13:44 +08:00
刘备
44fd7de754 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-12 23:29:30 +08:00
赵云
69b28c59f6 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-12 23:20:44 +08:00
华佗
4e71b861ab 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-12 23:08:08 +08:00
陈琳
7c471205a3 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-12 23:02:12 +08:00
刘备
dc8661c3d0 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-12 22:44:31 +08:00
赵云
c634551fdb Fix Bug #509: [门诊医生站-手术申请] 提交申请后列表未实时刷新展示数据,且提示语需优化
1. 优化提示语:将"新增成功"/"修改成功"改为"手术申请提交成功!"/"手术申请修改成功!"
2. 优化执行顺序:先emit('saved')通知父组件刷新医嘱列表,再调用getList()刷新手术申请列表,确保数据刷新时序正确

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 22:05:40 +08:00
赵云
9280065c5b 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-12 18:39:23 +08:00
Ranyunqiao
220982fa34 Merge remote-tracking branch 'origin/develop' into develop 2026-05-12 17:27:36 +08:00
Ranyunqiao
460890e3c2 413 460 513 514 2026-05-12 17:27:25 +08:00
华佗
6a0a2ca711 Fix Bug #469: [住院医生工作站-检验申请] 完善【操作】列临床业务逻辑:支持按状态动态切换修改、删除、撤回等功能
根据单据状态动态显示操作按钮:
- 待签发(状态0):显示修改、删除、详情
- 已签发(状态1):显示撤回、详情
- 其他状态:仅显示详情

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 17:27:24 +08:00
赵云
fbbe0e9248 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-12 17:22:23 +08:00
赵云
b6df124e1b 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-12 17:05:32 +08:00
赵云
a0162686f4 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-12 13:06:21 +08:00
赵云
23561cc9b1 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:31:37 +08:00
Ranyunqiao
cdb676c859 Merge remote-tracking branch 'origin/develop' into develop 2026-05-12 12:21:46 +08:00
Ranyunqiao
1b159cdfab 429 433 438 476 477 478 2026-05-12 12:21:25 +08:00
关羽
01d48c75a5 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:19:11 +08:00
赵云
c8fa9b4bf0 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 11:59:49 +08:00
赵云
349c587386 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 11:34:55 +08:00
赵云
656079b27b 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 11:10:43 +08:00
ede6180c97 调优:测试环境数据库改为 test1 2026-05-12 11:01:28 +08:00
赵云
8de1f933e5 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 10:05:19 +08:00
赵云
3f35b4f2bb 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 10:04:26 +08:00
eb3d0ee608 bug 486 [住院医生工作站-临床医嘱] 医嘱检索框不支持全局模糊搜索,未选“医嘱类型”时检索结果为空 删去了搜索条件的默认值 2026-05-11 17:01:52 +08:00
赵云
dfe300cc1f 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:27:18 +08:00
wangjian963
9bd39c06e7 Merge remote-tracking branch 'origin/develop' into develop 2026-05-11 15:25:17 +08:00
赵云
bde42d6b14 fix: 恢复 Bug #497 的后端修改 + 数据库字段同步 (ALTER TABLE doc_request_form ADD COLUMN status) 2026-05-11 14:57:06 +08:00
关羽
01bf3177c9 fix: 还原 Bug #443/#475/#477/#486/#497 引入的 getRequestForm 编译错误 2026-05-11 14:40:07 +08:00
赵云
2a9f8376e6 fix: 完整回退 Bug #497 的后端修改(SQL/Java接口/Impl/Dto) 2026-05-11 14:13:49 +08:00
关羽
0774d9f877 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:08:13 +08:00
赵云
cee38eceae 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 14:07:49 +08:00
赵云
bbc740b6ce fix: 完全回退 Bug #497 引入的 drf.status 字段(数据库不存在) 2026-05-11 14:06:53 +08:00
关羽
256b986c0e 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:49 +08:00
关羽
eaac16769d 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:23 +08:00
wangjian963
df6c5f3824 506 门诊挂号:门诊诊前退号后,数据库多表状态值变更与 PRD 定义不符
CommonConstants.AppointmentOrderStatus 常量 → OrderStatus 枚举重构
   新增枚举:0=患者取消 / 1=有效 / 2=系统取消 / 3=已完成
   退号流程加乐观锁防并发,slot 状态改回待约,退号日志独立事务 修复 XML 中 Integer 比较用字符串的问题
Bug #411 — 诊室过滤栏从科室下拉框改为诊室按钮组
2026-05-11 13:51:47 +08:00
关羽
08075c90e2 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:50:04 +08:00
37 changed files with 1390 additions and 561 deletions

View File

@@ -7,6 +7,7 @@ import com.core.common.utils.MessageUtils;
import com.openhis.administration.domain.Location;
import com.openhis.administration.domain.Organization;
import com.openhis.administration.domain.OrganizationLocation;
import com.openhis.workflow.domain.ActivityDefinition;
import com.openhis.administration.mapper.OrganizationLocationMapper;
import com.openhis.administration.service.ILocationService;
import com.openhis.administration.service.IOrganizationLocationService;
@@ -70,6 +71,7 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
// 获取科室下拉选列表
List<Organization> organizationList = organizationService.getList(OrganizationType.DEPARTMENT.getValue(), null);
List<OrgLocInitDto.departmentOption> organizationOptions = organizationList.stream()
.filter(organization -> organization != null && organization.getName() != null)
.map(organization -> new OrgLocInitDto.departmentOption(organization.getId(), organization.getName()))
.collect(Collectors.toList());
initDto.setLocationFormOptions(chargeItemStatusOptions).setDepartmentOptions(organizationOptions);
@@ -131,12 +133,18 @@ public class OrganizationLocationAppServiceImpl implements IOrganizationLocation
@Override
public R<?> addOrEditOrgLoc(OrgLocQueryDto orgLocQueryDto) {
// Validate required fields before processing
if (orgLocQueryDto.getOrganizationId() == null) {
return R.fail("请选择执行科室");
}
OrganizationLocation orgLoc = new OrganizationLocation();
BeanUtils.copyProperties(orgLocQueryDto, orgLoc);
Long activityDefinitionId = orgLoc.getActivityDefinitionId();
String activityName = activityDefinitionId != null
? activityDefinitionMapper.selectById(activityDefinitionId).getName() : "";
ActivityDefinition activityDef = activityDefinitionId != null
? activityDefinitionMapper.selectById(activityDefinitionId) : null;
String activityName = activityDef != null ? activityDef.getName() : "";
List<OrganizationLocation> organizationLocationList =
organizationLocationService.getOrgLocListByOrgIdAndActivityDefinitionId(orgLoc.getActivityDefinitionId());

View File

@@ -246,6 +246,16 @@ public class OpCreateScheduleDto {
*/
private String communicationInfo;
/**
* 是否外请专家 1-是 0-否
*/
private Integer isExternalExpert;
/**
* 外请专家姓名
*/
private String externalExpertName;
/**
* 备注
*/

View File

@@ -231,29 +231,49 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 费用定价主表ID集合
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId)
.collect(Collectors.toList());
// 医嘱库存集合
List<AdviceInventoryDto> adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
adviceDefinitionIdList,
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
// 待发放个数信息
List<AdviceInventoryDto> adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
// 预减库存
List<AdviceInventoryDto> adviceInventory = adviceUtils.subtractInventory(adviceInventoryList,
adviceDraftInventoryList);
// 查询取药科室配置
List<AdviceInventoryDto> medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
// 将配置转为 {categoryCode -> 允许的locationId集合}
Map<String, Set<Long>> allowedLocByCategory = new HashMap<>();
if (medLocationConfig != null && !medLocationConfig.isEmpty()) {
for (AdviceInventoryDto cfg : medLocationConfig) {
if (cfg.getCategoryCode() == null || cfg.getLocationId() == null) {
continue;
// 判断是否包含药品或耗材类型(只有这些类型才需要库存相关查询)
boolean hasMedOrDevice = adviceTypes != null
&& (adviceTypes.contains(1) || adviceTypes.contains(2));
// 医嘱库存集合 — 仅药品/耗材需要库存查询,手术/诊疗(3,6)无库存概念,跳过以减少数据库开销
List<AdviceInventoryDto> adviceInventoryList;
List<AdviceInventoryDto> adviceDraftInventoryList;
List<AdviceInventoryDto> adviceInventory;
if (hasMedOrDevice) {
adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
adviceDefinitionIdList,
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
// 待发放个数信息
adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
// 预减库存
adviceInventory = adviceUtils.subtractInventory(adviceInventoryList, adviceDraftInventoryList);
} else {
adviceInventoryList = Collections.emptyList();
adviceDraftInventoryList = Collections.emptyList();
adviceInventory = Collections.emptyList();
}
// 查询取药科室配置 — 仅药品开单场景需要
List<AdviceInventoryDto> medLocationConfig;
Map<String, Set<Long>> allowedLocByCategory;
if (hasMedOrDevice) {
medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
// 将配置转为 {categoryCode -> 允许的locationId集合}
allowedLocByCategory = new HashMap<>();
if (medLocationConfig != null && !medLocationConfig.isEmpty()) {
for (AdviceInventoryDto cfg : medLocationConfig) {
if (cfg.getCategoryCode() == null || cfg.getLocationId() == null) {
continue;
}
allowedLocByCategory.computeIfAbsent(String.valueOf(cfg.getCategoryCode()), k -> new HashSet<>())
.add(cfg.getLocationId());
}
allowedLocByCategory.computeIfAbsent(String.valueOf(cfg.getCategoryCode()), k -> new HashSet<>())
.add(cfg.getLocationId());
}
} else {
medLocationConfig = Collections.emptyList();
allowedLocByCategory = Collections.emptyMap();
}
// 费用定价子表信息 - 使用分批处理避免大量参数问题
List<AdvicePriceDto> childCharge = new ArrayList<>();

View File

@@ -1,5 +1,6 @@
package com.openhis.web.doctorstation.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@@ -55,6 +56,7 @@ public class InfectiousDiseaseReportDto {
private String sex;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/** 实足年龄 */

View File

@@ -17,6 +17,9 @@ import com.openhis.common.constant.PromptMsgConstant;
import com.openhis.common.enums.*;
import com.openhis.common.utils.EnumUtils;
import com.openhis.common.utils.HisQueryUtils;
import com.core.common.utils.SecurityUtils;
import com.openhis.workflow.domain.InventoryItem;
import com.openhis.workflow.service.IInventoryItemService;
import com.openhis.web.basedatamanage.dto.LocationDto;
import com.openhis.web.common.dto.UnitDto;
import com.openhis.web.inventorymanage.appservice.IProductTransferAppService;
@@ -57,6 +60,9 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Autowired
private IPractitionerService practitionerService;
@Autowired
private IInventoryItemService inventoryItemService;
/**
* 商品调拨页面初始化
*
@@ -196,6 +202,23 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override
public R<?> addOrEditBatchTransferReceipt(List<ProductTransferDto> productTransferDtoList, Boolean flag) {
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (ProductTransferDto dto : productTransferDtoList) {
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0");
}
// 查询该药品在源仓库的实时库存总量
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "");
}
}
List<String> idList = new ArrayList<>();
if (flag) {
// 批量保存按钮
@@ -309,6 +332,22 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
@Override
public R<?> addOrEditTransferReceipt(List<ProductTransferDto> productTransferDtoList) {
// 校验调拨数量:必须 > 0 且不超过源库存数量(从数据库查实时库存)
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (ProductTransferDto dto : productTransferDtoList) {
if (dto.getItemQuantity() == null || dto.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0");
}
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
dto.getItemId(), dto.getLotNumber(), dto.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (dto.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "");
}
}
List<String> idList = new ArrayList<>();
// 单据号取得
@@ -380,6 +419,25 @@ public class ProductTransferAppServiceImpl implements IProductTransferAppService
*/
@Override
public R<?> submitApproval(String busNo) {
// 提交前再次校验调拨数量(从数据库查实时库存)
List<SupplyRequest> requestList = supplyRequestService.getSupplyByBusNo(busNo);
if (requestList != null && !requestList.isEmpty()) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
for (SupplyRequest request : requestList) {
if (request.getItemQuantity() == null || request.getItemQuantity().compareTo(java.math.BigDecimal.ZERO) <= 0) {
return R.fail("调拨数量必须大于0请检查后重新保存");
}
// 查询该药品在源仓库的实时库存总量
List<InventoryItem> inventoryList = inventoryItemService.selectInventoryByItemId(
request.getItemId(), request.getLotNumber(), request.getSourceLocationId(), tenantId);
java.math.BigDecimal actualStock = inventoryList.stream()
.map(InventoryItem::getQuantity)
.reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);
if (request.getItemQuantity().compareTo(actualStock) > 0) {
return R.fail("调拨数量不可超出源库存数量(当前库存:" + actualStock + "),请检查后重新保存");
}
}
}
// 单据提交审核
boolean result = supplyRequestService.submitApproval(busNo);
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))

View File

@@ -39,10 +39,22 @@ public interface IRequestFormManageAppService {
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @return 申请单列表
*/
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status);
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate);
/**
* 查询申请单(支持筛选+状态+关键字)
*
* @param encounterId 就诊id
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表
*/
List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status, String keyword);
/**
* 分页查询申请单
@@ -51,20 +63,4 @@ public interface IRequestFormManageAppService {
* @return 申请单
*/
IPage<RequestFormPageDto> getRequestFormPage(RequestFormDto requestFormDto);
/**
* 删除申请单(仅待签发状态可删除)
*
* @param requestFormId 申请单ID
* @return 结果
*/
R<?> deleteRequestForm(Long requestFormId);
/**
* 撤回申请单(已签发状态撤回至待签发)
*
* @param requestFormId 申请单ID
* @return 结果
*/
R<?> withdrawRequestForm(Long requestFormId);
}

View File

@@ -414,7 +414,7 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode) {
// 调用重载方法,不传筛选参数
return getRequestForm(encounterId, typeCode, null, null, null);
return getRequestForm(encounterId, typeCode, null, null);
}
/**
@@ -424,17 +424,32 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @return 申请单列表
*/
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status) {
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate) {
return getRequestForm(encounterId, typeCode, startDate, endDate, null, null);
}
/**
* 查询申请单(支持筛选+状态+关键字)
*
* @param encounterId 就诊id
* @param typeCode 申请单类型
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @param status 单据状态(可选)
* @param keyword 关键字(可选,申请单号/项目名称模糊匹配)
* @return 申请单列表
*/
@Override
public List<RequestFormQueryDto> getRequestForm(Long encounterId, String typeCode, String startDate, String endDate, String status, String keyword) {
// 检查参数
if (encounterId == null) {
return new java.util.ArrayList<>(); // 返回空列表而不是查询数据库
return new java.util.ArrayList<>();
}
List<RequestFormQueryDto> requestFormList = requestFormManageAppMapper.getRequestForm(encounterId, typeCode, startDate, endDate, status, null);
List<RequestFormQueryDto> requestFormList = requestFormManageAppMapper.getRequestForm(encounterId, typeCode, startDate, endDate, status, keyword);
for (RequestFormQueryDto requestFormQueryDto : requestFormList) {
// 查询处方详情
List<RequestFormDetailQueryDto> requestFormDetail =
@@ -456,68 +471,4 @@ public class RequestFormManageAppServiceImpl implements IRequestFormManageAppSer
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

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

View File

@@ -100,6 +100,7 @@
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = 6 THEN T2."name"
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} AND T1.service_table = 'wor_service_request' THEN COALESCE(T9.surgery_name, wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} THEN COALESCE(wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"
@@ -225,6 +226,7 @@
WHEN T1.context_enum = 6 AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = 6 THEN T2."name"
WHEN T1.context_enum = #{activity} AND T1.product_id = 0 AND T1.service_table = 'wor_service_request' THEN COALESCE(wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} AND T1.service_table = 'wor_service_request' THEN COALESCE(T9.surgery_name, wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{activity} THEN COALESCE(wsr.content_json::json->>'surgeryName', wsr.content_json::json->>'adviceName', T2."name")
WHEN T1.context_enum = #{medication} THEN T3."name"
WHEN T1.context_enum = #{device} THEN T4."name"

View File

@@ -98,9 +98,6 @@
INNER JOIN adm_organization_location AS T6 ON T6.distribution_category_code = t1.category_code AND T6.delete_flag = '0' AND T6.item_code = '1' AND T6.organization_id = #{organizationId} AND (CURRENT_TIME :: time (6) BETWEEN T6.start_time AND T6.end_time)
WHERE t1.delete_flag = '0'
AND T2.status_enum = #{statusEnum}
<if test="pricingFlag == 1">
AND 1 = 2
</if>
<if test="categoryCode != null and categoryCode != ''">
<!-- 🔧 BugFix: 支持两种匹配方式 -->
<!-- 1. 直接匹配distribution_category_code = category_code都是数字代码 -->
@@ -544,9 +541,9 @@
CI.service_id AS request_id,
CI.service_id || '-ci-dev' AS unique_key,
'' AS prescription_no,
CI.enterer_id AS requester_id,
COALESCE(DR.requester_id, CI.enterer_id) AS requester_id,
CI.entered_date AS request_time,
CASE WHEN CI.enterer_id = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
CASE WHEN COALESCE(DR.requester_id, CI.enterer_id) = #{practitionerId} THEN '1' ELSE '0' END AS biz_request_flag,
DR.content_json AS content_json,
NULL AS skin_test_flag,
NULL AS inject_flag,

View File

@@ -280,17 +280,9 @@
aa.balance_amount
) AS personal_account
ON personal_account.encounter_id = ae.id
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
LEFT JOIN med_medication_dispense mmd
ON mmd.med_req_id = T1.id
AND mmd.delete_flag = '0'
WHERE T1.delete_flag = '0'
AND T1.refund_medicine_id IS NULL
AND T1.generate_source_enum = #{doctorPrescription}

View File

@@ -12,13 +12,24 @@
drf.desc_json,
drf.requester_id,
drf.create_time,
drf.status,
ap.NAME AS patient_name
ap.NAME AS patient_name,
CASE MIN(wsr.status_enum)
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END AS status
FROM doc_request_form AS drf
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
AND ae.delete_flag = '0'
LEFT JOIN adm_patient AS ap ON ap.ID = ae.patient_id
AND ap.delete_flag = '0'
LEFT JOIN wor_service_request AS wsr ON wsr.prescription_no = drf.prescription_no
AND wsr.delete_flag = '0'
WHERE drf.delete_flag = '0'
AND drf.encounter_id = #{encounterId}
AND drf.type_code = #{typeCode}
@@ -29,8 +40,32 @@
AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
</if>
<if test="status != null and status != ''">
AND drf.status = #{status}::integer
AND CASE MIN(wsr.status_enum)
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END = #{status}::integer
</if>
<if test="keyword != null and keyword != ''">
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'
OR EXISTS (
SELECT 1 FROM wor_service_request wsr2
WHERE wsr2.prescription_no = drf.prescription_no
AND wsr2.delete_flag = '0'
AND wsr2.activity_id IN (
SELECT id FROM wor_activity_definition wad
WHERE wad.delete_flag = '0'
AND wad.name ILIKE '%' || #{keyword} || '%'
)
))
</if>
GROUP BY drf.id, drf.encounter_id, drf.prescription_no, drf.name, drf.desc_json,
drf.requester_id, drf.create_time, ap.name
</select>
<select id="getRequestFormDetail" resultType="com.openhis.web.regdoctorstation.dto.RequestFormDetailQueryDto">

View File

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

View File

@@ -187,6 +187,12 @@ public class OpSchedule extends HisBaseEntity {
/** 沟通信息 */
private String communicationInfo;
/** 是否外请专家 1-是 0-否 */
private Integer isExternalExpert;
/** 外请专家姓名 */
private String externalExpertName;
/** 备注信息 */
private String remark;

View File

@@ -475,8 +475,13 @@ function calculateTotalPrice() {
});
totalPrice.value = sum.toFixed(2);
// Bug #464: 零售价与诊疗子项合计总价实时同步
if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId !== '') {
const hasValidItem = treatmentItems.value.some(
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
form.value.retailPrice = parseFloat(totalPrice.value);
} else {
form.value.retailPrice = undefined;
}
} catch (error) {
totalPrice.value = '0.00';
@@ -486,7 +491,7 @@ function calculateTotalPrice() {
// 添加表单项
function addItem() {
treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, retailPrice: 0 });
treatmentItems.value.push({ adviceDefinitionId: '', childrenRequestNum: 1, name: '', retailPrice: 0 });
// 使用nextTick确保DOM更新后再计算
nextTick(() => {
calculateTotalPrice();
@@ -647,12 +652,15 @@ async function submitForm() {
form.value.ybMatchFlag ? (form.value.ybMatchFlag = 1) : (form.value.ybMatchFlag = 0);
form.value.ruleId ? (form.value.ruleId = 1) : (form.value.ruleId = 0);
form.value.childrenJson =
treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != ''
treatmentItems.value.some((item) => item.adviceDefinitionId != '' && item.adviceDefinitionId)
? JSON.stringify(treatmentItems.value)
: undefined;
// Bug #464 修复:零售价自动与诊疗子项合计总价同步
// 当有子项时,零售价自动设置为子项合计总价
if (treatmentItems.value.length > 0 && treatmentItems.value[0].adviceDefinitionId != '') {
const hasValidItem = treatmentItems.value.some(
(item) => item.adviceDefinitionId && item.adviceDefinitionId !== ''
);
if (hasValidItem) {
form.value.retailPrice = parseFloat(totalPrice.value) || 0;
}
proxy.$refs['diagnosisTreatmentRef'].validate(async (valid) => {

View File

@@ -69,13 +69,14 @@ const throttledGetList = throttle(
watch(
() => props.adviceQueryParams,
(newValue) => {
// 只有在弹窗打开时才响应 adviceQueryParams 的变化,避免选择项目后弹窗关闭时触发不必要的请求
if (!props.popoverVisible) {
return;
}
// 始终同步参数到 queryParams避免弹窗打开时使用旧参数
queryParams.value.searchKey = newValue?.searchKey;
queryParams.value.adviceType = newValue?.adviceType;
queryParams.value.categoryCode = newValue?.categoryCode;
// 只有在弹窗打开时才触发 API 请求
if (!props.popoverVisible) {
return;
}
throttledGetList();
},
{ deep: true }

View File

@@ -519,9 +519,10 @@
>
<template #append>
<!-- 审核记录查看/审核都展示保证留痕可追溯 -->
<!-- 修复查看模式始终显示区块审核模式始终显示区块header + 空状态 timeline -->
<div
class="audit-records-section"
v-if="drawerMode === 'view' || (drawerMode === 'audit' && auditRecords.length > 0)"
v-if="drawerMode === 'view' || drawerMode === 'audit'"
>
<h3 class="section-title">审核记录</h3>
<el-timeline v-if="auditRecords.length > 0">

View File

@@ -127,6 +127,7 @@ const queryParams = ref({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3',
categoryCode: '',
});
// 节流函数 - 与V1.3一致300ms首次立即响应
@@ -148,6 +149,7 @@ watch(
} else {
queryParams.value.adviceTypes = '1,2,3';
}
queryParams.value.categoryCode = newValue.categoryCode || '';
throttledGetList();
},
{ deep: true }
@@ -175,6 +177,12 @@ function getList() {
const types = adviceTypes.split(',').map(t => parseInt(t));
filteredData = filteredData.filter(item => types.includes(item.adviceType));
}
// 根据 categoryCode 过滤(如西药='2',中成药='1'
const categoryCode = queryParams.value.categoryCode;
if (categoryCode) {
filteredData = filteredData.filter(item => String(item.categoryCode) === String(categoryCode));
}
// 根据搜索关键词过滤
if (searchKey && searchKey.length >= 1) {

View File

@@ -375,7 +375,7 @@ const finishCallPatient = async () => {
return;
}
try {
await completeEncounter(currentCallPatient.value.encounterId);
await completeEncounter({ encounterId: currentCallPatient.value.encounterId, firstEnum: currentCallPatient.value.firstEnum || 1 });
emit('finish');
emit('update:dialogVisible', false);
ElMessage.success('患者已完诊');

View File

@@ -349,11 +349,17 @@ async function getList() {
if (res.code == 200) {
// 过滤掉中医诊断,只保留西医诊断
form.value.diagnosisList = res.data.filter(item => item.typeName !== '中医诊断');
// 为旧数据添加默认分类
// 为旧数据添加默认分类和selectedDiseases
form.value.diagnosisList.forEach(item => {
if (!item.classification) {
item.classification = '西医';
}
// 如果ybNo诊断编码符合传染病编码格式添加到selectedDiseases
if (item.ybNo && /^(01|02|03)/.test(item.ybNo)) {
item.selectedDiseases = [item.ybNo];
} else {
item.selectedDiseases = item.selectedDiseases || [];
}
});
emits('diagnosisSave', false);
}
@@ -685,23 +691,78 @@ async function handleFoodDiseasesCheck() {
/**
* 传染病报告卡处理
* 通过诊断目录维护的'报卡类型'字段自动识别是否有需要填写的传染病报告卡
* 如果有则弹出诊断对应需登记的报告卡界面
* 通过诊断名称自动识别并勾选传染病报告卡中的疾病
*/
function handleInfectiousDiseaseReport() {
// 查找所有有报卡类型的诊断reportTypeCode不为空
const diagnosesWithReportType = form.value.diagnosisList.filter(d => d.reportTypeCode);
// 疾病名称到报卡编码的映射(根据传染病报告卡弹窗中的疾病列表
const diseaseNameToCode = {
// 甲类
'鼠疫': '0101',
'霍乱': '0102',
// 乙类
'传染性非典型肺炎': '0201',
'艾滋病': '0202',
'病毒性肝炎': '0203',
'脊髓灰质炎': '0204',
'人感染高致病性禽流感': '0205',
'麻疹': '0206',
'流行性出血热': '0207',
'狂犬病': '0208',
'流行性乙型脑炎': '0209',
'登革热': '0210',
'炭疽': '0211',
'细菌性和阿米巴性痢疾': '0212',
'肺结核': '0213',
'伤寒和副伤寒': '0214',
'流行性脑脊髓膜炎': '0215',
'百日咳': '0216',
'白喉': '0217',
'新生儿破伤风': '0218',
'猩红热': '0219',
'布鲁氏菌病': '0220',
'淋病': '0221',
'梅毒': '0222',
'钩端螺旋体病': '0223',
'血吸虫病': '0224',
'疟疾': '0225',
'新型冠状病毒肺炎': '0226',
'甲型H1N1流感': '0227',
'人感染H7N9禽流感': '0228',
// 丙类
'流行性感冒': '0301',
'流行性腮腺炎': '0302',
'风疹': '0303',
'急性出血性结膜炎': '0304',
'麻风病': '0305',
'流行性和地方性斑疹伤寒': '0306',
'黑热病': '0307',
'包虫病': '0308',
'丝虫病': '0309',
'除霍乱/菌痢/伤寒副伤寒以外的感染性腹泻病': '0310',
'其它感染性腹泻病': '0310',
'手足口病': '0311',
};
if (diagnosesWithReportType.length === 0) {
// 获取所有诊断名称对应的报卡编码
const allSelectedDiseases = form.value.diagnosisList
.map(d => diseaseNameToCode[d.name] || null)
.filter(code => code);
if (allSelectedDiseases.length === 0) {
return;
}
// 优先使用主诊断,如果没有主诊断有报卡类型则使用第一个有报卡类型的诊断
const mainDiagnosisWithReport = diagnosesWithReportType.find(d => d.maindiseFlag === 1);
const targetDiagnosis = mainDiagnosisWithReport || diagnosesWithReportType[0];
// 优先使用主诊断
const mainDiagnosis = form.value.diagnosisList.find(d => d.maindiseFlag === 1);
const firstDiagnosis = form.value.diagnosisList[0];
const diagnosisToShow = {
...(mainDiagnosis || firstDiagnosis),
selectedDiseases: allSelectedDiseases
};
// 弹出传染病报告卡弹窗
proxy.$refs.infectiousDiseaseReportRef?.show(targetDiagnosis);
proxy.$refs.infectiousDiseaseReportRef?.show(diagnosisToShow);
}
/**
@@ -814,7 +875,8 @@ form.value.diagnosisList.push({
classification: '西医', // 默认为西医
onsetDate: getCurrentDate(),
diagnosisDoctor: props.patientInfo.practitionerName || props.patientInfo.doctorName || props.patientInfo.physicianName || userStore.name,
diagnosisTime: getCurrentDate()
diagnosisTime: getCurrentDate(),
selectedDiseases: data.ybNo ? [data.ybNo] : [], // 用于传染病报告卡自动勾选
});
// 添加后按排序号排序

View File

@@ -16,15 +16,15 @@
v-model="form.cardNo"
class="card-number-input"
placeholder="单位自编,与网络直报一致"
maxlength="12"
:disabled="readOnly"
maxlength="20"
:disabled="readOnly || dialogReadOnly"
/>
</el-space>
</el-card>
</template>
<el-card class="report-form" shadow="never">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="readOnly">
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="readOnly || dialogReadOnly">
<!-- 患者姓名家长姓名身份证号 -->
<el-row :gutter="16" class="form-row">
<el-col :span="8" class="form-item">
@@ -476,9 +476,9 @@
<template #footer>
<slot name="footer" :close="handleClose" :submit-loading="submitLoading">
<el-space :size="16" justify="center" class="dialog-footer-space" style="display: flex; justify-content: center; width: 100%;">
<el-button v-if="!readOnly" type="primary" @click="handleSubmit" :loading="submitLoading" class="blue-button">保 存</el-button>
<el-button v-if="!(readOnly || dialogReadOnly)" type="primary" @click="handleSubmit" :loading="submitLoading" class="blue-button">保 存</el-button>
<el-button type="info" @click="handleClose">关 闭</el-button>
<el-button v-if="!readOnly" type="danger" @click="handleReset"> </el-button>
<el-button v-if="!(readOnly || dialogReadOnly)" type="danger" @click="handleReset"> </el-button>
</el-space>
</slot>
</template>
@@ -510,6 +510,7 @@ const DISEASE_NAMES = {
};
const dialogVisible = ref(false);
const dialogReadOnly = ref(false);
const formRef = ref(null);
// 保存按钮加载状态,防止重复提交
const submitLoading = ref(false);
@@ -1037,8 +1038,9 @@ function resetAddressSelector() {
* 以只读详情方式打开报卡弹窗,供报卡管理等页面复用医生站报卡样式。
* @param {Object} reportData - 报卡详情数据
*/
function showReport(reportData = {}) {
function showReport(reportData = {}, readOnly = true) {
dialogVisible.value = true;
dialogReadOnly.value = readOnly;
resetAddressSelector();
initProvinceOptions();
@@ -1069,7 +1071,7 @@ function showReport(reportData = {}) {
addressHouse: reportData.addressHouse || '',
patientBelong: reportData.patientBelong || 1,
occupation: reportData.occupation || '',
caseClass: reportData.caseClass != null ? String(reportData.caseClass) : '',
caseClass: reportData.diseaseType != null ? String(reportData.diseaseType) : '',
onsetDate: normalizeDate(reportData.onsetDate),
diagDate: normalizeDate(reportData.diagDate),
deathDate: normalizeDate(reportData.deathDate),
@@ -1078,13 +1080,13 @@ function showReport(reportData = {}) {
selectedClassB: diseaseSelection.selectedClassB,
selectedClassC: diseaseSelection.selectedClassC,
otherDisease: reportData.otherDisease || (diseaseCode === 'OTHER' ? reportData.diseaseName || '' : ''),
diseaseType: reportData.diseaseType || '',
diseaseType: reportData.diseaseSubtype || '',
reportOrg: reportData.reportOrg || '',
reportOrgPhone: reportData.reportOrgPhone || '',
reportDoc: reportData.reportDoc || '',
reportDate: normalizeDate(reportData.reportDate || reportData.createdAt),
correctName: reportData.correctName || '',
withdrawReason: reportData.withdrawReason || '',
correctName: reportData.revisedDiseaseName || '',
withdrawReason: reportData.returnReason || '',
remark: reportData.remark || '',
encounterId: reportData.encounterId || reportData.visitId || '',
patientId: reportData.patientId || reportData.patId || '',
@@ -1205,6 +1207,7 @@ function calculateAge() {
*/
async function show(diagnosisData) {
dialogVisible.value = true;
dialogReadOnly.value = false;
// 重置地址选择器状态
resetAddressSelector();
@@ -1238,14 +1241,13 @@ async function show(diagnosisData) {
let cardNo = '';
try {
const res = await getNextCardNo(orgCode);
if (res.code === 200 && res.data) {
if (res.code === 200 && res.data && res.data.length >= 12) {
cardNo = res.data;
} else {
cardNo = 'TEMP_' + Date.now();
}
// API失败或返回不合规时保持为空字符串由用户手动填写或后端自动生成
} catch (err) {
console.error('获取卡片编号失败:', err);
cardNo = 'TEMP_' + Date.now();
// 保持为空,不使用不合规的临时值
}
form.value = {
@@ -1424,9 +1426,9 @@ async function buildSubmitData() {
function validateFormManually() {
const errors = [];
// 卡片编号验证(可选但如果填写了必须是12位
if (form.value.cardNo && form.value.cardNo.length !== 12) {
errors.push('卡片编号必须为12位');
// 卡片编号验证(至少12位后端自动生成16位编号
if (form.value.cardNo && form.value.cardNo.length < 12) {
errors.push('卡片编号至少12位');
}
// 身份证号验证

View File

@@ -316,6 +316,7 @@
<span class="cat-title">{{ cat.categoryName }}</span>
<span v-if="categoryLoadingSet.has(cat.typeId)" class="loading-dot"></span>
</template>
<!-- 检查项目部位/项目列表 -->
<div
v-for="item in cat.items"
:key="item.id"
@@ -333,6 +334,27 @@
<div v-if="categoryLoadingSet.has(cat.typeId)" class="category-loading-hint">
加载中...
</div>
<!-- Bug #428修复: 渲染分类联动加载的检查方法列表 -->
<div
v-if="cat.methods && cat.methods.length > 0"
class="method-section"
>
<div class="method-section-title">检查方法</div>
<div
v-for="method in cat.methods"
:key="method.id"
class="method-row"
>
<el-checkbox
:model-value="isMethodSelected(method, cat)"
@change="(val) => handleMethodSelect(val, method, cat)"
class="method-checkbox"
>
{{ method.name }}
</el-checkbox>
<span class="method-price-tag">¥{{ method.packagePrice || method.price || 0 }}</span>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
@@ -918,6 +940,13 @@ function handleSave() {
ElMessage.warning('请至少选择一个检查明细项目');
return;
}
// 检查每个项目是否已选择检查方法
const itemsWithoutMethod = selectedItems.value.filter(item => !item.selectedMethod);
if (itemsWithoutMethod.length > 0) {
const names = itemsWithoutMethod.map(item => item.name).join('、');
ElMessage.warning(`以下项目未选择检查方法:${names},请在右侧勾选后再保存`);
return;
}
// 从已选项目推导检查类型编码(取第一个项目的 checkType如 CT / ECG / GI
const firstCheckType = selectedItems.value[0]?.checkType || 'unknown';
form.examTypeCode = firstCheckType;
@@ -1040,6 +1069,78 @@ function handleDelete(row) {
});
}
// Bug #428修复: 判断某个检查方法是否已被选中(任意项目关联了该方法)
function isMethodSelected(method, cat) {
return selectedItems.value.some(item =>
item.selectedMethod?.id === method.id && item.checkType === cat.typeName
);
}
// Bug #428修复: 勾选检查方法
async function handleMethodSelect(checked, method, cat) {
if (checked) {
// 找到该方法所属的第一个检查项目
const targetItem = cat.items[0];
if (!targetItem) {
// 如果分类下没有项目,尝试从其他分类找同名项目或创建
console.warn('分类下没有检查项目,无法关联方法');
return;
}
// 如果该项目已存在,只更新 selectedMethod
const existingItem = selectedItems.value.find(s => s.id === targetItem.id);
if (existingItem) {
existingItem.selectedMethod = method;
updateMethodDisplay();
return;
}
// 如果该项目不存在,创建一个并关联方法
if (selectedItems.value.length > 0) {
const currentCategory = selectedItems.value[0].checkType;
const newCategory = cat.typeCode || '';
if (currentCategory !== newCategory) {
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
return;
}
}
selectedItems.value.push({
id: targetItem.id, name: targetItem.name,
price: targetItem.price, quantity: 1,
serviceFee: targetItem.serviceFee || 0,
unit: targetItem.unit || '次',
applyPart: targetItem.name,
checkType: cat.typeName,
nationalCode: targetItem.nationalCode || '',
checked: true,
methods: [method],
selectedMethod: method,
expanded: false,
isPackage: !!targetItem.packageName,
packageId: targetItem.packageId || null
});
// 自动回填执行科室
if (selectedItems.value.length === 1 && cat?.performDeptName) {
form.performDeptCode = cat.performDeptName;
}
// 同时勾选左侧项目的 checkbox
targetItem.checked = true;
} else {
// 取消选择方法:将 selectedItems 中关联该方法的项的 selectedMethod 清空
const itemsWithMethod = selectedItems.value.filter(
item => item.selectedMethod?.id === method.id
);
for (const item of itemsWithMethod) {
item.selectedMethod = null;
}
}
updateMethodDisplay();
}
// ====== 勾选逻辑 ======
async function handleItemSelect(checked, item, cat) {
if (checked) {
@@ -1105,12 +1206,6 @@ async function handleItemSelect(checked, item, cat) {
form.performDeptCode = cat.performDeptName;
}
// 如果有且仅有一个检查方法,自动选中并更新显示
if (methods.length === 1) {
const lastIdx = selectedItems.value.length - 1;
selectedItems.value[lastIdx].selectedMethod = methods[0];
updateMethodDisplay(); // Bug #384修复: 联动更新显示
}
} else {
const idx = selectedItems.value.findIndex(s => s.id === item.id);
if (idx > -1) selectedItems.value.splice(idx, 1);
@@ -1366,6 +1461,53 @@ defineExpose({ getList });
margin-left: 6px;
}
/* Bug #428修复: 分类下检查方法区域样式 */
.method-section {
padding: 6px 8px;
background: #f0f7ff;
border-radius: 4px;
margin-top: 6px;
}
.method-section-title {
font-size: 12px;
font-weight: 600;
color: #409eff;
margin-bottom: 4px;
padding-bottom: 3px;
border-bottom: 1px dashed #d9ecff;
}
.method-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px 4px;
border-radius: 3px;
}
.method-row:hover {
background: #e8f4ff;
}
.method-checkbox {
flex: 1;
overflow: hidden;
}
.method-checkbox :deep(.el-checkbox__label) {
font-size: 12px;
color: #303133;
}
.method-price-tag {
font-size: 11px;
color: #e6a23c;
font-weight: 500;
flex-shrink: 0;
margin-left: 6px;
}
/* 已选择 tags */
.selected-panel {
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
@@ -1437,6 +1579,41 @@ defineExpose({ getList });
transform: rotate(180deg);
}
/* Bug #428修复: 套餐明细列表样式 */
.package-details-list {
padding: 6px 10px;
background: #fffbe6;
border-top: 1px solid #ffe58f;
display: flex;
flex-direction: column;
gap: 4px;
}
.detail-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
background: #fff;
}
.detail-row:hover {
background: #fff9e6;
}
.detail-name {
color: #303133;
font-weight: 500;
}
.detail-info {
color: #909399;
font-size: 10px;
white-space: nowrap;
}
/* Bug #384修复: 检查方法勾选框列表 */
.method-list {
padding: 6px 10px;

View File

@@ -109,6 +109,7 @@ const props = defineProps({
});
const { proxy } = getCurrentInstance();
const encounterId = ref();
const firstEnum = ref(1); // 初复诊标识1=初诊2=复诊
onMounted(() => {
getPatientList();
});
@@ -127,6 +128,7 @@ function getPatientList() {
function clickRow(row) {
encounterId.value = row.encounterId;
firstEnum.value = row.firstEnum ?? row.first_enum ?? 1;
emits('cellClick', row);
}
@@ -182,7 +184,7 @@ function handleComplete() {
}
proxy.$modal.confirm('是否完成该患者问诊?').then(() => {
proxy.$modal.loading();
completeEncounter(encounterId.value).then(() => {
completeEncounter({ encounterId: encounterId.value, firstEnum: firstEnum.value }).then(() => {
proxy.$modal.closeLoading();
proxy.$modal.msgSuccess('完成问诊成功');
getPatientList();

View File

@@ -1013,15 +1013,29 @@ const mapAdviceTypeLabel = (type, adviceTableName) => {
if (type === 2 && adviceTableName === 'adm_device_definition') {
return '耗材';
}
// 🔧 Bug Fix: 处理检查类型(adviceType=23)
// 检查类型属于诊疗类,应该显示为"检查"
if (type === 23) {
return '检查';
}
const found = adviceTypeList.value.find((item) => item.value === type);
return found ? found.label : '';
if (found) {
return found.label;
}
// 🔧 Bug #458 Fix: 诊疗/手术类型字典缺失时的兜底,避免保存后"医嘱类型"列显示为空
if (adviceTableName === 'wor_activity_definition' || adviceTableName === 'wor_service_request') {
if (type === 6) return '手术';
if (type === 4) return '手术';
if (type === 1) return '检验';
if (type === 2) return '检查';
if (type === 5) return '其他';
return '诊疗';
}
return '';
};
// 西药处方管理相关变量
@@ -2495,11 +2509,13 @@ function handleSave(prescriptionId) {
// 🔧 BugFix#318: 从 parsedContent 提取标准医嘱字段,排除手术特有字段
const standardFields = [
'accountId', 'chargeItemId', 'conditionDefinitionId', 'conditionId',
'contentJson', 'definitionDetailId', 'definitionId', 'diagnosisName',
'dosageInstruction', 'effectiveOrgId', 'encounterDiagnosisId',
'encounterId', 'lotNumber', 'patientId', 'practitionerId',
'prescriptionNo', 'skinTestFlag', 'unitPrice', 'volume', 'ybClassEnum'
'accountId', 'chargeItemId', 'conditionDefinitionId', 'conditionId',
'contentJson', 'definitionDetailId', 'definitionId', 'diagnosisName',
'dosageInstruction', 'effectiveOrgId', 'encounterDiagnosisId',
'encounterId', 'lotNumber', 'patientId', 'practitionerId',
'prescriptionNo', 'skinTestFlag', 'unitPrice', 'volume', 'ybClassEnum',
// 🔧 Bug Fix: 添加 therapyEnum 字段医嘱类型1=长期, 2=临时)
'therapyEnum'
];
let filteredContent = {};
standardFields.forEach(field => {
@@ -3143,7 +3159,9 @@ function handleSaveBatch(prescriptionId) {
// 🔧 Bug Fix: 添加 definitionId 和 definitionDetailId 字段
'definitionId', 'definitionDetailId',
// 🔧 Bug Fix: 添加 categoryEnum 字段(耗材必填)
'categoryEnum'
'categoryEnum',
// 🔧 Bug Fix: 添加 therapyEnum 字段医嘱类型1=长期, 2=临时)
'therapyEnum'
];
let filteredItem = {};
standardItemFields.forEach(field => {
@@ -3342,7 +3360,7 @@ function syncGroupFields(row) {
}
}
function setValue(row) {
async function setValue(row) {
unitCodeList.value = [];
unitCodeList.value.push({ value: row.unitCode, label: row.unitCode_dictText, type: 'unit' });
@@ -3394,8 +3412,8 @@ function setValue(row) {
showPopover: false, // 确保查询框关闭
};
console.log('[BugFix] setValue - prescriptionList[rowIndex].adviceType_dictText:', prescriptionList.value[rowIndex.value].adviceType_dictText);
// 🔧 修复执行科室逻辑:优先使用项目维护的所属科室(row.orgId)其次使用positionId最后回退到患者科室
prescriptionList.value[rowIndex.value].orgId = row.orgId || row.positionId || props.patientInfo?.orgId;
// 🔧 Bug #455: 执行科室默认逻辑:使用positionId(诊疗执行科室配置) → 患者就诊科室不再使用row.orgId(项目所属科室)
prescriptionList.value[rowIndex.value].orgId = row.positionId || props.patientInfo?.orgId;
prescriptionList.value[rowIndex.value].dose = row.dose || row.doseQuantity;
prescriptionList.value[rowIndex.value].quantity = row.quantity || 1;
prescriptionList.value[rowIndex.value].unitCodeList = unitCodeList.value;
@@ -3514,7 +3532,7 @@ function setValue(row) {
prescriptionList.value[rowIndex.value].positionId = finalLocationId;
}
} else {
getOrgList();
await getOrgList();
// 会诊类型adviceType == 5和诊疗类型adviceType == 3的处理
if (row.adviceType == 5) {
// 会诊类型:设置默认值

View File

@@ -24,7 +24,7 @@
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<!-- 手术单号 -->
<el-table-column label="手术单号" align="center" width="150">
<template #default="scope">
@@ -33,29 +33,31 @@
</el-link>
</template>
</el-table-column>
<!-- 申请日期 -->
<el-table-column label="申请日期" align="center" prop="createTime" width="180">
<template #default="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template>
</el-table-column>
<!-- 患者姓名 -->
<el-table-column label="患者姓名" align="center" prop="patientName" width="100" />
<!-- 申请医生 -->
<el-table-column label="申请医生" align="center" prop="applyDoctorName" width="100" />
<!-- 申请科室 -->
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip />
<!-- 手术名称 -->
<el-table-column label="手术名称" align="center" prop="surgeryName" min-width="150" show-overflow-tooltip />
<!-- 手术等级 -->
<el-table-column label="手术等级" align="center" prop="surgeryLevel_dictText" width="100" />
<!-- 手术室确认时间 -->
<el-table-column label="手术室确认时间" align="center" prop="operatingRoomConfirmTime" width="180">
<template #default="scope">
{{ scope.row.operatingRoomConfirmTime ? parseTime(scope.row.operatingRoomConfirmTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}
@@ -73,16 +75,16 @@
</el-tag>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<!-- 查看显示手术申请详情只读模式 -->
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button>
<!-- 编辑修改手术申请信息只有状态为新开的能修改 -->
<el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)" v-if="scope.row.statusEnum === 0">编辑</el-button>
<!-- 删除取消手术申请作废 -->
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.statusEnum === 0 || scope.row.statusEnum === 1">删除</el-button>
</template>
@@ -509,6 +511,10 @@ const props = defineProps({
})
const loading = ref(true)
const surgeryLoading = ref(false)
const anesthesiaLoading = ref(false)
let surgerySearchTimer = null
let anesthesiaSearchTimer = null
const surgeryList = ref([])
const open = ref(false)
const viewOpen = ref(false)
@@ -1127,12 +1133,13 @@ function submitForm() {
// 新增手术
addSurgery(form.value).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '新增成功')
proxy.$modal.msgSuccess(res.msg || '手术申请提交成功')
// 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else {
proxy.$modal.msgError(res.msg || '新增手术失败,请检查表单信息')
}
@@ -1144,12 +1151,13 @@ function submitForm() {
// 修改手术
updateSurgery(form.value).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess(res.msg || '修改成功')
proxy.$modal.msgSuccess(res.msg || '手术申请修改成功')
// 保存麻醉方式
sessionStorage.setItem('anesthesiaType', form.value.anesthesiaTypeEnum)
open.value = false
emit('saved') // 通知父组件刷新医嘱列表
// 刷新手术申请列表
getList()
emit('saved') // 🔧 触发保存事件,通知父组件刷新医嘱列表
} else {
proxy.$modal.msgError(res.msg || '更新手术失败,请检查表单信息')
}

View File

@@ -151,23 +151,12 @@
</div>
</div>
<el-dialog
v-model="detailVisible"
:title="detailMode === 'view' ? '报卡详情 - 中华人民共和国传染病报告卡' : '编辑报卡 - 中华人民共和国传染病报告卡'"
width="1100px"
destroy-on-close
class="card-detail-dialog"
>
<InfectiousReport
:mode=" detailMode"
:card-data="currentCard"
@submit-edit="handleSaveEdit"
style="max-height: 75vh; overflow-y: auto;"
/>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-dialog>
<InfectiousDiseaseReportDialog
ref="infectiousDiseaseReportRef"
:read-only="detailMode === 'view'"
@success="detailVisible = false"
@close="detailVisible = false"
/>
</div>
</template>
@@ -175,7 +164,7 @@
import { ref, reactive, onMounted, onActivated, onBeforeUnmount, nextTick, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { DataAnalysis, Warning, CircleCheck, Check, RefreshRight } from '@element-plus/icons-vue';
import InfectiousReport from '../components/infectiousReport/index.vue';
import InfectiousDiseaseReportDialog from '../components/diagnosis/infectiousDiseaseReportDialog.vue';
import {
getDoctorCardStatistics,
getDoctorCardList,
@@ -185,7 +174,6 @@ import {
batchDeleteCards,
exportCardToWord,
getCardDetail,
updateDoctorCard,
} from './api';
const loading = ref(false);
@@ -211,7 +199,7 @@ const queryParams = reactive({
const detailVisible = ref(false);
const detailMode = ref('view');
const currentCard = ref({});
const infectiousDiseaseReportRef = ref(null);
// 计算表格高度:根据视口高度动态调整
const tableHeight = computed(() => {
@@ -327,9 +315,11 @@ async function handleView(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'view';
detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {});
});
}
} catch (error) {
ElMessage.error('获取详情失败');
@@ -340,57 +330,17 @@ async function handleEdit(row) {
try {
const res = await getCardDetail(row.cardNo);
if (res.code === 200) {
currentCard.value = res.data || {};
detailMode.value = 'edit';
detailVisible.value = true;
nextTick(() => {
infectiousDiseaseReportRef.value?.showReport(res.data || {}, false);
});
}
} catch (error) {
ElMessage.error('获取详情失败');
}
}
async function handleSaveEdit(submitData) {
// submitData 来自 InfectiousReport 组件的 submit-edit 事件
try {
const updateData = {
cardNo: submitData.cardNo,
phone: submitData.phone,
contactPhone: submitData.contactPhone,
onsetDate: submitData.onsetDate,
diagDate: submitData.diagDate,
diseaseCode: submitData.diseaseCode,
diseaseType: submitData.diseaseType,
otherDisease: submitData.otherDisease,
caseClass: submitData.caseClass,
occupation: submitData.occupation,
patientBelong: submitData.patientBelong,
addressProv: submitData.addressProv,
addressCity: submitData.addressCity,
addressCounty: submitData.addressCounty,
addressTown: submitData.addressTown,
addressVillage: submitData.addressVillage,
addressHouse: submitData.addressHouse,
workplace: submitData.workplace,
parentName: submitData.parentName,
deathDate: submitData.deathDate,
correctName: submitData.correctName,
withdrawReason: submitData.withdrawReason,
remark: submitData.remark,
};
const res = await updateDoctorCard(updateData);
if (res.code === 200) {
ElMessage.success('保存成功');
detailVisible.value = false;
getList();
} else {
ElMessage.error(res.msg || '保存失败');
}
} catch (error) {
ElMessage.error('保存失败:' + (error.message || '网络错误'));
}
}
async function handleSubmit(row) {
try {
await ElMessageBox.confirm('确认提交该报卡?', '提示', {
@@ -800,17 +750,4 @@ function handleResize() {
margin-left: 0;
}
}
/* 报卡详情弹窗 */
:deep(.card-detail-dialog .el-dialog__body) {
padding: 0;
overflow: hidden;
}
:deep(.card-detail-dialog .infectious-report-container) {
padding: 16px;
height: auto;
max-height: 70vh;
overflow-y: auto;
}
</style>

View File

@@ -168,7 +168,7 @@ import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getCheck} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance();
@@ -293,8 +293,8 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
});
};
@@ -306,17 +306,19 @@ const recursionFun = (targetDepartment) => {
name = obj.name;
}
const subObjArray = obj['children'];
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
if (subObjArray && subObjArray.length > 0) {
for (let index = 0; index < subObjArray.length; index++) {
const item = subObjArray[index];
if (item.id == targetDepartment) {
name = item.name;
}
}
}
}
return name;
};
const handleViewDetail = (row) => {
const handleViewDetail = async (row) => {
console.log('targetDepartment========>', JSON.stringify(row));
currentDetail.value = row;
@@ -324,6 +326,15 @@ const handleViewDetail = (row) => {
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
// 确保科室数据已加载
if (!orgOptions.value || orgOptions.value.length === 0) {
await new Promise((resolve) => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
resolve();
});
});
}
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
} catch (e) {

View File

@@ -41,8 +41,6 @@
<el-option label="全部" value="" />
<el-option label="待签发" value="0" />
<el-option label="已签发" value="1" />
<el-option label="已采集" value="2" />
<el-option label="已收样" value="3" />
<el-option label="报告已出" value="4" />
<el-option label="已作废" value="5" />
</el-select>
@@ -103,8 +101,15 @@
</template>
</el-table-column>
<el-table-column prop="requesterId_dictText" label="申请者" width="120" />
<el-table-column label="操作" align="center" fixed="right">
<el-table-column label="操作" align="center" fixed="right" width="160">
<template #default="scope">
<template v-if="scope.row.billStatus == 0 || scope.row.status == 0">
<el-button link type="primary" @click="handleEdit(scope.row)">修改</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
<template v-else-if="scope.row.billStatus == 1 || scope.row.status == 1">
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
</template>
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
@@ -181,8 +186,8 @@
import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getInspection} from './api';
import {getDepartmentList} from '@/api/public.js';
import {getInspection, deleteRequestForm, withdrawRequestForm} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
@@ -291,8 +296,6 @@ const parseBillStatus = (status) => {
const statusMap = {
'0': '待签发',
'1': '已签发',
'2': '已采集',
'3': '已收样',
'4': '报告已出',
'5': '已作废',
};
@@ -353,11 +356,13 @@ const getLocationInfo = async () => {
};
const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
break;
}
const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) {
@@ -365,9 +370,11 @@ const recursionFun = (targetDepartment) => {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
break;
}
}
}
if (name) break;
}
return name;
};
@@ -385,7 +392,6 @@ const handleViewDetail = async (row) => {
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
descJsonData.value = obj;
// descJsonData.value = JSON.parse(row.descJson);
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
@@ -396,6 +402,61 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true;
};
/**
* 修改检验申请单(待签发状态)
*/
const handleEdit = (row) => {
// 复用详情查看逻辑,后续可扩展为打开编辑弹窗
handleViewDetail(row);
proxy.$modal?.msgInfo?.('修改功能待接入,请通过详情弹窗查看后重新开立');
};
/**
* 删除检验申请单(仅待签发状态可删除)
*/
const handleDelete = async (row) => {
try {
await proxy.$modal?.confirm?.(`确定要删除申请单 "${row.prescriptionNo}" 吗?此操作不可恢复。`);
} catch {
return; // 用户取消
}
try {
const res = await deleteRequestForm({ prescriptionNo: row.prescriptionNo });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('删除成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '删除失败');
}
} catch (e) {
proxy.$modal?.msgError?.(e.message || '删除异常');
}
};
/**
* 撤回检验申请单(已签发状态撤回至待签发)
*/
const handleWithdraw = async (row) => {
try {
await proxy.$modal?.confirm?.(`确定要撤回申请单 "${row.prescriptionNo}" 吗?撤回后将恢复为待签发状态。`);
} catch {
return; // 用户取消
}
try {
const res = await withdrawRequestForm({ prescriptionNo: row.prescriptionNo });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('撤回成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
}
} catch (e) {
proxy.$modal?.msgError?.(e.message || '撤回异常');
}
};
watch(
() => patientInfo.value?.encounterId,
async (val) => {

View File

@@ -112,6 +112,8 @@ const showApplicationFormDialog = (name) => {
setTimeout(() => {
applicationFormName.value = components.value[name];
applicationFormDialogVisible.value = true;
// 列表(项目列表)
applicationFormNameRef?.value.getList?.();
// 科室列表
applicationFormNameRef?.value.getLocationInfo();
// 诊断目录列表
@@ -121,6 +123,8 @@ const showApplicationFormDialog = (name) => {
applicationFormName.value = components.value[name];
applicationFormDialogVisible.value = true;
nextTick(() => {
// 列表(项目列表)
applicationFormNameRef?.value.getList?.();
// 科室列表
applicationFormNameRef?.value.getLocationInfo();
// 诊断目录列表

View File

@@ -81,7 +81,7 @@
import {getCurrentInstance, onBeforeMount, onMounted, reactive, watch} from 'vue';
import {patientInfo} from '../../../store/patient.js';
import {getApplicationList, saveInspection} from './api';
import {getDepartmentList} from '@/api/public.js';
import {getOrgList} from '@/views/doctorstation/components/api.js';
import {getEncounterDiagnosis} from '../../api.js';
import {ElMessage} from 'element-plus';
@@ -208,10 +208,8 @@ const projectWithDepartment = (selectProjectIds, type) => {
});
}
}
if (type == 1) {
if (isRelease) {
form.targetDepartment = findItem.id;
}
if (findItem && isRelease) {
form.targetDepartment = findItem.id;
}
}
return isRelease;
@@ -272,8 +270,8 @@ const submit = () => {
};
/** 查询科室 */
const getLocationInfo = () => {
getDepartmentList().then((res) => {
orgOptions.value = res.data || [];
getOrgList().then((res) => {
orgOptions.value = res.data.records;
console.log('科室========>', JSON.stringify(orgOptions.value));
});
};
@@ -310,7 +308,7 @@ function getDiagnosisList() {
}
});
}
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
</script>
<style lang="scss" scoped>
.LaboratoryTests-container {

View File

@@ -6,13 +6,15 @@
<template>
<div class="surgery-container">
<div class="transfer-wrapper">
<el-transfer
v-model="transferValue"
:data="applicationList"
filter-placeholder="项目代码/名称"
filterable
:titles="['未选择', '已选择']"
/>
<div v-loading="loading" style="min-height: 300px;">
<el-transfer
v-model="transferValue"
:data="applicationList"
filter-placeholder="项目代码/名称"
filterable
:titles="['未选择', '已选择']"
/>
</div>
</div>
<div class="bloodTransfusion-form">
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" class="demo-ruleForm">
@@ -104,11 +106,13 @@ const state = reactive({});
const applicationListAll = ref([]);
const applicationList = ref([]);
const orgOptions = ref([]); // 科室选项
const loading = ref(false); // 加载状态
const getList = () => {
if (!patientInfo.value?.inHospitalOrgId) {
applicationList.value = [];
return;
}
loading.value = true;
getApplicationList({
pageSize: 500,
pageNum: 1,
@@ -138,6 +142,9 @@ const getList = () => {
.catch((e) => {
console.warn('手术项目列表加载失败(可能无权限):', e?.message || e);
applicationList.value = [];
})
.finally(() => {
loading.value = false;
});
};
const transferValue = ref([]);
@@ -301,7 +308,7 @@ function getDiagnosisList() {
}
});
}
defineExpose({ state, submit, getLocationInfo, getDiagnosisList });
defineExpose({ state, submit, getLocationInfo, getDiagnosisList, getList });
</script>
<style lang="scss" scoped>
.surgery-container {

View File

@@ -801,8 +801,8 @@ function clickRowDb(row, column, event) {
return;
}
row.showPopover = false;
// 待签发(已保存 requestId存在)”允许编辑;仅“待保存(无requestId)”允许编辑
if (row.statusEnum == 1 && !row.requestId) {
// 仅”待签发(statusEnum==1)”允许编辑;”已签发(statusEnum==2)”及之后状态不允许编辑
if (row.statusEnum == 1) {
// 确保治疗类型为字符串,方便与单选框 label 对齐,默认为长期医嘱('1')
row.therapyEnum = String(row.therapyEnum ?? '1');
row.isEdit = true;

View File

@@ -11,9 +11,9 @@
>
<div>
<el-radio-group v-model="type" class="ml20" @change="handleRadioChange">
<el-radio :label="1">全部</el-radio>
<el-radio :label="2">长期</el-radio>
<el-radio :label="3">临时</el-radio>
<el-radio :label="null">全部</el-radio>
<el-radio :label="1">长期</el-radio>
<el-radio :label="2">临时</el-radio>
</el-radio-group>
<el-button class="ml20" type="primary" plain @click="handleGetPrescription">
查询
@@ -159,7 +159,7 @@ import {formatDateStr} from '@/utils/index';
const activeNames = ref([]);
const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const type = ref(1);
const type = ref(null);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const chooseAll = ref(false);
@@ -181,7 +181,7 @@ function handleGetPrescription() {
getPrescriptionList({
encounterIds: encounterIds,
requestStatus: props.requestStatus,
...(type.value !== undefined ? { therapyEnum: type.value } : {}),
...(type.value != null ? { therapyEnum: type.value } : {}),
pageSize: 10000,
pageNo: 1,
}).then((res) => {

View File

@@ -744,11 +744,7 @@ function handleSubmitApproval() {
} else {
submitApproval(receiptHeaderForm.busNo).then((response) => {
proxy.$modal.msgSuccess('提交审批成功');
// proxy.$tab.closePage(route).then(({ visitedViews }) => { // 关闭当前页
// toLastView(visitedViews, route)
// })
tagsViewStore.delView(router.currentRoute.value);
// 跳转到调拨管理页面
router.replace({ path: 'transferManagentList' });
});
}
@@ -871,7 +867,6 @@ function handleUnitCodeChange(row, index, value) {
}
function handleItemQuantityChange(row, value) {
debugger;
let quantityTemp = ''; // 转换成小单位的临时变量 做校验
// 大单位情况
if (row.unitCodeMap[row.unitCode] == 'unit') {
@@ -882,12 +877,6 @@ function handleItemQuantityChange(row, value) {
row.itemQuantity = value;
quantityTemp = value;
}
if (row.totalSourceQuantity < quantityTemp) {
proxy.$modal.msgWarning('调拨数量不可超出源库存数量');
row.itemQuantity = 0;
row.itemQuantityDisplay = 0;
return;
}
row.totalPrice = ((row.price * quantityTemp) / row.partPercent).toFixed(2);
// 更新总数据中对应的记录
const totalIndex = totalIncentoryInfoList.value.findIndex((item) => item.id === row.id);
@@ -918,11 +907,12 @@ function remakeBlur(row, index) {
editBatchTransfer(index);
}
function handleSave() {
const invalidRow = totalIncentoryInfoList.value.find(
// 校验单价
const invalidPriceRow = totalIncentoryInfoList.value.find(
(row) => !row.price || row.price <= 0
);
if (invalidRow) {
proxy.$message.warning('调拨单价不能为空或为0请检查');
if (invalidPriceRow) {
proxy.$modal.msgError('调拨单价不能为空或为0请检查');
return;
}
addTransferProductBatch(totalIncentoryInfoList.value).then((res) => {

View File

@@ -970,12 +970,6 @@ function handleItemQuantityChange(row, value) {
row.itemQuantity = value;
quantityTemp = value;
}
if (row.totalSourceQuantity < quantityTemp) {
proxy.$modal.msgWarning('调拨数量不可超出源库存数量');
row.itemQuantity = 0;
row.itemQuantityDisplay = 0;
return;
}
row.totalPrice = ((row.price * quantityTemp) / row.partPercent).toFixed(2);
}
@@ -1022,11 +1016,7 @@ function handleSubmitApproval() {
} else {
submitApproval(receiptHeaderForm.busNo).then((response) => {
proxy.$modal.msgSuccess('提交审批成功');
// proxy.$tab.closePage(route).then(({ visitedViews }) => { // 关闭当前页
// toLastView(visitedViews, route)
// })
tagsViewStore.delView(router.currentRoute.value);
// 跳转到调拨管理页面
router.replace({ path: 'transferManagentList' });
store.clearCurrentDataDB();
});

View File

@@ -829,7 +829,7 @@
</el-descriptions>
</div>
<div style="padding: 10px">
<prescriptionlist :patientInfo="chargePatientInfo" ref="prescriptionRef" />
<prescriptionlist v-if="showChargeDialog" :patientInfo="chargePatientInfo" ref="prescriptionRef" />
<div class="overlay" v-if="disabled"></div>
</div>
</div>