诊疗下没有项目功能完善

This commit is contained in:
2026-01-26 11:59:13 +08:00
parent 2df1ed645f
commit 1616f66fc4
6 changed files with 22 additions and 576 deletions

View File

@@ -96,4 +96,7 @@ WHERE aci.context_enum = 'ACTIVITY'

View File

@@ -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';

View File

@@ -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 文件
```

View File

@@ -11,7 +11,7 @@
@row-click="clickRow" @row-click="clickRow"
> >
<el-table-column label="名称" align="center" prop="adviceName" /> <el-table-column label="名称" align="center" prop="adviceName" />
<el-table-column label="类型" align="center" prop="activityType_enumText" /> <el-table-column label="类型" align="center" prop="activityType_dictText" />
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" /> <el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" /> <el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
<el-table-column label="规格" align="center" prop="volume" /> <el-table-column label="规格" align="center" prop="volume" />

View File

@@ -30,9 +30,6 @@
v-loading="loading" v-loading="loading"
> >
<el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip /> <el-table-column label="名称" align="center" prop="adviceName" width="200" show-overflow-tooltip />
<el-table-column label="类型" align="center" width="100">
<template #default="scope">{{ getCategoryName(scope.row) }}</template>
</el-table-column>
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" /> <el-table-column label="包装单位" align="center" prop="unitCode_dictText" width="100" />
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" width="100" /> <el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" width="100" />
<el-table-column label="单次剂量" align="center" width="120"> <el-table-column label="单次剂量" align="center" width="120">
@@ -105,6 +102,8 @@ const medicalInsuranceLevelMap = {
// 获取药品分类名称 - 使用系统统一的数据字典 // 获取药品分类名称 - 使用系统统一的数据字典
function getCategoryName(row) { function getCategoryName(row) {
if (!row) return '-';
if (row.adviceType === 1) { // 药品类型 if (row.adviceType === 1) { // 药品类型
// 优先使用系统统一的药品分类数据字典 // 优先使用系统统一的药品分类数据字典
if (med_category_code.value && med_category_code.value.length > 0) { if (med_category_code.value && med_category_code.value.length > 0) {
@@ -124,10 +123,11 @@ function getCategoryName(row) {
} else if (row.adviceType === 2) { // 耗材类型 } else if (row.adviceType === 2) { // 耗材类型
return '耗材'; return '耗材';
} else if (row.adviceType === 3) { // 诊疗类型 } else if (row.adviceType === 3) { // 诊疗类型
// 使用目录类别的字典文本显示 // 对于诊疗类型activityType 就是 category_code 的整数值,使用 activityType_dictText 显示
return row.categoryCode_dictText || row.categoryCode || '诊疗'; 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(() => { const filteredAdviceBaseList = computed(() => {
let result = [];
if (!props.adviceQueryParams.searchKey) { 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(); const searchKey = props.adviceQueryParams.searchKey.toLowerCase();
return adviceBaseList.value.filter(item => result = adviceBaseList.value.filter(item =>
item.adviceName.toLowerCase().includes(searchKey) || item.adviceName.toLowerCase().includes(searchKey) ||
item.py_str?.toLowerCase().includes(searchKey) || item.py_str?.toLowerCase().includes(searchKey) ||
item.wb_str?.toLowerCase().includes(searchKey) item.wb_str?.toLowerCase().includes(searchKey)
).slice(0, 100); // 限制返回数量 ).slice(0, 100); // 限制返回数量
}
return result;
}); });
// 预加载数据 // 预加载数据
@@ -322,7 +325,7 @@ async function getList() {
} }
} else { } else {
const res = await getAdviceBaseInfo(queryParams); const res = await getAdviceBaseInfo(queryParams);
const result = res.data?.records || []; let result = res.data?.records || [];
// 缓存结果 // 缓存结果
searchCache.set(cacheKey, { searchCache.set(cacheKey, {

View File

@@ -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<TodayOutpatientPatientDto> getTodayOutpatientPatients(...) {
// 打印查询条件
log.info("查询条件: queryDate={}, doctorId={}, departmentId={}",
queryDate, doctorId, departmentId);
// 执行查询
IPage<TodayOutpatientPatientDto> 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. **验证修复**:修改后验证数据
**记住**:查询条件使用的字段,必须在数据插入/更新时被设置!