From 1616f66fc4410f8b272ccd74c73360cfc740904d Mon Sep 17 00:00:00 2001 From: nanyangbreeze <1955231298@qq.com> Date: Mon, 26 Jan 2026 11:59:13 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=8A=E7=96=97=E4=B8=8B=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=8A=9F=E8=83=BD=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sql/diagnose_treatment_items_issue.sql | 3 + .../sql/快速诊断诊疗项目问题.sql | 91 ------ .../sql/诊断门诊划价检索不出诊疗项目问题.md | 170 ---------- .../bargain/component/adviceBaseList.vue | 2 +- .../components/adviceBaseList.vue | 33 +- 排查指南-字段查询问题.md | 299 ------------------ 6 files changed, 22 insertions(+), 576 deletions(-) delete mode 100644 openhis-server-new/sql/快速诊断诊疗项目问题.sql delete mode 100644 openhis-server-new/sql/诊断门诊划价检索不出诊疗项目问题.md delete mode 100644 排查指南-字段查询问题.md diff --git a/openhis-server-new/sql/diagnose_treatment_items_issue.sql b/openhis-server-new/sql/diagnose_treatment_items_issue.sql index 57914812..135aa5d5 100644 --- a/openhis-server-new/sql/diagnose_treatment_items_issue.sql +++ b/openhis-server-new/sql/diagnose_treatment_items_issue.sql @@ -96,4 +96,7 @@ WHERE aci.context_enum = 'ACTIVITY' + + + diff --git a/openhis-server-new/sql/快速诊断诊疗项目问题.sql b/openhis-server-new/sql/快速诊断诊疗项目问题.sql deleted file mode 100644 index 6e8e3ff8..00000000 --- a/openhis-server-new/sql/快速诊断诊疗项目问题.sql +++ /dev/null @@ -1,91 +0,0 @@ --- 快速诊断诊疗项目检索问题 --- 已知:wor_activity_definition 表中有4条数据 --- 关键:ChargeItemContext.ACTIVITY.getValue() = 3(整数) - --- 步骤1:检查这4条诊疗项目定义的delete_flag状态 -SELECT - id, - name, - delete_flag, - CASE WHEN delete_flag = '0' THEN '✅ 可用' ELSE '❌ 已删除' END as status -FROM wor_activity_definition -ORDER BY id; - --- 步骤2:检查收费项目中是否有诊疗项目类型(context_enum = 3)的记录 -SELECT - COUNT(*) as total_count, - COUNT(CASE WHEN delete_flag = '0' THEN 1 END) as active_count -FROM adm_charge_item -WHERE (context_enum::text = '3' OR context_enum = 3); - --- 步骤3:检查收费项目能否匹配到诊疗项目定义(最关键!) -SELECT - aci.id as charge_item_id, - aci.encounter_id, - aci.product_id, - aci.status_enum, - wad.id as activity_def_id, - wad.name as activity_name, - CASE - WHEN wad.id IS NULL THEN '❌ 无法匹配:product_id=' || aci.product_id || ' 在诊疗项目定义表中不存在' - WHEN wad.delete_flag != '0' THEN '❌ 无法匹配:诊疗项目定义已删除' - WHEN aci.delete_flag != '0' THEN '❌ 无法匹配:收费项目已删除' - WHEN aci.status_enum NOT IN (1,2,3,4,5,6) THEN '❌ 无法匹配:状态不在查询范围内 status=' || aci.status_enum - ELSE '✅ 可以匹配' - END as match_result -FROM adm_charge_item aci -LEFT JOIN wor_activity_definition wad - ON aci.product_id = wad.id AND wad.delete_flag = '0' -WHERE (aci.context_enum::text = '3' OR aci.context_enum = 3) - AND aci.delete_flag = '0' -LIMIT 50; - --- 步骤4:统计匹配情况 -SELECT - COUNT(*) as total_charge_items, - COUNT(CASE WHEN wad.id IS NOT NULL AND wad.delete_flag = '0' THEN 1 END) as matched_count, - COUNT(CASE WHEN wad.id IS NULL THEN 1 END) as unmatched_def_count, - COUNT(CASE WHEN wad.delete_flag != '0' THEN 1 END) as deleted_def_count, - COUNT(CASE WHEN wad.id IS NOT NULL AND wad.delete_flag = '0' AND aci.status_enum IN (1,2,3,4,5,6) THEN 1 END) as final_matched_count -FROM adm_charge_item aci -LEFT JOIN wor_activity_definition wad - ON aci.product_id = wad.id -WHERE (aci.context_enum::text = '3' OR aci.context_enum = 3) - AND aci.delete_flag = '0'; - --- 步骤5:检查具体就诊的诊疗项目(如果有encounterId,取消注释并替换) --- SELECT --- T1.encounter_id, --- T1.id as charge_item_id, --- T1.product_id, --- T1.status_enum, --- T2.id as activity_def_id, --- T2.name as activity_name, --- CASE --- WHEN T2.id IS NULL THEN '❌ 无法匹配' --- WHEN T2.delete_flag != '0' THEN '❌ 定义已删除' --- WHEN T1.status_enum NOT IN (1,2,3,4,5,6) THEN '❌ 状态不符合' --- ELSE '✅ 正常' --- END as status --- FROM adm_charge_item AS T1 --- LEFT JOIN wor_activity_definition AS T2 --- ON T1.product_id = T2.id AND T2.delete_flag = '0' --- WHERE T1.encounter_id = :encounterId -- 替换为实际的encounterId --- AND (T1.context_enum::text = '3' OR T1.context_enum = 3) --- AND T1.delete_flag = '0'; - - - - - - - - - - - - - - - - diff --git a/openhis-server-new/sql/诊断门诊划价检索不出诊疗项目问题.md b/openhis-server-new/sql/诊断门诊划价检索不出诊疗项目问题.md deleted file mode 100644 index 66817f9c..00000000 --- a/openhis-server-new/sql/诊断门诊划价检索不出诊疗项目问题.md +++ /dev/null @@ -1,170 +0,0 @@ -# 诊断:门诊划价检索不出诊疗项目问题 - -## 问题描述 -在门诊划价页面,检索不出诊疗项目,显示"暂无数据"。 - -## 问题分析 - -### SQL查询逻辑 -根据 `OutpatientChargeAppMapper.xml` 中的 `selectEncounterPatientPrescription` 查询: - -1. **查询主表**:`adm_charge_item`(收费项目表) -2. **关联诊疗项目定义表**:`wor_activity_definition` - - 关联条件:`T1.context_enum = #{activity}` AND `T1.product_id = T2.id` AND `T2.delete_flag = '0'` -3. **返回字段**:`item_name` 来自 `wor_activity_definition.name` - -### 可能的原因 - -#### 1. 数据库中没有诊疗项目定义数据 -- **检查**:`wor_activity_definition` 表中是否有数据 -- **SQL**: -```sql -SELECT COUNT(*) FROM wor_activity_definition WHERE delete_flag = '0'; -``` - -#### 2. 收费项目中的product_id无法匹配到诊疗项目定义 -- **检查**:`adm_charge_item` 表中的 `product_id` 是否存在于 `wor_activity_definition` 表中 -- **SQL**: -```sql -SELECT - aci.id, - aci.encounter_id, - aci.product_id, - aci.context_enum, - wad.id as activity_def_id, - wad.name as activity_name -FROM adm_charge_item aci -LEFT JOIN wor_activity_definition wad ON aci.product_id = wad.id AND wad.delete_flag = '0' -WHERE aci.context_enum = 'ACTIVITY' - AND aci.delete_flag = '0' - AND wad.id IS NULL; -- 无法匹配的记录 -``` - -#### 3. 诊疗项目定义被标记为删除 -- **检查**:`wor_activity_definition` 表中的 `delete_flag` 是否为 '0' -- **SQL**: -```sql -SELECT - id, - name, - delete_flag -FROM wor_activity_definition -WHERE delete_flag != '0'; -``` - -#### 4. context_enum 值不匹配 -- **检查**:`adm_charge_item` 表中的 `context_enum` 值是否正确 -- **SQL**: -```sql -SELECT DISTINCT context_enum FROM adm_charge_item WHERE delete_flag = '0'; -``` -- **注意**:后端代码中传入的 `#{activity}` 参数值应该是 `ChargeItemContext.ACTIVITY.getValue()` - -## 诊断步骤 - -### 步骤1:检查诊疗项目定义表是否有数据 -```sql -SELECT - COUNT(*) as total_count, - COUNT(CASE WHEN delete_flag = '0' THEN 1 END) as active_count, - COUNT(CASE WHEN delete_flag != '0' THEN 1 END) as deleted_count -FROM wor_activity_definition; -``` - -**预期结果**:`active_count` 应该 > 0 - -### 步骤2:检查收费项目中的诊疗项目是否能匹配到定义 -```sql -SELECT - COUNT(*) as total_charge_items, - COUNT(CASE WHEN wad.id IS NOT NULL THEN 1 END) as matched_count, - COUNT(CASE WHEN wad.id IS NULL THEN 1 END) as unmatched_count -FROM adm_charge_item aci -LEFT JOIN wor_activity_definition wad - ON aci.context_enum = 'ACTIVITY' - AND aci.product_id = wad.id - AND wad.delete_flag = '0' -WHERE aci.context_enum = 'ACTIVITY' - AND aci.delete_flag = '0'; -``` - -**预期结果**:`matched_count` 应该 > 0,`unmatched_count` 应该 = 0 - -### 步骤3:检查具体就诊的诊疗项目 -```sql --- 替换 :encounterId 为实际的就诊ID -SELECT - T1.encounter_id, - T1.id as charge_item_id, - T1.context_enum, - T1.product_id, - T1.status_enum, - T2.id as activity_def_id, - T2.name as activity_name, - T2.delete_flag as activity_delete_flag, - CASE - WHEN T2.id IS NULL THEN '诊疗项目定义不存在或已删除' - WHEN T2.delete_flag != '0' THEN '诊疗项目定义已删除' - ELSE '正常' - END as status -FROM adm_charge_item AS T1 -LEFT JOIN wor_activity_definition AS T2 - ON T1.context_enum = 'ACTIVITY' - AND T1.product_id = T2.id -WHERE T1.encounter_id = :encounterId -- 替换为实际的encounterId - AND T1.context_enum = 'ACTIVITY' - AND T1.delete_flag = '0' - AND T1.status_enum IN (1, 2, 3, 4, 5, 6); -``` - -### 步骤4:检查后端传入的参数值 -检查 `OutpatientChargeAppServiceImpl.java` 中: -```java -ChargeItemContext.ACTIVITY.getValue() -``` -确认这个值是什么(应该是 'ACTIVITY' 或对应的枚举值) - -## 解决方案 - -### 如果是数据问题: -1. **缺少诊疗项目定义数据**: - - 需要在 `wor_activity_definition` 表中添加诊疗项目数据 - - 或者在系统管理-目录管理-诊疗项目中添加 - -2. **product_id无法匹配**: - - 检查 `adm_charge_item` 表中的 `product_id` 是否正确 - - 检查 `wor_activity_definition` 表中的 `id` 是否与 `product_id` 匹配 - -3. **delete_flag不正确**: - - 将 `wor_activity_definition` 表中需要使用的记录的 `delete_flag` 设置为 '0' - -### 如果是代码问题: -1. **context_enum值不匹配**: - - 检查后端代码中 `ChargeItemContext.ACTIVITY.getValue()` 返回的值 - - 确保与数据库中的 `context_enum` 值一致 - -2. **SQL查询条件错误**: - - 检查 SQL 中的关联条件是否正确 - - 检查是否有其他过滤条件导致数据被过滤掉 - -## 快速诊断SQL -运行以下SQL可以快速诊断问题: -```sql --- 见 diagnose_treatment_items_issue.sql 文件 -``` - - - - - - - - - - - - - - - - diff --git a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/adviceBaseList.vue b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/adviceBaseList.vue index 8ef0688d..fe8a9b48 100644 --- a/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/adviceBaseList.vue +++ b/openhis-ui-vue3/src/views/clinicmanagement/bargain/component/adviceBaseList.vue @@ -11,7 +11,7 @@ @row-click="clickRow" > - + diff --git a/openhis-ui-vue3/src/views/doctorstation/components/adviceBaseList.vue b/openhis-ui-vue3/src/views/doctorstation/components/adviceBaseList.vue index d54fedce..339cdeb7 100644 --- a/openhis-ui-vue3/src/views/doctorstation/components/adviceBaseList.vue +++ b/openhis-ui-vue3/src/views/doctorstation/components/adviceBaseList.vue @@ -30,9 +30,6 @@ v-loading="loading" > - - - @@ -105,6 +102,8 @@ const medicalInsuranceLevelMap = { // 获取药品分类名称 - 使用系统统一的数据字典 function getCategoryName(row) { + if (!row) return '-'; + if (row.adviceType === 1) { // 药品类型 // 优先使用系统统一的药品分类数据字典 if (med_category_code.value && med_category_code.value.length > 0) { @@ -124,10 +123,11 @@ function getCategoryName(row) { } else if (row.adviceType === 2) { // 耗材类型 return '耗材'; } else if (row.adviceType === 3) { // 诊疗类型 - // 使用目录类别的字典文本显示 - return row.categoryCode_dictText || row.categoryCode || '诊疗'; + // 对于诊疗类型,activityType 就是 category_code 的整数值,使用 activityType_dictText 显示 + return row.activityType_dictText || row.categoryCode_dictText || row.categoryCode || '诊疗'; } - return row.activityType_dictText || '-'; + // 其他情况使用 categoryCode_dictText + return row.categoryCode_dictText || '-'; } // 获取医保等级 - 简单直接的实现 @@ -178,16 +178,19 @@ const isRequestInProgress = ref(false); // 请求进行中的标志 // 计算属性:根据搜索条件过滤数据 const filteredAdviceBaseList = computed(() => { + let result = []; if (!props.adviceQueryParams.searchKey) { - return adviceBaseList.value.slice(0, 50); // 返回前50个常用项目 + result = adviceBaseList.value.slice(0, 50); // 返回前50个常用项目 + } else { + const searchKey = props.adviceQueryParams.searchKey.toLowerCase(); + result = adviceBaseList.value.filter(item => + item.adviceName.toLowerCase().includes(searchKey) || + item.py_str?.toLowerCase().includes(searchKey) || + item.wb_str?.toLowerCase().includes(searchKey) + ).slice(0, 100); // 限制返回数量 } - - const searchKey = props.adviceQueryParams.searchKey.toLowerCase(); - return adviceBaseList.value.filter(item => - item.adviceName.toLowerCase().includes(searchKey) || - item.py_str?.toLowerCase().includes(searchKey) || - item.wb_str?.toLowerCase().includes(searchKey) - ).slice(0, 100); // 限制返回数量 + + return result; }); // 预加载数据 @@ -322,7 +325,7 @@ async function getList() { } } else { const res = await getAdviceBaseInfo(queryParams); - const result = res.data?.records || []; + let result = res.data?.records || []; // 缓存结果 searchCache.set(cacheKey, { diff --git a/排查指南-字段查询问题.md b/排查指南-字段查询问题.md deleted file mode 100644 index 8cff33c6..00000000 --- a/排查指南-字段查询问题.md +++ /dev/null @@ -1,299 +0,0 @@ -# 排查指南:字段查询不到数据的问题 - -## 问题类型 -**症状**:SQL 查询条件使用了某个字段,但查询结果为空,而数据库中明明有数据。 - -**根本原因**:查询条件使用的字段在数据插入/更新时没有被设置(为 NULL 或默认值)。 - ---- - -## 排查步骤 - -### 第一步:确认字段是否存在 -```sql --- 检查表结构,确认字段是否存在 -SELECT column_name, data_type, is_nullable, column_default -FROM information_schema.columns -WHERE table_name = 'adm_encounter' - AND column_name = 'start_time'; -``` - -**检查点**: -- ✅ 字段确实存在 -- ❌ 字段不存在 → 检查字段名拼写、大小写 - ---- - -### 第二步:检查字段是否有值 -```sql --- 检查字段的实际值 -SELECT - id, - start_time, -- 检查这个字段 - create_time, -- 对比其他时间字段 - status_enum -FROM adm_encounter -WHERE delete_flag = '0' - AND create_time::DATE = CURRENT_DATE -- 用能查到数据的条件 -LIMIT 10; -``` - -**检查点**: -- ✅ `start_time` 有值 → 继续排查查询条件 -- ❌ `start_time` 为 NULL → **问题确认**:字段没有被设置 - ---- - -### 第三步:检查插入/更新代码 -找到创建/更新数据的 Service 方法,检查是否设置了该字段。 - -**检查位置**: -1. **Service 实现类**:查找 `save`、`insert`、`update` 相关方法 -2. **Mapper XML**:检查 INSERT/UPDATE 语句 -3. **Entity 类**:检查字段定义和默认值 - -**示例检查**: -```java -// ❌ 错误示例:没有设置 start_time -Encounter encounter = new Encounter(); -encounter.setBusNo("xxx"); -encounter.setPatientId(123L); -// 没有 encounter.setStartTime(...) -iEncounterService.save(encounter); - -// ✅ 正确示例:设置了 start_time -Encounter encounter = new Encounter(); -encounter.setBusNo("xxx"); -encounter.setPatientId(123L); -encounter.setStartTime(new Date()); // 设置了字段 -iEncounterService.save(encounter); -``` - ---- - -### 第四步:对比能查到数据的方法 -找到系统中**能查到数据**的类似查询,对比差异。 - -**对比维度**: -1. **使用的字段**:`start_time` vs `create_time` -2. **查询条件**:WHERE 子句的差异 -3. **关联表**:是否 JOIN 了其他表 -4. **业务逻辑**:是否过滤了特定状态 - -**示例对比**: -```sql --- ❌ 查不到数据的方法 -SELECT * FROM adm_encounter -WHERE start_time::DATE = CURRENT_DATE; -- start_time 为 NULL - --- ✅ 能查到数据的方法 -SELECT * FROM adm_encounter -WHERE create_time::DATE = CURRENT_DATE; -- create_time 有值 -``` - ---- - -### 第五步:检查字段的业务含义 -确认字段的**业务含义**和**使用场景**。 - -**常见情况**: -- `create_time`:记录创建时间(自动设置) -- `update_time`:记录更新时间(自动设置) -- `start_time`:业务开始时间(需要手动设置) -- `end_time`:业务结束时间(需要手动设置) - -**判断规则**: -- 如果字段是**业务字段**(如 `start_time`),需要手动设置 -- 如果字段是**系统字段**(如 `create_time`),通常自动设置 - ---- - -## 调试技巧 - -### 技巧1:打印 SQL 和参数 -在 Service 方法中添加日志,查看实际执行的 SQL: - -```java -@Override -public IPage getTodayOutpatientPatients(...) { - // 打印查询条件 - log.info("查询条件: queryDate={}, doctorId={}, departmentId={}", - queryDate, doctorId, departmentId); - - // 执行查询 - IPage result = mapper.getTodayOutpatientPatients(...); - - // 打印结果 - log.info("查询结果: 总数={}, 记录数={}", - result.getTotal(), result.getRecords().size()); - - return result; -} -``` - -### 技巧2:直接执行 SQL 验证 -在数据库客户端直接执行 SQL,验证查询条件是否正确: - -```sql --- 1. 先查看数据 -SELECT id, start_time, create_time, status_enum -FROM adm_encounter -WHERE delete_flag = '0' -LIMIT 10; - --- 2. 测试查询条件 -SELECT COUNT(*) -FROM adm_encounter -WHERE delete_flag = '0' - AND start_time::DATE = CURRENT_DATE; -- 测试这个条件 - --- 3. 对比其他条件 -SELECT COUNT(*) -FROM adm_encounter -WHERE delete_flag = '0' - AND create_time::DATE = CURRENT_DATE; -- 对比这个条件 -``` - -### 技巧3:使用数据库工具检查 -使用数据库管理工具(如 pgAdmin、DBeaver): -1. 查看表结构 -2. 查看数据样本 -3. 执行测试查询 -4. 检查字段的 NULL 值比例 - ---- - -## 预防措施 - -### 1. 代码审查检查清单 -在代码审查时,检查以下内容: - -- [ ] **插入数据时**:是否设置了所有必要的业务字段? -- [ ] **查询条件时**:使用的字段是否在插入时被设置? -- [ ] **字段命名**:是否遵循命名规范(`create_time` vs `start_time`)? -- [ ] **文档注释**:字段的业务含义是否清晰? - -### 2. 单元测试 -编写单元测试,验证字段设置: - -```java -@Test -public void testSaveEncounter() { - Encounter encounter = new Encounter(); - encounter.setPatientId(123L); - encounter.setBusNo("TEST001"); - // 检查是否设置了 start_time - assertNotNull(encounter.getStartTime(), "start_time 应该被设置"); - - Long id = encounterService.saveEncounterByRegister(encounter); - - // 验证数据库中的值 - Encounter saved = encounterService.getById(id); - assertNotNull(saved.getStartTime(), "数据库中的 start_time 不应该为 NULL"); -} -``` - -### 3. 数据库约束 -在数据库层面添加约束,防止 NULL 值: - -```sql --- 如果 start_time 是必填字段,添加 NOT NULL 约束 -ALTER TABLE adm_encounter -ALTER COLUMN start_time SET NOT NULL; - --- 或者添加默认值 -ALTER TABLE adm_encounter -ALTER COLUMN start_time SET DEFAULT CURRENT_TIMESTAMP; -``` - -### 4. 代码规范 -建立代码规范,明确字段使用规则: - -```java -/** - * 保存就诊信息(门诊挂号) - * - * 注意: - * - create_time: 自动设置(系统字段) - * - start_time: 需要手动设置(业务字段,表示就诊开始时间) - * - 如果 start_time 为 NULL,查询时使用 create_time 作为挂号时间 - */ -public Long saveEncounterByRegister(Encounter encounter) { - // 如果没有设置 start_time,使用当前时间 - if (encounter.getStartTime() == null) { - encounter.setStartTime(new Date()); - } - // ... -} -``` - ---- - -## 常见问题模式 - -### 模式1:时间字段混淆 -- **问题**:使用 `start_time` 查询,但插入时没有设置 -- **解决**:使用 `create_time` 或确保插入时设置 `start_time` - -### 模式2:状态字段未设置 -- **问题**:使用 `status_enum` 查询,但插入时使用默认值 -- **解决**:明确设置状态值 - -### 模式3:关联字段未设置 -- **问题**:使用 `organization_id` 查询,但插入时为 NULL -- **解决**:确保插入时设置关联字段 - ---- - -## 快速排查清单 - -遇到"查询不到数据"问题时,按以下顺序检查: - -1. ✅ **数据库中有数据吗?** - ```sql - SELECT COUNT(*) FROM adm_encounter WHERE delete_flag = '0'; - ``` - -2. ✅ **查询条件使用的字段有值吗?** - ```sql - SELECT start_time FROM adm_encounter WHERE delete_flag = '0' LIMIT 10; - ``` - -3. ✅ **插入代码设置了该字段吗?** - - 查看 Service 的 save/insert 方法 - - 检查是否调用了 setter 方法 - -4. ✅ **字段的业务含义是什么?** - - 是系统字段(自动设置)还是业务字段(手动设置)? - -5. ✅ **有类似功能能查到数据吗?** - - 对比能查到数据的方法,找出差异 - ---- - -## 总结 - -**核心原则**: -1. **先看数据**:检查字段是否有值 -2. **再看代码**:检查插入时是否设置 -3. **对比差异**:找出能查到数据的方法 -4. **验证修复**:修改后验证数据 - -**记住**:查询条件使用的字段,必须在数据插入/更新时被设置! - - - - - - - - - - - - - - - - -