Compare commits
151 Commits
faf73a5ac4
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3d011951b | ||
| 7dc76d7b59 | |||
| b2dec2667a | |||
|
|
879d31b51d | ||
|
|
473a5f7f06 | ||
| 2eec988c56 | |||
| 8820048d55 | |||
| 6af7720470 | |||
|
|
5f134945ab | ||
| bc12cc1b08 | |||
| 17b8ea7192 | |||
|
|
2bfdd686c7 | ||
|
|
066cfaba46 | ||
| e8850e85fc | |||
| d083a3123a | |||
| 96c1927f8d | |||
| bdac9d0709 | |||
| 8faba1ea21 | |||
| dd9b77f6bb | |||
| d45955f6de | |||
| f905915f34 | |||
|
|
52951d7296 | ||
| 3c47979913 | |||
| 9aad809322 | |||
| b7850e5b8a | |||
|
|
effcdfbbe6 | ||
| 4277a369d2 | |||
| cf3f971741 | |||
| 75737cf95c | |||
| 4b544dc214 | |||
| 597e621b69 | |||
| 725ac4b76a | |||
| 8e8b35faa4 | |||
| 664ee0312c | |||
| cceaf7fb07 | |||
| d5d638b60b | |||
| 8de5ae3a4f | |||
| 8f20c48baa | |||
|
|
547cccbeb7 | ||
| 86e665bcae | |||
|
|
d1aa91f727 | ||
| bc021924e4 | |||
| 6179a89b6c | |||
| 7c12028f63 | |||
| befb4739ee | |||
| fe07cee58c | |||
|
|
066c457d90 | ||
| 39b608dfd0 | |||
| 6b600b44ca | |||
|
|
b26ad75299 | ||
| b69f312611 | |||
| c65db9abc3 | |||
| 1b4ad5e710 | |||
| e46e2be830 | |||
| f515b90c43 | |||
| 6aff10e240 | |||
|
|
5d02da03b4 | ||
| d99188bfb9 | |||
| c3776c642b | |||
| 46a99ecd55 | |||
| 81744b9b9e | |||
| 469b325f0e | |||
| 8a3fe5461e | |||
| b65841c0cc | |||
| 8ef334ba1b | |||
|
|
2492daa0ad | ||
| 8af06f6916 | |||
| 7008fb007f | |||
| dc039fcced | |||
|
|
fcb1d771f4 | ||
| 30ca81090a | |||
| e722841e60 | |||
| b4ab67aed9 | |||
| 6a8f82bb2e | |||
| 09761c8ce8 | |||
|
|
5e3affcf3a | ||
|
|
455f7938be | ||
|
|
9525b1d927 | ||
| 8810c678c9 | |||
| cd3155e63c | |||
| 45fdca65a7 | |||
| 491db8bc03 | |||
| c735bc3a78 | |||
| e2db4bd3a5 | |||
| fc0f5a11be | |||
| c5528ce1b7 | |||
| 9116ea4a84 | |||
| ce8b0b16b1 | |||
| 259c62b6b4 | |||
| 208b8fc41d | |||
| c611c0ce6f | |||
| 25c266babb | |||
| 04ad139eae | |||
|
|
6add091a7b | ||
|
|
a05b3a8d3c | ||
| 35bc735ecc | |||
|
|
fcd2d03424 | ||
| 87c7981ad9 | |||
| 9edf8936ba | |||
|
|
753bd8bb4b | ||
| 3e09b4cc10 | |||
| 55970622f1 | |||
| 98164c65a2 | |||
|
|
d63a34f4e6 | ||
| 20ab9f890f | |||
| 038213a26c | |||
| ff41aa9c04 | |||
| f60e070984 | |||
| 3f7169844c | |||
|
|
8b993d5ddd | ||
|
|
9cfa9a3417 | ||
|
|
b200f80d88 | ||
|
|
e57baaead6 | ||
|
|
2576f62f88 | ||
|
|
cd24fe007f | ||
|
|
3898880665 | ||
| 562e618aaa | |||
| ec4d57ea69 | |||
|
|
7a5b26607e | ||
|
|
50ceb98e83 | ||
|
|
2065395bd2 | ||
|
|
517dc41f01 | ||
|
|
6f8e677045 | ||
|
|
1747291f41 | ||
|
|
cff1e0145b | ||
|
|
3ab7ea1898 | ||
|
|
c5d75f053b | ||
|
|
730476e927 | ||
| d8a4487b2b | |||
| c93a5f69d8 | |||
|
|
b826afb17c | ||
|
|
2a5a157c57 | ||
|
|
ca9b145d3e | ||
|
|
7676f03c96 | ||
|
|
b5b91d8971 | ||
|
|
f1bddf3fbe | ||
| b1c966f69f | |||
| dba6350493 | |||
|
|
6fb5b5993a | ||
|
|
4c2b015210 | ||
|
|
9f9f193287 | ||
|
|
d34a314f02 | ||
| 8d45cfe9db | |||
| f9d897c081 | |||
| d2616ac2f9 | |||
| d2a6780c23 | |||
| fc32b83980 | |||
|
|
b936654a11 | ||
| 2a525f95b9 | |||
| 585b9bd720 | |||
| 89bf85fd97 |
76
.github/copilot-instructions.md
vendored
76
.github/copilot-instructions.md
vendored
@@ -1,76 +0,0 @@
|
|||||||
# OpenHIS — AI 编码助手 指南
|
|
||||||
|
|
||||||
目的:帮助自动化/AI 编码代理快速上手本仓库,包含架构要点、关键文件、常用构建/运行命令以及项目约定。请只按照仓库内真实可见的内容提出修改建议或补充说明。
|
|
||||||
|
|
||||||
- **代码组织**: 本项目是一个 Java 后端(多模块 Maven)+ Vue3 前端(Vite)的大型应用。
|
|
||||||
- 后端主模块目录:`openhis-server-new/`(顶层为 `pom`,包含多个子模块)。关键子模块示例:`openhis-application`, `openhis-domain`, `openhis-common`, `core-*` 系列。
|
|
||||||
- 前端目录:`openhis-ui-vue3/`(Vite + Vue 3,使用 Pinia、Element Plus 等)。
|
|
||||||
|
|
||||||
- **大局观(Big Picture)**: 后端以 Spring Boot(Java 17)实现,使用多模块 Maven 管理公共库与业务模块;前端由单独仓库目录通过 Vite 构建并以环境变量(`VITE_APP_BASE_API`)与后端交互。后端扫描 `com.core` 与 `com.openhis` 包(见 `OpenHisApplication.java`),启动类位于:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
|
||||||
|
|
||||||
- **运行/构建(Windows PowerShell 示例)**:
|
|
||||||
- 构建后端(从仓库根执行):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
```
|
|
||||||
|
|
||||||
- 仅运行后端模块(开发时常用):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
# 或在 IDE 中运行 `com.openhis.OpenHisApplication` 的 main()
|
|
||||||
```
|
|
||||||
|
|
||||||
- 前端启动与构建(需要 Node.js v16.x,仓库 README 建议 v16.15):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
npm run dev # 本地开发(热重载)
|
|
||||||
npm run build:prod # 生产构建
|
|
||||||
```
|
|
||||||
|
|
||||||
- **环境与配置**:
|
|
||||||
- 后端配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`(数据库、端口、profile 等)。README 还提及 `application-druid.yml`(若存在请优先查看)。
|
|
||||||
- 前端配置:多个 `.env.*` 文件(例如 `.env.development`, `.env.staging`, `.env.production`),关键变量:`VITE_APP_BASE_API`(例如 `/dev-api`),前端通过 `import.meta.env.VITE_APP_BASE_API` 拼接后端 URL(见 `src/utils/request.js`、多个视图与组件)。
|
|
||||||
|
|
||||||
- **重要约定 / 模式**:
|
|
||||||
- 后端采用 Java 17、Spring Boot 2.5.x 家族,父 POM在 `openhis-server-new/pom.xml` 定义。常用依赖版本在该 POM 的 `<properties>` 中集中维护。
|
|
||||||
- 模块间以 Maven 模块依赖与 `com.core` / `com.openhis` 包名分层(见 `pom.xml` 的 `<modules>` 与 `dependencyManagement`)。
|
|
||||||
- 前端通过 Vite 插件配置(`openhis-ui-vue3/vite/plugins`)管理 svg、自动导入等。UI 框架为 Element Plus,状态管理为 Pinia。
|
|
||||||
|
|
||||||
- **集成点 & 外部依赖**:
|
|
||||||
- 数据库:PostgreSQL(README 建议 v16.2),仓库根含一个大型初始化 SQL:`数据库初始话脚本(请使用navicat16版本导入).sql`,用于初始化表与演示数据。
|
|
||||||
- 缓存/会话:Redis(需自行配置)。
|
|
||||||
- 其他:Flowable(工作流),Druid(连接池监控),第三方服务通过特定配置类(例如 `YbServiceConfig` 在 `OpenHisApplication` 中启用)。
|
|
||||||
|
|
||||||
- **调试与常见位置**:
|
|
||||||
- 启动类:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
|
||||||
- 全局配置:`openhis-server-new/openhis-application/src/main/resources/`(`application.yml`、profile 文件等)。
|
|
||||||
- 前端入口:`openhis-ui-vue3/src/main.js`、路由在 `openhis-ui-vue3/src/router/index.js`。
|
|
||||||
- API 文档与监控路径(通常由后端暴露并被前端访问):
|
|
||||||
- Swagger UI: `<VITE_APP_BASE_API>/swagger-ui/index.html`(前端视图在 `src/views/tool/swagger/index.vue`)。
|
|
||||||
- Druid: `<VITE_APP_BASE_API>/druid/login.html`(见前端相关视图引用)。
|
|
||||||
|
|
||||||
- **为 AI 代理的具体建议(如何安全、有效地修改代码)**:
|
|
||||||
- 修改后端时:优先在子模块(例如 `openhis-application`)本地运行 `mvn spring-boot:run` 验证启动与基础 API;大量改动前先执行 `mvn -T1C -DskipTests clean package` 在 CI 环境上验证构建(本地机器也可用)。
|
|
||||||
- 修改前端时:检查/调整对应 `.env.*` 文件中的 `VITE_APP_BASE_API`,使用 `npm run dev` 本地联调后端接口(可通过代理或将 `VITE_APP_BASE_API` 指向后端地址)。
|
|
||||||
- 修改数据库结构或 seed:请参考仓库根的 SQL 初始化脚本,任何 DDL/数据变更需同步该脚本并通知数据库管理员/运维。
|
|
||||||
|
|
||||||
- **举例(常见任务示例)**:
|
|
||||||
- 本地联调前端 + 后端(PowerShell):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# 启动后端
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 启动前端(另开终端)
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
如需我把这些内容合并为更短或更详细的版本,或把其中某部分(例如后端模块依赖关系图、关键 Java 包说明)展开,请告诉我要增强哪一节。
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询结果显示问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
|
||||||
2. 实际查询返回了5条记录,但COUNT查询只返回了1条记录
|
|
||||||
3. 这导致前端只显示了1条记录,而不是全部5条
|
|
||||||
4. 原因:MyBatis-Plus自动生成的COUNT查询和实际查询使用了不同的条件,特别是逻辑删除条件
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
1. 修改TicketMapper.xml中的自定义COUNT查询,显式添加`delete_flag = '0'`条件
|
|
||||||
2. 在selectTicketPage和selectTicketPage_mpCount查询中都添加逻辑删除条件
|
|
||||||
3. 确保两个查询使用完全相同的WHERE条件
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改`selectTicketPage`查询,添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
2. 修改`selectTicketPage_mpCount`查询,添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
3. 确保两个查询的WHERE条件完全一致
|
|
||||||
4. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-domain/src/main/resources/mapper/clinical/TicketMapper.xml`
|
|
||||||
- 查询:`selectTicketPage` 和 `selectTicketPage_mpCount`
|
|
||||||
- 修改内容:添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,COUNT查询和实际查询将使用完全相同的条件,包括逻辑删除条件,从而确保COUNT查询返回正确的总记录数,前端能显示所有5条专家号记录。
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询COUNT结果不正确问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
|
||||||
2. COUNT查询和实际查询的WHERE条件完全相同:`WHERE delete_flag = '0' AND ticket_type = '专家'`
|
|
||||||
3. 但COUNT查询只返回1条记录,而实际查询返回5条记录
|
|
||||||
4. 原因:MyBatis-Plus的分页插件在处理自定义COUNT查询时,存在bug,导致COUNT查询结果不正确
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
修改`TicketAppServiceImpl.java`中的`listTicket`方法,不使用MyBatis-Plus的自动分页功能,而是手动实现分页查询:
|
|
||||||
1. 直接调用`ticketService.countTickets`方法获取总记录数
|
|
||||||
2. 手动构建查询条件
|
|
||||||
3. 确保COUNT查询和实际查询使用完全相同的条件
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改`TicketAppServiceImpl.java`中的`listTicket`方法
|
|
||||||
2. 手动实现分页查询,包括:
|
|
||||||
- 构建查询条件
|
|
||||||
- 调用`countTickets`获取总记录数
|
|
||||||
- 调用`selectTicketList`获取分页数据
|
|
||||||
- 手动组装分页结果
|
|
||||||
3. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
|
||||||
- 方法:`listTicket`
|
|
||||||
- 修改内容:替换MyBatis-Plus的自动分页,改为手动分页实现
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,COUNT查询和实际查询将使用完全相同的条件,COUNT查询将返回正确的总记录数(5条),前端能显示所有5条专家号记录。
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
## 问题分析
|
|
||||||
根据日志和代码分析,发现号源列表显示"没有更多数据了"的问题原因:
|
|
||||||
|
|
||||||
1. **后端查询正常**:成功查询到5条符合条件的专家号源记录
|
|
||||||
2. **数据转换失败**:在`convertToDto`方法中,`fee`字段类型转换错误
|
|
||||||
3. **响应返回空列表**:由于转换异常,最终返回给前端的号源列表为空
|
|
||||||
|
|
||||||
## 问题根源
|
|
||||||
- `Ticket`实体类的`fee`字段为**BigDecimal类型**(数据库存储)
|
|
||||||
- `TicketDto`类的`fee`字段为**String类型**(前端展示)
|
|
||||||
- 在`convertToDto`方法中,直接将BigDecimal类型的`fee`赋值给String类型的`fee`,导致**ClassCastException**
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
修改`TicketAppServiceImpl.java`文件中的`convertToDto`方法,将BigDecimal类型的`fee`转换为String类型:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 原代码
|
|
||||||
dto.setFee(ticket.getFee());
|
|
||||||
|
|
||||||
// 修复后代码
|
|
||||||
dto.setFee(ticket.getFee().toString());
|
|
||||||
```
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
1. 修复后,后端能成功将`Ticket`实体转换为`TicketDto`
|
|
||||||
2. 前端能接收到包含5条专家号源的完整列表
|
|
||||||
3. 页面显示正常,不再出现"没有更多数据了"的提示
|
|
||||||
|
|
||||||
## 验证方法
|
|
||||||
1. 重新启动项目,访问号源管理页面
|
|
||||||
2. 选择"专家号"类型,查看是否能正确显示5条号源记录
|
|
||||||
3. 检查日志,确认没有类型转换异常
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
从日志中发现关键问题:
|
|
||||||
- 前端传递的ticket_type值是英文:`general` (普通号) 和 `expert` (专家号)
|
|
||||||
- 数据库中存储的ticket_type值是中文:`普通` 和 `专家`
|
|
||||||
- 导致查询条件不匹配,无法查询到数据
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
需要在后端添加类型映射转换,将前端传递的英文类型转换为数据库中存储的中文类型。
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改 `TicketAppServiceImpl.java` 文件,在处理type参数时添加映射转换逻辑
|
|
||||||
2. 添加从英文类型到中文类型的映射关系
|
|
||||||
3. 测试修复后的功能,确保普通号和专家号都能正确查询
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
|
||||||
- 方法:`listTicket` 中的type参数处理部分
|
|
||||||
- 修改内容:添加类型映射转换,将 "general" 转换为 "普通","expert" 转换为 "专家"
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,前端选择"普通号"或"专家号"时,系统能正确查询到对应的号源数据,不再出现"没有更多数据了"的提示。
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
**问题分析**:
|
|
||||||
后端返回的响应格式是`{code: 200, msg: "操作成功", data: {total: 5, limit: 20, page: 1, list: [5条记录]}}`,而前端可能期望直接访问`list`属性,导致只能显示1条数据。
|
|
||||||
|
|
||||||
**修复方案**:
|
|
||||||
|
|
||||||
1. 修改`TicketAppServiceImpl.java`的`listTicket`方法,确保返回的分页数据格式正确
|
|
||||||
2. 调整响应结构,使其更符合前端期望
|
|
||||||
3. 保持与现有代码的兼容性
|
|
||||||
|
|
||||||
**修改点**:
|
|
||||||
|
|
||||||
* `TicketAppServiceImpl.java`:优化`listTicket`方法的响应格式
|
|
||||||
|
|
||||||
* 确保分页信息和列表数据都能正确返回给前端
|
|
||||||
|
|
||||||
**预期效果**:
|
|
||||||
|
|
||||||
* 后端返回正确格式的响应数据
|
|
||||||
|
|
||||||
* 前端能够正确显示所有5条专家号数据
|
|
||||||
|
|
||||||
* 保持与现有代码的兼容性
|
|
||||||
|
|
||||||
188
AGENTS.md
Normal file
188
AGENTS.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# OpenHIS - AI Agent Development Guide
|
||||||
|
|
||||||
|
## 项目概览
|
||||||
|
OpenHIS 是一个医院管理系统,采用 Java 17 + Spring Boot 后端和 Vue 3 + Vite 前端架构。
|
||||||
|
|
||||||
|
## 构建和运行命令
|
||||||
|
|
||||||
|
### 后端(Java/Spring Boot)
|
||||||
|
```bash
|
||||||
|
# 构建整个项目
|
||||||
|
cd openhis-server-new
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 运行后端(开发模式)
|
||||||
|
cd openhis-server-new/openhis-application
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# 运行特定模块
|
||||||
|
cd openhis-server-new/[module-name]
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端(Vue 3 + Vite)
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 开发服务器
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 生产构建
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
# 测试环境构建
|
||||||
|
npm run build:test
|
||||||
|
|
||||||
|
# 预览构建结果
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
项目当前没有配置正式的测试框架。如需添加测试:
|
||||||
|
- 后端:考虑使用 JUnit 5 + Mockito
|
||||||
|
- 前端:考虑使用 Vitest + Vue Test Utils
|
||||||
|
|
||||||
|
## 代码风格规范
|
||||||
|
|
||||||
|
### Java 后端规范
|
||||||
|
- **Java 版本**: 17
|
||||||
|
- **框架**: Spring Boot 2.5.15
|
||||||
|
- **ORM**: MyBatis Plus 3.5.5
|
||||||
|
- **数据库**: PostgreSQL
|
||||||
|
- **包结构**:
|
||||||
|
- `com.openhis` - 业务逻辑
|
||||||
|
- `com.core` - 核心框架
|
||||||
|
- **命名约定**:
|
||||||
|
- 类名:PascalCase(如 `UserController`)
|
||||||
|
- 方法名:camelCase(如 `getUserList`)
|
||||||
|
- 常量:SCREAMING_SNAKE_CASE
|
||||||
|
- 配置文件:kebab-case
|
||||||
|
- **注解使用**:
|
||||||
|
- 使用 `@Slf4j` 替代手动声明 logger
|
||||||
|
- 使用 `@Data` 在实体类中
|
||||||
|
- 使用 `@Service/@Controller/@Repository` 等 Spring 注解
|
||||||
|
- **异常处理**:
|
||||||
|
- 使用统一的异常处理机制
|
||||||
|
- 自定义业务异常继承 `RuntimeException`
|
||||||
|
|
||||||
|
### Vue 前端规范
|
||||||
|
- **框架**: Vue 3 + Composition API
|
||||||
|
- **UI 库**: Element Plus
|
||||||
|
- **状态管理**: Pinia
|
||||||
|
- **路由**: Vue Router 4
|
||||||
|
- **构建工具**: Vite 5
|
||||||
|
- **组件命名**: PascalCase
|
||||||
|
- **文件命名**: kebab-case
|
||||||
|
- **变量命名**: camelCase
|
||||||
|
- **常量命名**: SCREAMING_SNAKE_CASE
|
||||||
|
- **函数命名**:
|
||||||
|
- 事件处理:`handle` 前缀
|
||||||
|
- 数据获取:`get`/`load` 前缀
|
||||||
|
- 提交操作:`submit` 前缀
|
||||||
|
|
||||||
|
### 导入顺序
|
||||||
|
#### Java
|
||||||
|
1. `java.*`
|
||||||
|
2. `javax.*`
|
||||||
|
3. 第三方库
|
||||||
|
4. `com.core.*`
|
||||||
|
5. `com.openhis.*`
|
||||||
|
6. `*.*`(其他包)
|
||||||
|
|
||||||
|
#### JavaScript/Vue
|
||||||
|
1. `vue` 相关
|
||||||
|
2. 第三方库
|
||||||
|
3. `@/` 别名导入
|
||||||
|
4. 相对路径导入
|
||||||
|
|
||||||
|
### 代码格式
|
||||||
|
#### Java
|
||||||
|
- 缩进:4个空格
|
||||||
|
- 行长度:120字符
|
||||||
|
- 左大括号不换行
|
||||||
|
|
||||||
|
#### Vue/JavaScript
|
||||||
|
- 缩进:2个空格
|
||||||
|
- 字符串:优先使用单引号
|
||||||
|
- 行长度:100字符
|
||||||
|
|
||||||
|
## 关键配置文件
|
||||||
|
|
||||||
|
### 后端配置
|
||||||
|
- 主配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||||
|
- 环境配置:`application-{profile}.yml`
|
||||||
|
- Maven 父 POM:`openhis-server-new/pom.xml`
|
||||||
|
|
||||||
|
### 前端配置
|
||||||
|
- Vite 配置:`openhis-ui-vue3/vite.config.js`
|
||||||
|
- 环境变量:`.env.*` 文件
|
||||||
|
- 路由配置:`openhis-ui-vue3/src/router/index.js`
|
||||||
|
|
||||||
|
## 开发约定
|
||||||
|
|
||||||
|
### API 设计
|
||||||
|
- RESTful API 风格
|
||||||
|
- 统一响应格式
|
||||||
|
- 使用 Swagger 文档
|
||||||
|
- 错误码统一管理
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- 表名:snake_case
|
||||||
|
- 字段名:snake_case
|
||||||
|
- 主键:使用 `id`
|
||||||
|
- 软删除:使用 `valid_flag` 字段
|
||||||
|
|
||||||
|
### 前端组件
|
||||||
|
- 单一职责原则
|
||||||
|
- Props 使用 camelCase
|
||||||
|
- Events 使用 kebab-case
|
||||||
|
- 使用 Composition API
|
||||||
|
- 组件文档使用 JSDoc
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
- 模块化设计
|
||||||
|
- 异步操作使用 actions
|
||||||
|
- 避免在组件中直接修改状态
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
- `VITE_APP_BASE_API`: API 基础路径
|
||||||
|
- `VITE_APP_ENV`: 环境标识
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- `spring.profiles.active`: 激活的配置文件
|
||||||
|
- `core.name`: 应用名称
|
||||||
|
- `core.version`: 应用版本
|
||||||
|
|
||||||
|
## 安全规范
|
||||||
|
- 所有 API 接口需要权限验证
|
||||||
|
- 敏感信息使用环境变量
|
||||||
|
- SQL 注入防护
|
||||||
|
- XSS 攻击防护
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- 后端使用连接池(Druid)
|
||||||
|
- 前端使用路由懒加载
|
||||||
|
- 图片使用 WebP 格式
|
||||||
|
- 大列表使用虚拟滚动
|
||||||
|
|
||||||
|
## 常用工具类
|
||||||
|
- 后端:`com.core.common.utils.*`
|
||||||
|
- 前端:`@/utils/*`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
1. 修改数据库结构需要同步 SQL 脚本
|
||||||
|
2. 新增功能需要添加权限配置
|
||||||
|
3. 前端路由需要在权限系统中注册
|
||||||
|
4. 接口变更需要更新 Swagger 文档
|
||||||
|
5. 遵循现有代码风格,避免不必要的变化
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
- 后端端口:18080
|
||||||
|
- 前端端口:81
|
||||||
|
- API 前缀:`/openhis`
|
||||||
|
- Swagger UI:`/openhis/swagger-ui/index.html`
|
||||||
|
- Druid 监控:`/openhis/druid/login.html`
|
||||||
376
CODEBUDDY.md
376
CODEBUDDY.md
@@ -1,376 +0,0 @@
|
|||||||
# CODEBUDDY.md
|
|
||||||
|
|
||||||
This file provides guidance to CodeBuddy Code when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This is a comprehensive Hospital Information System (HIS) built with a Java Spring Boot backend and Vue 3 frontend.
|
|
||||||
|
|
||||||
- **Backend**: Java 17, Spring Boot 2.5.15, multi-module Maven architecture
|
|
||||||
- **Frontend**: Vue 3, Vite, Element Plus, Pinia state management
|
|
||||||
- **Database**: PostgreSQL (recommended v16.2)
|
|
||||||
- **Cache**: Redis
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── openhis-server-new/ # Backend multi-module Maven project
|
|
||||||
│ ├── openhis-application/ # Main application module with startup class
|
|
||||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
|
||||||
│ ├── openhis-common/ # Shared utilities and common code
|
|
||||||
│ ├── core-admin/ # Core administration module
|
|
||||||
│ ├── core-framework/ # Framework configuration and security
|
|
||||||
│ ├── core-system/ # System management module
|
|
||||||
│ ├── core-quartz/ # Scheduled tasks
|
|
||||||
│ ├── core-generator/ # Code generation utilities
|
|
||||||
│ ├── core-common/ # Core utilities
|
|
||||||
│ └── core-flowable/ # Workflow engine integration
|
|
||||||
└── openhis-ui-vue3/ # Vue 3 frontend
|
|
||||||
├── src/
|
|
||||||
│ ├── api/ # API service layer
|
|
||||||
│ ├── components/ # Reusable components
|
|
||||||
│ ├── router/ # Vue Router configuration
|
|
||||||
│ ├── store/ # Pinia state management
|
|
||||||
│ ├── utils/ # Utility functions
|
|
||||||
│ └── views/ # Page components
|
|
||||||
└── vite/ # Vite plugins configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build and Development Commands
|
|
||||||
|
|
||||||
### Backend (Java)
|
|
||||||
|
|
||||||
**Build the entire backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
```
|
|
||||||
|
|
||||||
**Run the backend application (development):**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Alternative: Run directly from IDE:**
|
|
||||||
- Run the main method in `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
|
||||||
|
|
||||||
**Start scripts:**
|
|
||||||
- Linux/Mac: `openhis-server-new/start.sh`
|
|
||||||
- Windows: `openhis-server-new/start.bat`
|
|
||||||
|
|
||||||
### Frontend (Vue 3)
|
|
||||||
|
|
||||||
**Install dependencies:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Development server (with hot reload):**
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
- Runs on port 81 by default
|
|
||||||
- Proxies `/dev-api` requests to `http://localhost:18080/openhis`
|
|
||||||
|
|
||||||
**Build for production:**
|
|
||||||
```bash
|
|
||||||
npm run build:prod # Production build
|
|
||||||
npm run build:stage # Staging build
|
|
||||||
npm run build:test # Test environment build
|
|
||||||
npm run build:dev # Development build
|
|
||||||
npm run build:spug # Spug environment build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Preview production build:**
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
### Backend Architecture
|
|
||||||
|
|
||||||
The backend uses a multi-module Maven architecture with clear separation of concerns:
|
|
||||||
|
|
||||||
1. **openhis-application**: Entry point with `OpenHisApplication.java` (d:\his\openhis-server-new\openhis-application\src\main\java\com\openhis\OpenHisApplication.java:20)
|
|
||||||
- Scans `com.core` and `com.openhis` packages
|
|
||||||
- Configures async processing and YAML service configuration
|
|
||||||
- Runs on port 18080 with context path `/openhis`
|
|
||||||
|
|
||||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
|
||||||
- `administration`: Administrative functions
|
|
||||||
- `appointmentmanage`: Appointment management
|
|
||||||
- `check`: Medical examination/checkup
|
|
||||||
- `clinical`: Clinical workflows
|
|
||||||
- `crosssystem`: Cross-system integration
|
|
||||||
- `document`: Document management
|
|
||||||
- `financial`: Financial/billing
|
|
||||||
- `lab`: Laboratory operations
|
|
||||||
- `medication`: Medication management
|
|
||||||
- `triageandqueuemanage`: Patient triage and queue management
|
|
||||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
|
||||||
- `workflow`: Workflow management
|
|
||||||
- `jlau`, `nenu`: Additional domain modules
|
|
||||||
- `template`: Template management
|
|
||||||
|
|
||||||
3. **Core Modules** (com.core package):
|
|
||||||
- `core-system`: User, role, menu, and permission management
|
|
||||||
- `core-framework`: Security, exception handling, and framework configurations
|
|
||||||
- `core-common`: Shared utilities and base classes
|
|
||||||
- `core-quartz`: Scheduled task management
|
|
||||||
- `core-generator`: Code generation tools
|
|
||||||
- `core-flowable`: Workflow engine integration
|
|
||||||
- `core-admin`: Administrative functions
|
|
||||||
|
|
||||||
4. **openhis-common**: Domain-specific shared code and utilities under `com.openhis.common` package
|
|
||||||
|
|
||||||
**Key Technologies:**
|
|
||||||
- MyBatis-Plus 3.5.5 for ORM with enhanced CRUD operations
|
|
||||||
- Druid 1.2.27 connection pool with monitoring at `/druid/*`
|
|
||||||
- Flowable 6.8.0 for workflow management
|
|
||||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
|
||||||
- Swagger 3.0.0 for API documentation
|
|
||||||
- JWT 0.9.1 for authentication
|
|
||||||
- Hutool 5.3.8 utility library
|
|
||||||
- Fastjson2 2.0.58 for JSON processing
|
|
||||||
- Pinyin4j 2.5.1 for Chinese character to Pinyin conversion
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
The frontend uses Vue 3 with composition API and modern tooling:
|
|
||||||
|
|
||||||
**Key Files:**
|
|
||||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
|
||||||
- Router configuration: `openhis-ui-vue3/src/router/index.js`
|
|
||||||
- Store initialization: `openhis-ui-vue3/src/store/store.js`
|
|
||||||
- Vite configuration: `openhis-ui-vue3/vite.config.js`
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- Pinia for global state (replaces Vuex)
|
|
||||||
- Store modules: `app`, `dict`, `permission`, `settings`, `tagsView`, `user`
|
|
||||||
- Modules located in `openhis-ui-vue3/src/store/modules/`
|
|
||||||
|
|
||||||
**Routing:**
|
|
||||||
- Vue Router 4.3.0
|
|
||||||
- Two types of routes:
|
|
||||||
- `constantRoutes`: Public routes (login, 404, etc.)
|
|
||||||
- `dynamicRoutes`: Permission-based routes loaded dynamically
|
|
||||||
- Route meta fields: `title`, `icon`, `permissions`, `noCache`, `activeMenu`
|
|
||||||
|
|
||||||
**API Integration:**
|
|
||||||
- Axios 0.27.2 for HTTP requests
|
|
||||||
- Base API URL configured via environment variables (`VITE_APP_BASE_API`)
|
|
||||||
- Proxy configuration in vite.config.js for development
|
|
||||||
- `/dev-api` → `http://localhost:18080/openhis`
|
|
||||||
- `/ybplugin` → `http://localhost:5000` (insurance plugin)
|
|
||||||
- Request/response interceptors in `openhis-ui-vue3/src/utils/request.js`
|
|
||||||
- API service files organized by module in `openhis-ui-vue3/src/api/`
|
|
||||||
- `administration`, `appoinmentmanage`, `monitor`, `system`, `tool`
|
|
||||||
- Shared APIs: `home.js`, `login.js`, `menu.js`, `public.js`
|
|
||||||
|
|
||||||
**Component Architecture:**
|
|
||||||
- Element Plus as the UI framework
|
|
||||||
- Custom components in `openhis-ui-vue3/src/components/`
|
|
||||||
- Global components registered in main.js:
|
|
||||||
- Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview
|
|
||||||
- RightToolbar, Editor, DictTag
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Backend Configuration
|
|
||||||
|
|
||||||
**Main configuration file:** `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
|
|
||||||
**Environment-specific profiles:**
|
|
||||||
- `application-dev.yml` - Development environment
|
|
||||||
- `application-test.yml` - Test environment
|
|
||||||
- `application-prd.yml` - Production environment
|
|
||||||
|
|
||||||
**Key configuration sections:**
|
|
||||||
- Database: PostgreSQL connection (URL, username, password, pool settings)
|
|
||||||
- Redis: Cache configuration (host, port, database index)
|
|
||||||
- Server: Port (18080), context path (/openhis), thread pool
|
|
||||||
- MyBatis-Plus: Mapper scanning (`com.core.**.domain,com.openhis.**.domain`), type aliases, logical delete
|
|
||||||
- Logging: Debug levels for com.openhis and com.baomidou.mybatisplus
|
|
||||||
- Swagger: API documentation at `/swagger-ui/index.html`
|
|
||||||
- Druid: Database monitoring at `/druid/*` (credentials: openhis/123456)
|
|
||||||
- Flowable: Workflow engine settings (schema update disabled)
|
|
||||||
- LiteFlow: Business rule configuration at `config/flow.el.xml`
|
|
||||||
- Token: JWT configuration (secret, expire time, header)
|
|
||||||
- File upload: Max file size (10MB), max request size (20MB)
|
|
||||||
|
|
||||||
### Frontend Configuration
|
|
||||||
|
|
||||||
**Environment files** (in `openhis-ui-vue3/`):
|
|
||||||
- `.env.dev` - Dev environment
|
|
||||||
- `.env.development` - Development environment variables
|
|
||||||
- `.env.staging` - Staging environment variables
|
|
||||||
- `.env.production` - Production environment variables
|
|
||||||
- `.env.test` - Test environment variables
|
|
||||||
- `.env.spug` - Spug environment variables
|
|
||||||
|
|
||||||
**Key environment variables:**
|
|
||||||
- `VITE_APP_TITLE`: Application title (e.g., "医院信息管理系统")
|
|
||||||
- `VITE_APP_BASE_API`: Backend API base URL (e.g., `/dev-api`)
|
|
||||||
- `VITE_APP_ENV`: Environment identifier
|
|
||||||
|
|
||||||
**Vite configuration:**
|
|
||||||
- Development server: Port 81, host true, auto-open
|
|
||||||
- Proxy: `/dev-api` → `http://localhost:18080/openhis`
|
|
||||||
- Path aliases: `@` → `./src`, `~` → `./`
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
**Initialization script:** `数据库初始话脚本(请使用navicat16版本导入).sql` (located at repository root)
|
|
||||||
- Use Navicat version 16 to import
|
|
||||||
- Contains schema and initial demonstration data
|
|
||||||
|
|
||||||
**Database connection (dev environment):**
|
|
||||||
- Type: PostgreSQL
|
|
||||||
- URL: `jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=hisdev`
|
|
||||||
- Driver: `org.postgresql.Driver`
|
|
||||||
- Schema: `hisdev`
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Running Full Stack Locally
|
|
||||||
|
|
||||||
**Terminal 1 - Start backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Terminal 2 - Start frontend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Access the application at:
|
|
||||||
- Frontend: http://localhost:81
|
|
||||||
- Backend API: http://localhost:18080/openhis
|
|
||||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
|
||||||
- Druid monitoring: http://localhost:18080/openhis/druid/login.html
|
|
||||||
|
|
||||||
### Adding a New Backend Feature
|
|
||||||
|
|
||||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
|
||||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
|
||||||
3. Create mapper XML in `openhis-domain/[module]/resources/mapper/` (if custom SQL needed)
|
|
||||||
4. Create service interface and implementation in `openhis-domain/[module]/service/`
|
|
||||||
5. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
|
||||||
6. Add MyBatis-Plus annotations if using enhanced features
|
|
||||||
7. Test endpoints via Swagger UI at `http://localhost:18080/openhis/swagger-ui/index.html`
|
|
||||||
|
|
||||||
**Note:** Controllers are organized under `com.openhis.web` by business module (e.g., `web.administration`, `web.clinicalmanage`, `web.patientmanage`, etc.)
|
|
||||||
|
|
||||||
### Adding a New Frontend Page
|
|
||||||
|
|
||||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
|
||||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
|
||||||
3. Add route to `openhis-ui-vue3/src/router/index.js` (constantRoutes or dynamicRoutes)
|
|
||||||
4. Add Pinia store module if state management needed
|
|
||||||
5. Register global components if reusable
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
- Run unit tests (if configured):
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Patterns and Conventions
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
- Package structure follows domain-driven design
|
|
||||||
- Service layer uses `@Service` annotation
|
|
||||||
- Controllers use `@RestController` with request mapping
|
|
||||||
- MyBatis-Plus base mapper: `BaseMapper<T>`
|
|
||||||
- Logical delete field: `validFlag` (1 = active, 0 = deleted)
|
|
||||||
- Use `@EnableAsync` for async processing
|
|
||||||
- JWT token stored in `Authorization` header
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
- Use Vue 3 Composition API (`<script setup>`)
|
|
||||||
- Element Plus components with Chinese locale (zhCn)
|
|
||||||
- API calls through centralized request utility in `src/utils/request.js`
|
|
||||||
- Route-based permission control
|
|
||||||
- Dictionary data through `useDict()` composable
|
|
||||||
- Global properties: `$download`, `$downloadGet`, `$parseTime`, `$resetForm`, `$handleTree`, `$formatDateStr`
|
|
||||||
- CSS in SCSS with global styles in `src/assets/styles/index.scss`
|
|
||||||
- Registered global components: DictTag, Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview, RightToolbar, Editor
|
|
||||||
- Hiprint plugin for printing functionality (window.hiprint)
|
|
||||||
|
|
||||||
## Important Files
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- Startup class: `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
|
||||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
- MyBatis config: `openhis-server-new/openhis-application/src/main/resources/mybatis/mybatis-config.xml`
|
|
||||||
- Parent POM: `openhis-server-new/pom.xml`
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
|
||||||
- Router: `openhis-ui-vue3/src/router/index.js`
|
|
||||||
- Request utils: `openhis-ui-vue3/src/utils/request.js`
|
|
||||||
- Vite config: `openhis-ui-vue3/vite.config.js`
|
|
||||||
- Environment files: `openhis-ui-vue3/.env.*`
|
|
||||||
|
|
||||||
## External Integrations
|
|
||||||
|
|
||||||
- **PostgreSQL 42.2.27**: Primary database
|
|
||||||
- **MySQL Connector 9.4.0**: MySQL database support (alternative)
|
|
||||||
- **Redis**: Caching and session management
|
|
||||||
- **Flowable 6.8.0**: Workflow engine
|
|
||||||
- **LiteFlow 2.12.4.1**: Business rule engine
|
|
||||||
- **Swagger 3.0.0**: API documentation
|
|
||||||
- **Druid 1.2.27**: Database connection pool and monitoring
|
|
||||||
- **Element Plus 2.12.0**: Vue 3 UI component library
|
|
||||||
- **Pinia 2.2.0**: State management
|
|
||||||
- **Vite 5.0.4**: Build tool and dev server
|
|
||||||
- **Hutool 5.3.8**: Java utility library
|
|
||||||
- **Fastjson2 2.0.58**: JSON processing
|
|
||||||
- **Pinyin4j 2.5.1**: Chinese character to Pinyin conversion
|
|
||||||
- **iText 5.5.12**: PDF generation
|
|
||||||
- **Apache POI 4.1.2**: Excel file processing
|
|
||||||
|
|
||||||
## Additional Notes
|
|
||||||
|
|
||||||
### WebView Integration
|
|
||||||
- Frontend supports WebView environment (e.g., embedded in desktop applications)
|
|
||||||
- Chrome WebView integration with C# accessor (`chrome.webview.hostObjects.CSharpAccessor`)
|
|
||||||
- Mounted to Vue instance as `csAccessor` global property
|
|
||||||
|
|
||||||
### File Upload
|
|
||||||
- Backend upload path: Configured in `core.profile` property (default: `D:/home/uploadPath`)
|
|
||||||
- Max file size: 10MB per file, 20MB total request
|
|
||||||
- File upload component: `FileUpload` (global component)
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
- JWT token stored in `Authorization` header
|
|
||||||
- Token configuration: `token.secret`, `token.expireTime`, `token.header`
|
|
||||||
- Password lockout: 5 failed attempts, 10-minute lock time
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
- Backend logs: Configured in `logback.xml`
|
|
||||||
- Debug logging enabled for: `com.openhis`, `com.baomidou.mybatisplus`, `com.alibaba.druid`
|
|
||||||
- Druid slow SQL threshold: 1000ms
|
|
||||||
|
|
||||||
### Code Generation
|
|
||||||
- Backend code generator: `core-generator` module
|
|
||||||
- Access via Swagger or `/tool/gen` route
|
|
||||||
- Uses Velocity templates in `openhis-application/src/main/resources/vm/`
|
|
||||||
674
LICENSE
674
LICENSE
@@ -1,674 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright 2022-2025 湖北天天数链技术有限公司
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
OpenHis Copyright (C) 2022-2025 湖北天天数链技术有限公司
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
# MyBatis-Plus 自动填充处理器优化指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
本文档说明如何优化 `MybastisColumnsHandler` 以确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
|
||||||
|
|
||||||
## 问题背景
|
|
||||||
在 OpenHIS 系统中,当保存实体时可能会遇到以下错误:
|
|
||||||
```
|
|
||||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
|
||||||
```
|
|
||||||
|
|
||||||
这是因为数据库表中的审计字段设置了 NOT NULL 约束,但在某些情况下自动填充机制未能正确设置这些字段。
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
通过优化 `MybastisColumnsHandler` 来确保总是使用当前登录用户的用户名填充 `create_by` 字段,使用当前时间填充 `create_time` 字段。
|
|
||||||
|
|
||||||
## 实施步骤
|
|
||||||
|
|
||||||
### 1. 替换现有处理器
|
|
||||||
将 `D:\his\openhis-server-new\core-framework\src\main\java\com\core\framework\handler\MybastisColumnsHandler.java` 文件替换为以下内容:
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.core.framework.handler;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
|
||||||
import com.core.common.utils.SecurityUtils;
|
|
||||||
import com.core.framework.config.TenantContext;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MyBatis-Plus 自动填充处理器
|
|
||||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
|
|
||||||
// 设置数据新增时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 获取当前登录用户名
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
|
|
||||||
// 填充创建人
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// 确保tenantId被设置
|
|
||||||
Integer tenantId = getCurrentTenantId();
|
|
||||||
if (tenantId == null) {
|
|
||||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
|
||||||
}
|
|
||||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
|
||||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置数据修改时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 填充更新人
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户名
|
|
||||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
|
||||||
*/
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system"; // 默认值
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
} else {
|
|
||||||
// 尝试从请求中获取用户信息
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
|
||||||
// 例如从请求头、session等获取用户信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理流程
|
|
||||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
|
||||||
// 可以考虑记录日志
|
|
||||||
}
|
|
||||||
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前租户 ID
|
|
||||||
*/
|
|
||||||
private Integer getCurrentTenantId() {
|
|
||||||
Integer result = null;
|
|
||||||
|
|
||||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
|
||||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
|
||||||
if (threadLocalTenantId != null) {
|
|
||||||
result = threadLocalTenantId;
|
|
||||||
} else {
|
|
||||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
|
||||||
try {
|
|
||||||
if (SecurityUtils.getAuthentication() != null) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
result = loginUser.getTenantId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理
|
|
||||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
// 尝试从请求头中获取租户ID
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
if (request != null) {
|
|
||||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
|
||||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
|
||||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
|
||||||
// 登录
|
|
||||||
if ("login".equals(requestMethodName)) {
|
|
||||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
|
||||||
try {
|
|
||||||
result = Integer.parseInt(tenantIdHeader);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍然没有获取到租户ID,返回默认值
|
|
||||||
if (result == null) {
|
|
||||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
|
||||||
result = 1; // 默认租户ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 验证处理器是否被正确扫描
|
|
||||||
确保在主应用类或配置类中启用了自动填充功能:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootApplication
|
|
||||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
|
||||||
@EnableTransactionManagement // 启用事务管理
|
|
||||||
public class OpenHisApplication {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(OpenHisApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 测试验证
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldsAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 保存实体
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 验证审计字段是否被正确填充
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotEqualTo("");
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
practitionerMapper.deleteById(practitioner.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **安全上下文**:确保在调用保存方法时用户已登录,这样 `SecurityUtils.getLoginUser()` 才能返回有效的用户对象。
|
|
||||||
|
|
||||||
2. **异常处理**:处理器中包含了异常处理,如果无法获取当前用户,将使用 "system" 作为默认值。
|
|
||||||
|
|
||||||
3. **租户ID**:处理器也处理租户ID的自动填充,这对于多租户系统很重要。
|
|
||||||
|
|
||||||
4. **兼容性**:处理器同时支持驼峰命名(createBy)和下划线命名(create_by)的字段,以兼容不同的配置。
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
通过优化 `MybastisColumnsHandler`,我们可以确保所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误,同时保持数据完整性和审计跟踪功能。
|
|
||||||
184
QWEN.md
184
QWEN.md
@@ -1,184 +0,0 @@
|
|||||||
# Qwen Code Context for HIS (Hospital Information System)
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This is a comprehensive Hospital Information System (HIS) called OpenHIS, built with a Java Spring Boot backend and Vue 3 frontend. The system is designed to manage hospital operations including patient management, appointments, clinical workflows, billing, and administrative tasks.
|
|
||||||
|
|
||||||
### Technology Stack
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
- Java 17
|
|
||||||
- Spring Boot 2.5.15
|
|
||||||
- PostgreSQL (recommended v16.2)
|
|
||||||
- Redis
|
|
||||||
- MyBatis-Plus 3.5.5 for ORM
|
|
||||||
- Druid 1.2.27 for database connection pooling
|
|
||||||
- Flowable 6.8.0 for workflow management
|
|
||||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
|
||||||
- Swagger 3.0.0 for API documentation
|
|
||||||
- JWT 0.9.1 for authentication
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
- Vue 3 with Composition API
|
|
||||||
- Vite 5.0.4 as build tool
|
|
||||||
- Element Plus 2.12.0 as UI component library
|
|
||||||
- Pinia 2.2.0 for state management
|
|
||||||
- Axios 0.27.2 for HTTP requests
|
|
||||||
- Sass for styling
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── openhis-server-new/ # Backend multi-module Maven project
|
|
||||||
│ ├── openhis-application/ # Main application module with startup class
|
|
||||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
|
||||||
│ ├── openhis-common/ # Shared utilities and common code
|
|
||||||
│ ├── core-admin/ # Core administration module
|
|
||||||
│ ├── core-framework/ # Framework configuration and security
|
|
||||||
│ ├── core-system/ # System management module
|
|
||||||
│ ├── core-quartz/ # Scheduled tasks
|
|
||||||
│ ├── core-generator/ # Code generation utilities
|
|
||||||
│ ├── core-common/ # Core utilities
|
|
||||||
│ └── core-flowable/ # Workflow engine integration
|
|
||||||
├── openhis-ui-vue3/ # Vue 3 frontend
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── api/ # API service layer
|
|
||||||
│ │ ├── components/ # Reusable components
|
|
||||||
│ │ ├── router/ # Vue Router configuration
|
|
||||||
│ │ ├── store/ # Pinia state management
|
|
||||||
│ │ ├── utils/ # Utility functions
|
|
||||||
│ │ └── views/ # Page components
|
|
||||||
│ └── vite/ # Vite plugins configuration
|
|
||||||
├── sql/ # Database scripts
|
|
||||||
├── 发版记录/ # Release records
|
|
||||||
└── 迁移记录-DB变更记录/ # Database migration records
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building and Running
|
|
||||||
|
|
||||||
### Backend Setup
|
|
||||||
|
|
||||||
1. **Prerequisites:**
|
|
||||||
- JDK 17 (required)
|
|
||||||
- PostgreSQL v16.2 (required)
|
|
||||||
- Redis (stable version)
|
|
||||||
|
|
||||||
2. **Database Setup:**
|
|
||||||
- Import the database initialization script using Navicat 16 or later
|
|
||||||
- Script location: `sql/20251224init脚本(使用Navicat Premium 17导入).sql`
|
|
||||||
- Configure database connection in `application.yml` or `application-dev.yml`
|
|
||||||
|
|
||||||
3. **Build and Run:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
cd openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run directly from IDE by executing `OpenHisApplication.java`
|
|
||||||
|
|
||||||
### Frontend Setup
|
|
||||||
|
|
||||||
1. **Prerequisites:**
|
|
||||||
- Node.js v16.15 (recommended)
|
|
||||||
|
|
||||||
2. **Installation and Run:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Access the application:**
|
|
||||||
- Frontend: http://localhost:81
|
|
||||||
- Backend API: http://localhost:18080/openhis
|
|
||||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
|
||||||
|
|
||||||
## Development Conventions
|
|
||||||
|
|
||||||
### Backend Architecture
|
|
||||||
|
|
||||||
The backend follows a multi-module Maven architecture with clear separation of concerns:
|
|
||||||
|
|
||||||
1. **openhis-application**: Entry point with `OpenHisApplication.java`
|
|
||||||
- Scans `com.core` and `com.openhis` packages
|
|
||||||
- Configured to run on port 18080 with context path `/openhis`
|
|
||||||
|
|
||||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
|
||||||
- `administration`: Administrative functions
|
|
||||||
- `appointmentmanage`: Appointment management
|
|
||||||
- `check`: Medical examination/checkup
|
|
||||||
- `clinical`: Clinical workflows
|
|
||||||
- `crosssystem`: Cross-system integration
|
|
||||||
- `document`: Document management
|
|
||||||
- `financial`: Financial/billing
|
|
||||||
- `lab`: Laboratory operations
|
|
||||||
- `medication`: Medication management
|
|
||||||
- `triageandqueuemanage`: Patient triage and queue management
|
|
||||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
|
||||||
- `workflow`: Workflow management
|
|
||||||
|
|
||||||
3. **Core Modules** (com.core package):
|
|
||||||
- `core-system`: User, role, menu, and permission management
|
|
||||||
- `core-framework`: Security, exception handling, and framework configurations
|
|
||||||
- `core-common`: Shared utilities and base classes
|
|
||||||
- `core-quartz`: Scheduled task management
|
|
||||||
- `core-generator`: Code generation tools
|
|
||||||
- `core-flowable`: Workflow engine integration
|
|
||||||
- `core-admin`: Administrative functions
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
The frontend uses Vue 3 with composition API and modern tooling:
|
|
||||||
|
|
||||||
1. **State Management:** Pinia for global state with modules for app, dict, permission, settings, tagsView, and user
|
|
||||||
|
|
||||||
2. **Routing:** Vue Router 4.3.0 with public routes and dynamic permission-based routes
|
|
||||||
|
|
||||||
3. **API Integration:** Axios with request/response interceptors and API services organized by module
|
|
||||||
|
|
||||||
4. **Component Architecture:** Element Plus as UI framework with custom components in `src/components/`
|
|
||||||
|
|
||||||
## Key Configuration Files
|
|
||||||
|
|
||||||
### Backend Configuration
|
|
||||||
|
|
||||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
- Environment-specific: `application-dev.yml`, `application-test.yml`, `application-prd.yml`
|
|
||||||
- Database connection settings, Redis configuration, server settings, and MyBatis-Plus configuration
|
|
||||||
|
|
||||||
### Frontend Configuration
|
|
||||||
|
|
||||||
- Environment files: `.env.*` in `openhis-ui-vue3/`
|
|
||||||
- Vite configuration: `vite.config.js`
|
|
||||||
- Main entry: `src/main.js`
|
|
||||||
- Router: `src/router/index.js`
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Adding a New Backend Feature
|
|
||||||
|
|
||||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
|
||||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
|
||||||
3. Create service interface and implementation in `openhis-domain/[module]/service/`
|
|
||||||
4. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
|
||||||
5. Test endpoints via Swagger UI
|
|
||||||
|
|
||||||
### Adding a New Frontend Page
|
|
||||||
|
|
||||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
|
||||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
|
||||||
3. Add route to `openhis-ui-vue3/src/router/index.js`
|
|
||||||
4. Add Pinia store module if state management needed
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
- The system uses logical deletion with a `validFlag` field (1 = active, 0 = deleted)
|
|
||||||
- JWT tokens are stored in the `Authorization` header
|
|
||||||
- The system supports WebView environments with C# accessor integration
|
|
||||||
- File uploads are configured with max 10MB per file and 20MB total request size
|
|
||||||
- Password lockout occurs after 5 failed attempts with a 10-minute lock time
|
|
||||||
- The system includes a code generator accessible via `/tool/gen` route
|
|
||||||
- Printing functionality is implemented using the hiprint plugin
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
# OpenHIS 系统审计字段填充最佳实践
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
本文档介绍如何在 OpenHIS 系统中确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
|
||||||
|
|
||||||
## 自动填充机制
|
|
||||||
|
|
||||||
### 1. 基础实体类
|
|
||||||
所有需要审计字段的实体类都应该继承自 `HisBaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
import com.core.common.core.domain.HisBaseEntity;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
@TableId(type = IdType.AUTO)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
// 其他业务字段...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 自动填充处理器
|
|
||||||
系统使用 `MybastisColumnsHandler` 来自动填充审计字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间和创建人
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = getCurrentUsername(); // 获取当前用户名
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间和更新人
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = getCurrentUsername(); // 获取当前用户名
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system";
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 确保自动填充正常工作的要点
|
|
||||||
|
|
||||||
### 1. 检查实体类继承关系
|
|
||||||
确保所有实体类都正确继承了 `HisBaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 正确的做法
|
|
||||||
public class Practitioner extends HisBaseEntity { ... }
|
|
||||||
|
|
||||||
// 如果不能继承 HisBaseEntity,则需要手动添加审计字段
|
|
||||||
public class CustomEntity {
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
|
||||||
private String createBy;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
|
||||||
private Date createTime;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.UPDATE)
|
|
||||||
private String updateBy;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.UPDATE)
|
|
||||||
private Date updateTime;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 验证安全上下文
|
|
||||||
确保在执行数据库操作时有有效的安全上下文:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerService {
|
|
||||||
public void savePractitioner(Practitioner practitioner) {
|
|
||||||
// 确保调用此方法时用户已登录
|
|
||||||
// SecurityUtils.getLoginUser() 应该能返回有效的 LoginUser 对象
|
|
||||||
|
|
||||||
// MyBatis-Plus 会在保存时自动调用 MybastisColumnsHandler
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 检查配置
|
|
||||||
确保自动填充处理器被正确配置:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# application.yml
|
|
||||||
mybatis-plus:
|
|
||||||
global-config:
|
|
||||||
db-config:
|
|
||||||
# 其他配置...
|
|
||||||
configuration:
|
|
||||||
# 其他配置...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 手动填充(特殊情况)
|
|
||||||
在某些特殊情况下,如果自动填充不工作,可以手动设置:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerService {
|
|
||||||
public void savePractitionerManually(Practitioner practitioner) {
|
|
||||||
// 手动设置审计字段
|
|
||||||
Date now = new Date();
|
|
||||||
String currentUser = getCurrentUsername();
|
|
||||||
|
|
||||||
practitioner.setCreateTime(now);
|
|
||||||
practitioner.setCreateBy(currentUser);
|
|
||||||
practitioner.setUpdateTime(now);
|
|
||||||
practitioner.setUpdateBy(currentUser);
|
|
||||||
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题及解决方案
|
|
||||||
|
|
||||||
### 问题1:自动填充不生效
|
|
||||||
**原因:**
|
|
||||||
- 实体类没有继承 `HisBaseEntity`
|
|
||||||
- `MybastisColumnsHandler` 没有被Spring管理(缺少@Component注解)
|
|
||||||
- 没有有效的安全上下文
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 确保实体类继承 `HisBaseEntity`
|
|
||||||
- 检查 `MybastisColumnsHandler` 是否有 `@Component` 注解
|
|
||||||
- 确保在调用保存方法时用户已登录
|
|
||||||
|
|
||||||
### 问题2:获取不到当前用户
|
|
||||||
**原因:**
|
|
||||||
- 用户未登录
|
|
||||||
- 安全上下文配置错误
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 在调用保存方法前确保用户已登录
|
|
||||||
- 检查安全配置是否正确
|
|
||||||
|
|
||||||
### 问题3:批量操作时审计字段未填充
|
|
||||||
**原因:**
|
|
||||||
- 批量操作可能绕过了自动填充机制
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 对于批量操作,手动设置审计字段
|
|
||||||
- 或者使用 MyBatis-Plus 的批量操作方法,确保它们支持自动填充
|
|
||||||
|
|
||||||
## 测试验证
|
|
||||||
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldsAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 保存实体
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 验证审计字段是否被正确填充
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
practitionerMapper.deleteById(practitioner.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
通过遵循以上最佳实践,可以确保 OpenHIS 系统中的所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误。
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# 关于数据库审计字段(create_by, create_time等)的处理方案
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
在使用OpenHIS系统时,可能会遇到如下错误:
|
|
||||||
```
|
|
||||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
|
||||||
```
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 数据库表中的审计字段(如create_by, create_time)设置了NOT NULL约束
|
|
||||||
2. 应用程序层面使用了MyBatis-Plus的自动填充功能来设置这些字段
|
|
||||||
3. 当自动填充机制失效时,就会出现违反非空约束的错误
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
|
|
||||||
### 方案一:修复自动填充机制(推荐)
|
|
||||||
系统已经实现了自动填充机制,位于 `MybastisColumnsHandler.java`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 设置数据新增时候的,字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 同时填充驼峰和下划线命名的字段,以兼容不同的配置
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = "system";
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
// 使用 fillStrategy 确保即使字段为 null 也会被填充
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
// 如果 strictInsertFill 没有生效,使用 setFieldValByName 强制设置
|
|
||||||
if (metaObject.hasGetter("createBy") && metaObject.getValue("createBy") == null) {
|
|
||||||
this.setFieldValByName("createBy", username, metaObject);
|
|
||||||
}
|
|
||||||
if (metaObject.hasGetter("create_by") && metaObject.getValue("create_by") == null) {
|
|
||||||
this.setFieldValByName("create_by", username, metaObject);
|
|
||||||
}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
确保所有实体类都继承自 `HisBaseEntity` 或 `BaseEntity`,这样就能自动获得审计字段。
|
|
||||||
|
|
||||||
### 方案二:移除数据库约束(谨慎使用)
|
|
||||||
如果确实需要允许审计字段为NULL,可以移除数据库约束:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 移除 adm_practitioner 表中 create_by 列的 NOT NULL 约束
|
|
||||||
ALTER TABLE "public"."adm_practitioner"
|
|
||||||
ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
|
|
||||||
-- 同样处理 create_time 列(如果需要)
|
|
||||||
ALTER TABLE "public"."adm_practitioner"
|
|
||||||
ALTER COLUMN "create_time" DROP NOT NULL;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案三:批量修复所有表的约束
|
|
||||||
如果多个表都存在这个问题,可以使用以下脚本:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 为所有表的审计字段移除NOT NULL约束
|
|
||||||
-- 注意:执行前请备份数据库!
|
|
||||||
|
|
||||||
-- 1. 检查所有包含审计字段的表
|
|
||||||
SELECT
|
|
||||||
table_name,
|
|
||||||
column_name,
|
|
||||||
is_nullable
|
|
||||||
FROM
|
|
||||||
information_schema.columns
|
|
||||||
WHERE
|
|
||||||
column_name IN ('create_by', 'create_time', 'update_by', 'update_time')
|
|
||||||
AND table_schema = 'public'
|
|
||||||
AND is_nullable = 'NO'; -- NO 表示 NOT NULL 约束
|
|
||||||
|
|
||||||
-- 2. 根据需要移除特定表的约束
|
|
||||||
-- 示例:移除多个表的create_by约束
|
|
||||||
ALTER TABLE "public"."adm_practitioner" ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
ALTER TABLE "public"."adm_patient" ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
-- 添加更多表的处理...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 确保实体类继承基础类
|
|
||||||
所有实体类应继承 `HisBaseEntity` 或 `BaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
// 其他字段...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查安全上下文
|
|
||||||
确保在保存数据时有有效的安全上下文,这样自动填充处理器才能获取到当前用户信息。
|
|
||||||
|
|
||||||
### 3. 验证自动填充配置
|
|
||||||
确保 `MybastisColumnsHandler` 在Spring容器中被正确注册(使用@Component注解)。
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
- 推荐保持数据库中的NOT NULL约束,确保数据完整性
|
|
||||||
- 依赖MyBatis-Plus的自动填充机制来设置审计字段
|
|
||||||
- 确保所有实体类继承基础实体类
|
|
||||||
- 在必要时才考虑移除数据库约束
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
-- 检查流水号(display_order)是否按“科室+医生+当天”正确递增
|
|
||||||
--
|
|
||||||
-- 说明:
|
|
||||||
-- 1. display_order 存的是纯数字(1, 2, 3...),不带时间戳前缀
|
|
||||||
-- 2. 时间戳前缀(如 20260109)是在前端显示时加上的
|
|
||||||
-- 3. 后端用 Redis key "ORG-{科室ID}-DOC-{医生ID}" 按天自增
|
|
||||||
--
|
|
||||||
-- 如何判断逻辑是否正确:
|
|
||||||
-- 同一科室、同一医生、同一天的记录,display_order 应该递增(1, 2, 3...)
|
|
||||||
-- 不同科室、不同医生、不同天的记录,可能都是 1(这是正常的)
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询1:按“科室+医生+日期”分组,看每组内的 display_order 是否递增
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
DATE(start_time) AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
COUNT(*) AS 该组记录数,
|
|
||||||
MIN(display_order) AS 最小序号,
|
|
||||||
MAX(display_order) AS 最大序号,
|
|
||||||
STRING_AGG(display_order::text, ', ' ORDER BY start_time) AS 序号列表,
|
|
||||||
STRING_AGG(id::text, ', ' ORDER BY start_time) AS 记录ID列表
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days' -- 只看最近7天
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
GROUP BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY 日期 DESC, 科室ID, 医生ID;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询2:详细查看每条记录,看同组内的序号是否连续
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
id AS 记录ID,
|
|
||||||
DATE(start_time) AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
start_time AS 挂号时间,
|
|
||||||
display_order AS 流水号,
|
|
||||||
-- 计算:同组内的序号应该是 1, 2, 3...,看是否有重复或跳号
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) AS 应该是第几个,
|
|
||||||
CASE
|
|
||||||
WHEN display_order = ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) THEN '✓ 正常'
|
|
||||||
ELSE '✗ 异常'
|
|
||||||
END AS 是否正常
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
ORDER BY DATE(start_time) DESC, organization_id, registrar_id, start_time;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询3:只看今天的数据(最直观)
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
id AS 记录ID,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
start_time AS 挂号时间,
|
|
||||||
display_order AS 流水号
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND DATE(start_time) = CURRENT_DATE
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
ORDER BY organization_id, registrar_id, start_time;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询4:发现问题 - 找出同组内 display_order 重复的记录
|
|
||||||
-- ========================================
|
|
||||||
WITH ranked AS (
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
DATE(start_time) AS reg_date,
|
|
||||||
organization_id,
|
|
||||||
registrar_id,
|
|
||||||
start_time,
|
|
||||||
display_order,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) AS should_be_order
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
reg_date AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
COUNT(*) AS 重复数量,
|
|
||||||
STRING_AGG(id::text || '->' || display_order::text, ', ') AS 问题记录
|
|
||||||
FROM ranked
|
|
||||||
WHERE display_order != should_be_order
|
|
||||||
GROUP BY reg_date, organization_id, registrar_id
|
|
||||||
ORDER BY reg_date DESC;
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
-- 会诊邀请对象表
|
|
||||||
CREATE TABLE hisdev.consultation_invited (
|
|
||||||
id BIGINT NOT NULL PRIMARY KEY,
|
|
||||||
consultation_request_id BIGINT NOT NULL,
|
|
||||||
invited_department_id BIGINT,
|
|
||||||
invited_department_name VARCHAR(100),
|
|
||||||
invited_physician_id BIGINT,
|
|
||||||
invited_physician_name VARCHAR(50),
|
|
||||||
invited_status SMALLINT DEFAULT 0,
|
|
||||||
confirm_time TIMESTAMP,
|
|
||||||
confirm_opinion TEXT,
|
|
||||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
create_by VARCHAR(64),
|
|
||||||
update_by VARCHAR(64),
|
|
||||||
tenant_id BIGINT,
|
|
||||||
is_deleted SMALLINT DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 添加注释
|
|
||||||
COMMENT ON TABLE hisdev.consultation_invited IS '会诊邀请对象表';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.id IS '主键ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.consultation_request_id IS '会诊申请ID(外键)';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_id IS '邀请科室ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_name IS '邀请科室名称';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_id IS '邀请医生ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_name IS '邀请医生姓名';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_status IS '邀请状态(0-待确认,1-已确认,2-已拒绝)';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_time IS '确认时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_opinion IS '确认意见';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.create_time IS '创建时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.update_time IS '更新时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.create_by IS '创建人';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.update_by IS '更新人';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.tenant_id IS '租户ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.is_deleted IS '删除标识(0-未删除,1-已删除)';
|
|
||||||
|
|
||||||
-- 创建索引
|
|
||||||
CREATE INDEX idx_consultation_request_id ON hisdev.consultation_invited(consultation_request_id);
|
|
||||||
CREATE INDEX idx_invited_physician_id ON hisdev.consultation_invited(invited_physician_id);
|
|
||||||
CREATE INDEX idx_tenant_id ON hisdev.consultation_invited(tenant_id);
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# 检查后端API返回数据结构
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
尽管我们更新了DTO和SQL查询,前端仍然没有显示创建时间,可能的原因:
|
|
||||||
1. API响应中没有包含createTime字段
|
|
||||||
2. SQL查询没有正确返回createTime字段
|
|
||||||
3. 数据库中createTime字段本身为null
|
|
||||||
4. JSON序列化问题
|
|
||||||
|
|
||||||
## 检查步骤
|
|
||||||
|
|
||||||
### 1. 检查数据库中数据
|
|
||||||
首先检查数据库中sys_user表的createTime字段是否正确填充:
|
|
||||||
```sql
|
|
||||||
SELECT user_id, user_name, nick_name, create_time
|
|
||||||
FROM sys_user
|
|
||||||
WHERE create_time IS NOT NULL
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查API端点
|
|
||||||
API端点是:GET /base-data-manage/practitioner/user-practitioner-page
|
|
||||||
这个端点在PractitionerController中定义,调用practitionerAppService.getUserPractitionerPage()
|
|
||||||
|
|
||||||
### 3. 检查SQL查询
|
|
||||||
在PractitionerAppMapper.xml中,我们已经添加了createTime字段:
|
|
||||||
```xml
|
|
||||||
T2.create_time
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 验证DTO映射
|
|
||||||
UserAndPractitionerDto中已添加createTime字段:
|
|
||||||
```java
|
|
||||||
private Date createTime;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 检查JSON序列化
|
|
||||||
检查是否有@JsonFormat注解或其他序列化配置问题
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
# 深度排查 MyBatis-Plus 自动填充不生效问题
|
|
||||||
|
|
||||||
## 问题概述
|
|
||||||
尽管对 MyBatis-Plus 的自动填充处理器进行了多次优化和配置,但 `create_by` 和 `create_time` 字段仍然没有被自动填充。
|
|
||||||
|
|
||||||
## 深度排查步骤
|
|
||||||
|
|
||||||
### 1. 检查 AOP 代理是否生效
|
|
||||||
MyBatis-Plus 的自动填充功能依赖于 AOP 代理。如果实体类的方法被直接调用而非通过代理调用,自动填充可能不会生效。
|
|
||||||
|
|
||||||
### 2. 验证 Service 层实现
|
|
||||||
确保使用的是 MyBatis-Plus 提供的通用 Service 方法,而不是自定义的 SQL。
|
|
||||||
|
|
||||||
### 3. 检查 @TableField 注解配置
|
|
||||||
确认实体类中的字段注解配置正确。
|
|
||||||
|
|
||||||
### 4. 检查事务配置
|
|
||||||
某些事务配置可能会影响 AOP 代理的生效。
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
|
|
||||||
### 方案一:在 Service 层手动设置审计字段
|
|
||||||
|
|
||||||
创建一个工具类来统一处理审计字段的设置:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class AuditFieldUtil {
|
|
||||||
|
|
||||||
public static void setCreateInfo(Object entity) {
|
|
||||||
if (entity == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 使用反射设置字段值
|
|
||||||
Field createByField = getField(entity.getClass(), "createBy");
|
|
||||||
if (createByField != null) {
|
|
||||||
createByField.setAccessible(true);
|
|
||||||
if (createByField.get(entity) == null || "".equals(createByField.get(entity))) {
|
|
||||||
createByField.set(entity, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Field createTimeField = getField(entity.getClass(), "createTime");
|
|
||||||
if (createTimeField != null) {
|
|
||||||
createTimeField.setAccessible(true);
|
|
||||||
if (createTimeField.get(entity) == null) {
|
|
||||||
createTimeField.set(entity, currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理下划线命名的字段
|
|
||||||
Field createByFieldUnderscore = getField(entity.getClass(), "create_by");
|
|
||||||
if (createByFieldUnderscore != null) {
|
|
||||||
createByFieldUnderscore.setAccessible(true);
|
|
||||||
if (createByFieldUnderscore.get(entity) == null || "".equals(createByFieldUnderscore.get(entity))) {
|
|
||||||
createByFieldUnderscore.set(entity, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Field createTimeFieldUnderscore = getField(entity.getClass(), "create_time");
|
|
||||||
if (createTimeFieldUnderscore != null) {
|
|
||||||
createTimeFieldUnderscore.setAccessible(true);
|
|
||||||
if (createTimeFieldUnderscore.get(entity) == null) {
|
|
||||||
createTimeFieldUnderscore.set(entity, currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("设置审计字段时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, String fieldName) {
|
|
||||||
try {
|
|
||||||
return clazz.getDeclaredField(fieldName);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
if (clazz.getSuperclass() != null) {
|
|
||||||
return getField(clazz.getSuperclass(), fieldName);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后在 Service 实现中使用:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
|
||||||
implements IPractitionerService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuditFieldUtil auditFieldUtil;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public boolean save(Practitioner entity) {
|
|
||||||
// 在保存前手动设置审计字段
|
|
||||||
auditFieldUtil.setCreateInfo(entity);
|
|
||||||
return super.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public boolean saveBatch(Collection<Practitioner> entityList) {
|
|
||||||
entityList.forEach(auditFieldUtil::setCreateInfo);
|
|
||||||
return super.saveBatch(entityList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案二:重写 BaseMapper 方法
|
|
||||||
|
|
||||||
如果 Service 层的方法不起作用,可以直接在 Mapper 层处理:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Mapper
|
|
||||||
public interface PractitionerMapper extends BaseMapper<Practitioner> {
|
|
||||||
|
|
||||||
@Insert({
|
|
||||||
"<script>",
|
|
||||||
"INSERT INTO adm_practitioner (",
|
|
||||||
"id, active_flag, name, name_json, gender_enum, birth_date, deceased_date,",
|
|
||||||
"phone, address, address_province, address_city, address_district, address_street,",
|
|
||||||
"address_json, py_str, wb_str, bus_no, yb_no, user_id, tenant_id, delete_flag,",
|
|
||||||
"create_by, create_time, update_by, update_time, org_id,",
|
|
||||||
"phar_prac_cert_no, prsc_dr_cert_code, dr_profttl_code, kpd_code, signature, pos_no",
|
|
||||||
") VALUES (",
|
|
||||||
"#{id}, #{activeFlag}, #{name}, #{nameJson}, #{genderEnum}, #{birthDate}, #{deceasedDate},",
|
|
||||||
"#{phone}, #{address}, #{addressProvince}, #{addressCity}, #{addressDistrict}, #{addressStreet},",
|
|
||||||
"#{addressJson}, #{pyStr}, #{wbStr}, #{busNo}, #{ybNo}, #{userId}, #{tenantId}, #{deleteFlag},",
|
|
||||||
"#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{orgId},",
|
|
||||||
"#{pharPracCertNo}, #{prscDrCertCode}, #{drProfttlCode}, #{kpdCode}, #{signature}, #{posNo}",
|
|
||||||
")",
|
|
||||||
"</script>"
|
|
||||||
})
|
|
||||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
|
||||||
int insertWithAuditFields(Practitioner record);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案三:使用 MyBatis 拦截器
|
|
||||||
|
|
||||||
创建一个 MyBatis 拦截器来自动填充字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Intercepts({
|
|
||||||
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
|
|
||||||
})
|
|
||||||
@Component
|
|
||||||
public class AuditFieldInterceptor implements Interceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object intercept(Invocation invocation) throws Throwable {
|
|
||||||
Object[] args = invocation.getArgs();
|
|
||||||
MappedStatement ms = (MappedStatement) args[0];
|
|
||||||
Object parameter = args[1];
|
|
||||||
|
|
||||||
String sqlCommandType = ms.getSqlCommandType().toString();
|
|
||||||
|
|
||||||
if ("INSERT".equals(sqlCommandType)) {
|
|
||||||
setCreateAuditFields(parameter);
|
|
||||||
} else if ("UPDATE".equals(sqlCommandType)) {
|
|
||||||
setUpdateAuditFields(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return invocation.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCreateAuditFields(Object parameter) {
|
|
||||||
if (parameter == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 设置 createBy 和 createTime
|
|
||||||
setFieldValue(parameter, "createBy", username);
|
|
||||||
setFieldValue(parameter, "create_time", username);
|
|
||||||
setFieldValue(parameter, "createTime", currentTime);
|
|
||||||
setFieldValue(parameter, "create_time", currentTime);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpdateAuditFields(Object parameter) {
|
|
||||||
if (parameter == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 设置 updateBy 和 updateTime
|
|
||||||
setFieldValue(parameter, "updateBy", username);
|
|
||||||
setFieldValue(parameter, "update_by", username);
|
|
||||||
setFieldValue(parameter, "updateTime", currentTime);
|
|
||||||
setFieldValue(parameter, "update_time", currentTime);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFieldValue(Object obj, String fieldName, Object value) {
|
|
||||||
try {
|
|
||||||
Field field = getField(obj.getClass(), fieldName);
|
|
||||||
if (field != null) {
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.get(obj) == null) { // 只在原值为 null 时设置
|
|
||||||
field.set(obj, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略无法设置的字段
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Field getField(Class<?> clazz, String fieldName) {
|
|
||||||
try {
|
|
||||||
return clazz.getDeclaredField(fieldName);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
if (clazz.getSuperclass() != null) {
|
|
||||||
return getField(clazz.getSuperclass(), fieldName);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object plugin(Object target) {
|
|
||||||
if (target instanceof Executor) {
|
|
||||||
return Plugin.wrap(target, this);
|
|
||||||
} else {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProperties(Properties properties) {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 推荐实施顺序
|
|
||||||
|
|
||||||
1. 首先尝试方案一(Service 层手动设置),这是最简单且可控的方式
|
|
||||||
2. 如果方案一不行,尝试方案三(MyBatis 拦截器),它在更底层起作用
|
|
||||||
3. 方案二是最后的选择,需要重写具体的插入逻辑
|
|
||||||
|
|
||||||
## 验证方法
|
|
||||||
|
|
||||||
创建一个测试来验证自动填充是否生效:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IPractitionerService practitionerService;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 记录保存前的值
|
|
||||||
System.out.println("保存前 - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("保存前 - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
boolean success = practitionerService.save(practitioner);
|
|
||||||
|
|
||||||
// 从数据库重新查询以验证
|
|
||||||
Practitioner saved = practitionerService.getById(practitioner.getId());
|
|
||||||
System.out.println("保存后 - createBy: " + saved.getCreateBy());
|
|
||||||
System.out.println("保存后 - createTime: " + saved.getCreateTime());
|
|
||||||
|
|
||||||
Assertions.assertTrue(success);
|
|
||||||
Assertions.assertNotNull(saved.getCreateBy());
|
|
||||||
Assertions.assertNotNull(saved.getCreateTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
通过这些方案,应该能够解决自动填充不生效的问题。
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# 诊断 MyBatis-Plus 自动填充问题
|
|
||||||
|
|
||||||
## 问题现象
|
|
||||||
尽管 `MybastisColumnsHandler` 已经实现并配置了自动填充功能,但 `create_by` 和 `create_time` 字段仍然没有被正确填充。
|
|
||||||
|
|
||||||
## 可能的原因及解决方案
|
|
||||||
|
|
||||||
### 1. 检查组件扫描配置
|
|
||||||
确保 `MybastisColumnsHandler` 类被Spring容器正确管理:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component // 确保这个注解存在
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查包扫描路径
|
|
||||||
在主应用类中确保扫描到了处理器所在的包:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootApplication
|
|
||||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
|
||||||
@ComponentScan(basePackages = {"com.core", "com.openhis"}) // 确保扫描到处理器
|
|
||||||
public class OpenHisApplication {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(OpenHisApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 验证实体类配置
|
|
||||||
确保实体类正确继承了 `HisBaseEntity` 并且字段上有正确的注解:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
// 不需要在子类中重复定义 createBy, createTime 等字段
|
|
||||||
// 因为它们已在 HisBaseEntity 中定义并带有 @TableField(fill = FieldFill.INSERT)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 检查安全上下文
|
|
||||||
自动填充处理器依赖于安全上下文来获取当前用户。确保在执行保存操作时用户已登录:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 在保存之前,确保用户已登录
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser == null) {
|
|
||||||
// 用户未登录,可能需要手动设置审计字段
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 手动测试自动填充
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AutoFillTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 检查在保存前字段是否为空
|
|
||||||
System.out.println("Before insert - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("Before insert - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
// 执行插入操作
|
|
||||||
int result = practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 检查保存后字段是否被填充
|
|
||||||
System.out.println("After insert - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("After insert - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
assertThat(result).isEqualTo(1);
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 临时解决方案
|
|
||||||
如果自动填充仍然不工作,可以在服务层手动设置这些字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
|
||||||
implements IPractitionerService {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void savePractitioner(Practitioner practitioner) {
|
|
||||||
// 手动设置审计字段
|
|
||||||
if (practitioner.getCreateBy() == null || practitioner.getCreateBy().isEmpty()) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
practitioner.setCreateBy(loginUser.getUsername());
|
|
||||||
} else {
|
|
||||||
practitioner.setCreateBy("system"); // 默认值
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (practitioner.getCreateTime() == null) {
|
|
||||||
practitioner.setCreateTime(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行保存操作
|
|
||||||
this.save(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 检查 MyBatis-Plus 版本兼容性
|
|
||||||
确保使用的 MyBatis-Plus 版本与自动填充功能兼容。当前项目使用的是 3.5.5 版本,应该支持自动填充功能。
|
|
||||||
|
|
||||||
### 8. 调试自动填充处理器
|
|
||||||
在 `MybastisColumnsHandler` 中添加日志来调试是否被调用:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
System.out.println("MybastisColumnsHandler.insertFill() called"); // 调试日志
|
|
||||||
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
System.out.println("Setting createBy to: " + username); // 调试日志
|
|
||||||
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// ... 其他代码
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
通过以上步骤,应该能够诊断并解决自动填充不工作的问题。
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
package com.core.framework.handler;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
|
||||||
import com.core.common.utils.SecurityUtils;
|
|
||||||
import com.core.framework.config.TenantContext;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MyBatis-Plus 自动填充处理器
|
|
||||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
|
|
||||||
// 设置数据新增时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 获取当前登录用户名
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
|
|
||||||
// 填充创建人
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// 确保tenantId被设置
|
|
||||||
Integer tenantId = getCurrentTenantId();
|
|
||||||
if (tenantId == null) {
|
|
||||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
|
||||||
}
|
|
||||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
|
||||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置数据修改时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 填充更新人
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户名
|
|
||||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
|
||||||
*/
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system"; // 默认值
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
} else {
|
|
||||||
// 尝试从请求中获取用户信息
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
|
||||||
// 例如从请求头、session等获取用户信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理流程
|
|
||||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
|
||||||
// 可以考虑记录日志
|
|
||||||
}
|
|
||||||
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前租户 ID
|
|
||||||
*/
|
|
||||||
private Integer getCurrentTenantId() {
|
|
||||||
Integer result = null;
|
|
||||||
|
|
||||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
|
||||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
|
||||||
if (threadLocalTenantId != null) {
|
|
||||||
result = threadLocalTenantId;
|
|
||||||
} else {
|
|
||||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
|
||||||
try {
|
|
||||||
if (SecurityUtils.getAuthentication() != null) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
result = loginUser.getTenantId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理
|
|
||||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
// 尝试从请求头中获取租户ID
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
if (request != null) {
|
|
||||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
|
||||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
|
||||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
|
||||||
// 登录
|
|
||||||
if ("login".equals(requestMethodName)) {
|
|
||||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
|
||||||
try {
|
|
||||||
result = Integer.parseInt(tenantIdHeader);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍然没有获取到租户ID,返回默认值
|
|
||||||
if (result == null) {
|
|
||||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
|
||||||
result = 1; // 默认租户ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.openhis;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 示例类 - 引用 OpenHisApplication
|
|
||||||
*/
|
|
||||||
public class Fragment {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// 引用 OpenHisApplication
|
|
||||||
Class<?> applicationClass = com.openhis.OpenHisApplication.class;
|
|
||||||
System.out.println("Application class: " + applicationClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
## 报卡管理界面PRD文档
|
||||||
|
|
||||||
|
### 一、页面概述
|
||||||
|
|
||||||
|
**页面名称**:报卡管理界面
|
||||||
|
**页面目标**:提供传染病报卡的审核、管理、筛选及批量操作功能,帮助疾控人员高效完成报卡审核工作
|
||||||
|
**适用场景**:疾控中心工作人员日常审核医疗机构上报的传染病病例
|
||||||
|
|
||||||
|
- 医院CDC管理员日常审核传染病报卡
|
||||||
|
- 批量处理待审核/退回的报卡
|
||||||
|
- 按条件筛选统计报卡数据
|
||||||
|
**页面类型**:数据管理列表页(含详情抽屉)
|
||||||
|
|
||||||
|
**核心功能**:
|
||||||
|
|
||||||
|
1. 报卡数据概览统计(今日待审/本月失败/本月成功/本月上报)
|
||||||
|
2. 多维度筛选报卡数据(时间/状态/科室/来源等)
|
||||||
|
3. 报卡列表展示与批量操作(审核/退回/导出)
|
||||||
|
4. 报卡详情查看与单条审核
|
||||||
|
5. 审核记录追溯与意见填写
|
||||||
|
|
||||||
|
**用户价值**:
|
||||||
|
|
||||||
|
- 疾控人员可快速处理待审报卡
|
||||||
|
- 支持批量审核提升工作效率
|
||||||
|
- 完整记录审核过程便于追溯
|
||||||
|
- 多维度筛选快速定位目标报卡
|
||||||
|
**原型图地址**:https://static.pm-ai.cn/prototype/20260206/cc9991b716df0303fa3459042e33a1ea/index.html
|
||||||
|
**流程图**:
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A["进入报卡管理界面"] --> B["查看报卡概览统计"]
|
||||||
|
B --> C["点击统计卡片"]
|
||||||
|
C --> D["悬停统计卡片"]
|
||||||
|
D --> E["自动设置筛选项"]
|
||||||
|
B --> F["鼠标悬停"]
|
||||||
|
F --> G["筛选报卡数据"]
|
||||||
|
G --> H["卡片上浮+阴影"]
|
||||||
|
E --> I["设置筛选条件"]
|
||||||
|
I --> K["点击重置按钮"]
|
||||||
|
K --> J["清空条件"]
|
||||||
|
I --> L["点击查询按钮"]
|
||||||
|
L --> M["触发筛选"]
|
||||||
|
M --> N["操作报卡列表"]
|
||||||
|
N --> O["勾选报卡"]
|
||||||
|
N --> P["处理单条报卡"]
|
||||||
|
O --> Q["批量操作报卡"]
|
||||||
|
P --> R["点击单条审核"]
|
||||||
|
P --> S["点击单条查看"]
|
||||||
|
Q --> T["点击批量审核"]
|
||||||
|
Q --> U["点击批量退回"]
|
||||||
|
R --> V{"报卡状态"}
|
||||||
|
S --> W{"报卡状态"}
|
||||||
|
V -- 已审核 --> X["打开查看抽屉"]
|
||||||
|
V -- 待审核/失败 --> Y["校验选择状态"]
|
||||||
|
W -- 任意 --> X
|
||||||
|
T --> Z["校验选择状态"]
|
||||||
|
U --> AA["校验选择状态"]
|
||||||
|
Y -- 选择有效 --> AB["打开审核抽屉"]
|
||||||
|
AB-->AN["展示审核记录"]
|
||||||
|
AB-->AO["展示审核记录"]
|
||||||
|
Y -- 未选择 --> AC["提示请选择报卡"]
|
||||||
|
Z -- 选择有效 --> AD{"包含已审核项"}
|
||||||
|
Z -- 未选择 --> AE["提示请选择报卡"]
|
||||||
|
AD -- 是 --> AF["提示只能选择待审核报卡"]
|
||||||
|
AD -- 否 --> AG["弹出审核弹窗"]
|
||||||
|
AA -- 选择有效 --> AH{"包含已审核项"}
|
||||||
|
AA -- 未选择 --> AI["提示请选择报卡"]
|
||||||
|
AH -- 是 --> AF
|
||||||
|
AH -- 否 --> AJ["弹出退回弹窗"]
|
||||||
|
AG --> AK["加载报卡详情"]
|
||||||
|
AJ --> AL["加载报卡详情"]
|
||||||
|
AK -- 加载失败 --> AM["提示数据加载失败"]
|
||||||
|
AL -- 加载失败 --> AM
|
||||||
|
AK -- 成功 --> AN["展示审核记录"]
|
||||||
|
AL -- 成功 --> AO["展示审核记录"]
|
||||||
|
AN --> AP["填写审核意见"]
|
||||||
|
AO --> AQ["填写退回原因"]
|
||||||
|
AP --> AR{"意见是否为空"}
|
||||||
|
AQ --> AS{"原因是否为空"}
|
||||||
|
AR -- 是 --> AT["红字提示必填"]
|
||||||
|
AS -- 是 --> AU["红字提示必填"]
|
||||||
|
AR -- 否 --> AV["点击确认审核"]
|
||||||
|
AS -- 否 --> AW["点击确认退回"]
|
||||||
|
AV --> AX["点击审核通过"]
|
||||||
|
AW --> AY["点击退回修改"]
|
||||||
|
AX --> AZ["更新报卡状态"]
|
||||||
|
AY --> BA["更新报卡状态"]
|
||||||
|
AZ --> BB["生成审核记录"]
|
||||||
|
BA --> BC["生成审核记录"]
|
||||||
|
BB --> BD["按钮置灰"]
|
||||||
|
BC --> BD["按钮置灰"]
|
||||||
|
AX --> BE["提示操作失败,请检查网络"]
|
||||||
|
AY --> BE
|
||||||
|
AZ --> BF["刷新表格数据"]
|
||||||
|
BA --> BF
|
||||||
|
BF --> BG["刷新行状态"]
|
||||||
|
BG --> BH["结束"]
|
||||||
|
BE --> BH
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 二、整体布局分析
|
||||||
|
|
||||||
|
**页面宽度**:自适应布局
|
||||||
|
**主要区域划分**:
|
||||||
|
|
||||||
|
1. **顶部导航栏**(固定高度60px)
|
||||||
|
2. **报卡管理概览区**(统计卡片+快捷操作,高度自适应)
|
||||||
|
3. **筛选控制区**(多条件组合筛选,折叠式布局)
|
||||||
|
4. **报卡列表区**(表格展示,占主体60%高度)
|
||||||
|
**布局特点**:上下层级结构,筛选区支持折叠/展开
|
||||||
|
5. **抽屉详情区**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 三、页面区域详细描述
|
||||||
|
|
||||||
|
#### 1. 顶部导航栏
|
||||||
|
|
||||||
|
**区域位置**:页面顶部固定
|
||||||
|
**区域尺寸**:高度60px,100%宽度
|
||||||
|
**区域功能**:展示系统标识和用户信息
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **系统Logo**
|
||||||
|
|
||||||
|
- - 元素类型:图标+文字组合
|
||||||
|
- 显示内容:"CDC"图标+"报卡管理"文字
|
||||||
|
- 样式特征:蓝色主色调(#4a6fa5),左侧对齐
|
||||||
|
|
||||||
|
#### 2. 报卡管理概览
|
||||||
|
|
||||||
|
**区域位置**:导航栏下方
|
||||||
|
|
||||||
|
**区域尺寸**:100%宽度,自适应高度
|
||||||
|
**区域功能**:关键数据统计与快速操作入口
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **统计卡片组**(4个)
|
||||||
|
|
||||||
|
- - 展示方式:网格布局(4列)
|
||||||
|
- 数据字段:
|
||||||
|
|
||||||
|
| **字段名** | **类型** | **示例值** | **可操作** | **计算逻辑** |
|
||||||
|
| ------------ | -------- | ---------- | ---------- | ------------------------- |
|
||||||
|
| 今日待审核 | 数字 | 12 | 可点击 | 当天created_at+待审状态 |
|
||||||
|
| 本月审核失败 | 数字 | 3 | 可点击 | 当月created_at+失败状态 |
|
||||||
|
| 本月审核成功 | 数字 | 2 | 可点击 | 当月created_at+成功状态 |
|
||||||
|
| 本月已上报 | 数字 | 156 | 可点击 | 当月created_at+已上报状态 |
|
||||||
|
|
||||||
|
o 交互行为:
|
||||||
|
|
||||||
|
- - - 悬停:卡片上浮5px+阴影加深
|
||||||
|
- 点击:自动设置对应筛选项
|
||||||
|
|
||||||
|
- 样式特征:左侧状态色条(蓝/橙/红/绿)
|
||||||
|
|
||||||
|
#### 3. 筛选控制区
|
||||||
|
|
||||||
|
**区域功能**:多维度组合筛选报卡数据
|
||||||
|
|
||||||
|
**区域尺寸**:100%宽度,高度自适应(展开状态)
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **筛选条件组**(横向排列→移动端垂直堆叠)
|
||||||
|
|
||||||
|
- - 登记来源(下拉单选):全部/门诊/住院/急诊/体检
|
||||||
|
- 上报时间范围(双日期选择器)--默认值:最近一个月
|
||||||
|
- 患者姓名(文本输入)
|
||||||
|
- 审核状态(下拉单选):全部/待审核/审核通过/审核失败/已上报
|
||||||
|
- 上报科室(树形下拉多选)--全部科室/取值于《科室管理》adm_organization表
|
||||||
|
|
||||||
|
- **操作按钮**:
|
||||||
|
|
||||||
|
- - “查询”(主按钮,触发筛选)
|
||||||
|
- “重置”(次要按钮,清空条件)
|
||||||
|
|
||||||
|
#### 4. 报卡列表区
|
||||||
|
|
||||||
|
**区域功能**:展示报卡数据及操作入口
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **表格头部**
|
||||||
|
|
||||||
|
- - 全选复选框(联动所有行选择状态)
|
||||||
|
- 列标题:报卡名称/病种名称/患者信息等11列
|
||||||
|
|
||||||
|
- **快捷操作按钮组**
|
||||||
|
|
||||||
|
- - 包含按钮:
|
||||||
|
|
||||||
|
- - “批量审核”蓝色按钮,(需选择条目)点击弹出填写审核备注弹窗
|
||||||
|
- “批量退回修改”橙色警示按钮,(需选择条目)点击弹出退回原因填写弹窗
|
||||||
|
- “导出当前”(按筛选条件)
|
||||||
|
|
||||||
|
- **表格行**
|
||||||
|
|
||||||
|
- - 数据字段:
|
||||||
|
|
||||||
|
取值于传染病报卡表(infectious_card)
|
||||||
|
|
||||||
|
| **列名** | **数据类型** | **示例值** | **说明** |
|
||||||
|
| -------- | ------------ | ---------------- | --------------- |
|
||||||
|
| 选择框 | boolean | - | 带全选功能 |
|
||||||
|
| 报卡名称 | string | 传染病报告卡 | - |
|
||||||
|
| 病种名称 | string | 病毒性肝炎 | - |
|
||||||
|
| 报卡编号 | string | HOSP202601150001 | 唯一标识 |
|
||||||
|
| 患者姓名 | string | 张某某 | 脱敏显示 |
|
||||||
|
| 性别 | enum | 男 | 男/女/未知 |
|
||||||
|
| 年龄 | number | 32 | - |
|
||||||
|
| 上报科室 | string | 儿科 | - |
|
||||||
|
| 登记来源 | string | 门诊 | - |
|
||||||
|
| 上报时间 | datetime | 2026-01-31 09:23 | - |
|
||||||
|
| 状态 | badge | 待审核 | 待审核<->已提交 |
|
||||||
|
| 操作 | button | 审核/查看 | 根据状态禁用 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- - 行操作按钮:
|
||||||
|
|
||||||
|
- - “审核”(主按钮,打开可编辑抽屉)
|
||||||
|
- “查看”(次要按钮,打开只读抽屉)
|
||||||
|
|
||||||
|
- 交互规则:
|
||||||
|
|
||||||
|
- - 已审核通过的报卡禁用"审核"按钮
|
||||||
|
- 行hover时显示浅蓝色背景
|
||||||
|
|
||||||
|
- 分页功能:
|
||||||
|
|
||||||
|
- - 实现分页功能(可以设置每页5/10/20条)
|
||||||
|
|
||||||
|
#### 5. 抽屉详情区
|
||||||
|
|
||||||
|
**区域位置**:右侧滑出
|
||||||
|
**区域尺寸**:自适应布局
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
· **报卡表单(****根据具体报卡登记的界面内容,比如《中华人民共和国传染病报告卡》内容和功能与需求编号102界面保持一致,建议用同一个界面)**
|
||||||
|
|
||||||
|
- - 字段分组:
|
||||||
|
|
||||||
|
- 1. 患者基本信息(姓名、身份证等)
|
||||||
|
2. 临床信息(发病日期、诊断分类等)
|
||||||
|
3. 疾病选择(甲/乙/丙类传染病复选框)
|
||||||
|
4. 上报信息(报告单位、医生等)
|
||||||
|
|
||||||
|
· **审核记录**
|
||||||
|
|
||||||
|
- - 展示方式:时间轴列表
|
||||||
|
- 数据字段:时间、操作人、操作类型、意见
|
||||||
|
|
||||||
|
· **操作按钮组**
|
||||||
|
|
||||||
|
- - 主按钮:审核通过(绿色)
|
||||||
|
- 次按钮:退回修改(橙色)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 四、交互功能详细说明
|
||||||
|
|
||||||
|
#### 1. 批量审核流程
|
||||||
|
|
||||||
|
**触发条件**:勾选多行后点击"批量审核"
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 系统校验:至少选择1条非"已通过"状态的报卡
|
||||||
|
|
||||||
|
2. 弹出审核弹窗(500px居中模态框)
|
||||||
|
|
||||||
|
3. 填写审核意见(必填)
|
||||||
|
|
||||||
|
4. 点击"确认审核":
|
||||||
|
|
||||||
|
5. 批量更新状态为"审核通过",(自动批量写入每一条审核记录(插入infectious_audit 字段详表②、更改表infectious_card. Statu= 2和infectious_card. update_time= now()))
|
||||||
|
|
||||||
|
6. 刷新表格数据
|
||||||
|
|
||||||
|
7. - 成功:更新状态为"审核通过",添加审核记录
|
||||||
|
- 失败:提示"审核失败,请检查网络"
|
||||||
|
- 包含已审核项:提示"只能选择待审核报卡"
|
||||||
|
- 未填写意见:阻止提交并红字提示
|
||||||
|
|
||||||
|
#### 2. 批量退回修改操作
|
||||||
|
|
||||||
|
**触发方式**:勾选多选框后点击"批量退回"
|
||||||
|
**前置校验**:
|
||||||
|
|
||||||
|
- 至少勾选一条非"已审核"状态报卡
|
||||||
|
- 选中已审核报卡时提示"只能操作待审核报卡"
|
||||||
|
|
||||||
|
**执行流程**:
|
||||||
|
|
||||||
|
1. 弹出模态框要求填写退回原因(必填)填写退回原因:阻止提交并红字提示
|
||||||
|
2. 提交后批量更新选中报卡状态
|
||||||
|
3. 每条生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=5和infectious_card. update_time= now())
|
||||||
|
|
||||||
|
#### 3.单卡审核流程
|
||||||
|
|
||||||
|
**触发方式**:点击操作列"审核"按钮
|
||||||
|
**状态控制**:
|
||||||
|
|
||||||
|
- 待审核/审核失败:可操作
|
||||||
|
- 审核通过:按钮禁用
|
||||||
|
**执行流程**:
|
||||||
|
|
||||||
|
1. 右侧滑出审核抽屉
|
||||||
|
2. 从行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||||
|
3. 展示历史审核记录(如有)
|
||||||
|
4. 填写审核意见/退回原因
|
||||||
|
5. 点击"审核通过"或"退回修改"
|
||||||
|
6. 更新表格行状态(生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=2/5和infectious_card. update_time= now()))
|
||||||
|
|
||||||
|
**异常处理**:
|
||||||
|
|
||||||
|
· 数据加载失败:提示"数据加载失败,请重试"
|
||||||
|
|
||||||
|
**·** 重复提交:按钮置灰防止重复点击
|
||||||
|
|
||||||
|
**状态变化**:
|
||||||
|
|
||||||
|
- 审核通过:表格行变绿色,按钮禁用,状态变成“审核通过”
|
||||||
|
- 退回修改:表格行变橙色,生成退回记录,状态变成“审核失败”审核失败<->退回
|
||||||
|
|
||||||
|
**数据校验**:
|
||||||
|
|
||||||
|
- 必填字段红框提示
|
||||||
|
- 身份证号格式校验
|
||||||
|
- 日期逻辑校验(发病日期≤诊断日期)
|
||||||
|
|
||||||
|
#### 4. 筛选查询功能
|
||||||
|
|
||||||
|
**触发方式**:点击"查询"按钮
|
||||||
|
**查询逻辑**:
|
||||||
|
|
||||||
|
- 登记来源:精确匹配
|
||||||
|
- 患者姓名:模糊匹配
|
||||||
|
- 时间范围:闭区间查询
|
||||||
|
- 状态筛选:精确匹配
|
||||||
|
- 上报科室:多选
|
||||||
|
**性能优化**:
|
||||||
|
- 500ms防抖处理
|
||||||
|
- 分页加载(可以设置每页5/10/20条)实现分页功能
|
||||||
|
|
||||||
|
#### 5. 报卡详情查看
|
||||||
|
|
||||||
|
**触发条件**:点击行"查看"按钮
|
||||||
|
**抽屉内容**:
|
||||||
|
|
||||||
|
- 只读表单(包含所有报卡字段)
|
||||||
|
- 审核记录时间轴(倒序展示)
|
||||||
|
- 关闭按钮(右上角×图标)
|
||||||
|
**数据加载**:根据行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||||
|
|
||||||
|
#### 6. 筛选联动逻辑
|
||||||
|
|
||||||
|
| **操作** | **系统响应** |
|
||||||
|
| ---------------------- | ------------------------------------------ |
|
||||||
|
| 点击"今日待审核"统计卡 | 自动设置: - 时间=当天 - 状态=待审核 |
|
||||||
|
| 本月审核失败 | 自动设置: - 时间=本月 - 状态=审核失败 |
|
||||||
|
| 本月审核成功 | 自动设置: - 时间=本月 - 状态=审核成功 |
|
||||||
|
| 本月已上报 | 自动设置: - 时间=本月 - 状态=已上报 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 五、数据结构说明
|
||||||
|
|
||||||
|
**关键数据表**:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
infectious_card 与报卡审核记录表(infectious_audit)采用 一对多 关联:
|
||||||
|
① 、一张报卡可经历多次审核(初审、复审、退回、重审等)。
|
||||||
|
② 、关联键:infectious_card.card_no → infectious_audit.card_id(FK)。
|
||||||
|
*infectious_card. Statu增加状态5退回=审核失败(当批量退回修改/退回修改时更改表infectious_card. Statu=5 and infectious_card. update_time= now())
|
||||||
|
|
||||||
|
*infectious_audit 字段详表
|
||||||
|
```
|
||||||
|
|
||||||
|
| **字段名** | **中文名称** | **取值说明** | **类型****(PG)** | **约束** |
|
||||||
|
| ----------------- | ------------ | ------------------------------------------------------------ | ---------------- | ---------------------------------------- |
|
||||||
|
| audit_id | 审核记录ID | 主键,自增 | BIGINT | PRIMARY KEY |
|
||||||
|
| card_id | 报卡ID | 关联infectious_card.card_no | BIGINT | NOT NULL, FK |
|
||||||
|
| audit_seq | 审核序号 | 第几次审核,从1开始 | SMALLINT | NOT NULL, ≥1 |
|
||||||
|
| audit_type | 审核类型 | 1批量审核/2单审核通过/3批量退回修改/4单退回修改 /5其他 | CHAR(1) | NOT NULL, IN('1','2','3','4','5') |
|
||||||
|
| audit_status_from | 审核前状态 | 同infectious_card.status(0暂存/1已提交=待审核/2已审核=审核通过/3已上报/4失败/5退回=审核失败) | CHAR(1) | NOT NULL, IN('0','1','2','3','4','5') |
|
||||||
|
| audit_status_to | 审核后状态 | 同上,审核后的新状态 | CHAR(1) | NOT NULL, IN('0','1','2','3','4') |
|
||||||
|
| audit_time | 审核时间 | 精确到秒,当前时间戳 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||||
|
| auditor_id | 审核人账号 | 登录账号 | VARCHAR(20) | NOT NULL |
|
||||||
|
| auditor_name | 审核人姓名 | 登录账号的姓名 | VARCHAR(50) | NOT NULL |
|
||||||
|
| audit_opinion | 审核意见 | 审核意见简述 | TEXT | |
|
||||||
|
| Reason_for_return | 退回原因 | 退回原因简述 | TEXT | |
|
||||||
|
| fail_reason_code | 失败原因码 | 字典:001必填项/002逻辑错误/003网络超时等 | VARCHAR(20) | |
|
||||||
|
| fail_reason_desc | 失败详情 | 详细描述,可空 | TEXT | |
|
||||||
|
| is_batch | 是否批量 | 0单条审核/1批量审核 | BOOLEAN | NOT NULL, DEFAULT false |
|
||||||
|
| batch_size | 批量数量 | 批量时涉及报卡数 | INTEGER | NOT NULL, DEFAULT 1, ≥1 |
|
||||||
|
| created_time | 记录创建时间 | 自动生成 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||||
|
| updated_time | 记录更新时间 | 自动更新 | TIMESTAMP | NOT NULL, DEFAULT now(), ON UPDATE now() |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 六、开发实现要点
|
||||||
|
|
||||||
|
**样式规范**:
|
||||||
|
|
||||||
|
- **主色调**:`#4a6fa5`(导航栏/主按钮)
|
||||||
|
|
||||||
|
- **状态色**:
|
||||||
|
|
||||||
|
- - 待审核:`rgba(74, 111, 165, 0.1)`
|
||||||
|
- 审核失败:`rgba(231, 76, 60, 0.1)`
|
||||||
|
|
||||||
|
- **字体**:
|
||||||
|
|
||||||
|
- - 标题:`16px/1.5 #333`
|
||||||
|
- 表格内容:`14px/1.5 #666`
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
- 审核记录需永久保存不可删除
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 七、补充说明
|
||||||
|
|
||||||
|
1. **日期格式**:统一使用`YYYY/MM/DD`(符合医疗系统惯例)
|
||||||
|
2. **地址组件**:四级联动(省→市→区→街道)
|
||||||
|
3. **职业选项**:使用国家标准职业分类
|
||||||
|
4. **病种名称**:严格遵循《传染病报告卡》规范用词
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.core.common.annotation;
|
package com.core.common.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel额外表头信息注解
|
* Excel额外表头信息注解
|
||||||
@@ -14,7 +15,7 @@ public @interface ExcelExtra {
|
|||||||
/**
|
/**
|
||||||
* 表头名称
|
* 表头名称
|
||||||
*/
|
*/
|
||||||
String name();
|
String name() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
||||||
@@ -35,4 +36,15 @@ public @interface ExcelExtra {
|
|||||||
* 是否导出
|
* 是否导出
|
||||||
*/
|
*/
|
||||||
boolean isExport() default true;
|
boolean isExport() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度 默认:-1(默认不开启BigDecimal格式化)
|
||||||
|
*/
|
||||||
|
int scale() default -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
|
||||||
|
*/
|
||||||
|
int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.core.common.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色枚举
|
||||||
|
*
|
||||||
|
* @author swb
|
||||||
|
* @date 2026-01-29
|
||||||
|
*/
|
||||||
|
public enum RoleEnum {
|
||||||
|
DOCTOR("doctor", "医生"),
|
||||||
|
NURSE("nurse", "护士"),
|
||||||
|
ADMIN("admin", "管理员");
|
||||||
|
private final String code;
|
||||||
|
private final String info;
|
||||||
|
|
||||||
|
RoleEnum(String code, String info) {
|
||||||
|
this.code = code;
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInfo() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,67 +33,125 @@ public final class AgeCalculatorUtil {
|
|||||||
return period.getYears();
|
return period.getYears();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 当前年龄取得(床位列表表示年龄用)
|
||||||
|
// */
|
||||||
|
// public static String getAge(Date date) {
|
||||||
|
// // 将 Date 转换为 LocalDateTime
|
||||||
|
// LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||||
|
// LocalDateTime now = LocalDateTime.now();
|
||||||
|
// int years = now.getYear() - dateTime.getYear();
|
||||||
|
// if (years > 2) {
|
||||||
|
// return String.format("%d岁", years);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||||
|
// int months = period.getMonths();
|
||||||
|
// int days = period.getDays();
|
||||||
|
// long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||||
|
//
|
||||||
|
// if (hours < 0) {
|
||||||
|
// hours += 24;
|
||||||
|
// days--;
|
||||||
|
// }
|
||||||
|
// if (days < 0) {
|
||||||
|
// months--;
|
||||||
|
// days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||||
|
// }
|
||||||
|
// if (months < 0) {
|
||||||
|
// months += 12;
|
||||||
|
// years--;
|
||||||
|
// }
|
||||||
|
// if (years < 0) {
|
||||||
|
// return "1小时";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (years > 0 && months > 0) {
|
||||||
|
// return String.format("%d岁%d月", years, months);
|
||||||
|
// }
|
||||||
|
// if (years > 0) {
|
||||||
|
// return String.format("%d岁", years);
|
||||||
|
// }
|
||||||
|
// if (months > 0 && days > 0) {
|
||||||
|
// return String.format("%d月%d天", months, days);
|
||||||
|
// }
|
||||||
|
// if (months > 0) {
|
||||||
|
// return String.format("%d月", months);
|
||||||
|
// }
|
||||||
|
// if (days > 0 && hours > 0) {
|
||||||
|
// return String.format("%d天%d小时", days, hours);
|
||||||
|
// }
|
||||||
|
// if (days > 0) {
|
||||||
|
// return String.format("%d天", days);
|
||||||
|
// }
|
||||||
|
// if (hours > 0) {
|
||||||
|
// return String.format("%d小时", hours);
|
||||||
|
// }
|
||||||
|
// return "1小时";
|
||||||
|
// }
|
||||||
/**
|
/**
|
||||||
* 当前年龄取得(床位列表表示年龄用)
|
* 复刻Oracle函数FUN_GET_AGE的核心逻辑:返回年龄字符串
|
||||||
|
*
|
||||||
|
* @param birthDate 出生日期
|
||||||
|
* @return 年龄字符串(如:29岁、3岁5月、2月15天、18天),出生日期晚于当前日期返回空字符串
|
||||||
*/
|
*/
|
||||||
public static String getAge(Date date) {
|
public static String getAge(Date birthDate) {
|
||||||
// 添加空值检查
|
// 入参校验
|
||||||
if (date == null) {
|
if (birthDate == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
// 将 Date 转换为 LocalDateTime
|
|
||||||
LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
// 将Date转换为LocalDate(使用系统默认时区)
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDate birthLocalDate = birthDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||||
int years = now.getYear() - dateTime.getYear();
|
LocalDate currentDate = LocalDate.now();
|
||||||
if (years > 2) {
|
|
||||||
return String.format("%d岁", years);
|
// 计算总天数(对应Oracle中的IDAY)
|
||||||
|
long totalDays = ChronoUnit.DAYS.between(birthLocalDate, currentDate);
|
||||||
|
|
||||||
|
// 若出生日期晚于当前日期,返回空字符串
|
||||||
|
if (totalDays < 0) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
// 计算年份(复刻Oracle的闰年补偿逻辑:(当前年-出生年)/4 补偿闰年天数)
|
||||||
int months = period.getMonths();
|
int birthYear = birthLocalDate.getYear();
|
||||||
int days = period.getDays();
|
int currentYear = currentDate.getYear();
|
||||||
long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
long leapYearCompensation = (currentYear - birthYear) / 4;
|
||||||
|
long adjustedDays = totalDays - leapYearCompensation;
|
||||||
|
|
||||||
if (hours < 0) {
|
// 计算年、月、天(按365天/年、30天/月粗略折算,与Oracle逻辑一致)
|
||||||
hours += 24;
|
int iYear = (int) (adjustedDays / 365);
|
||||||
days--;
|
long remainingDaysAfterYear = adjustedDays - iYear * 365;
|
||||||
}
|
int iMonth = (int) (remainingDaysAfterYear / 30);
|
||||||
if (days < 0) {
|
int iDay = (int) (remainingDaysAfterYear - iMonth * 30);
|
||||||
months--;
|
|
||||||
days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
|
||||||
}
|
|
||||||
if (months < 0) {
|
|
||||||
months += 12;
|
|
||||||
years--;
|
|
||||||
}
|
|
||||||
if (years < 0) {
|
|
||||||
return "1小时";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (years > 0 && months > 0) {
|
// 按原函数规则拼接返回字符串
|
||||||
return String.format("%d岁%d月", years, months);
|
if (iYear <= 0) {
|
||||||
|
// 小于1岁
|
||||||
|
if (iMonth <= 0) {
|
||||||
|
// 小于1个月,返回X天
|
||||||
|
return iDay + "天";
|
||||||
|
} else {
|
||||||
|
// 1个月及以上,返回X月X天
|
||||||
|
return iMonth + "月" + iDay + "天";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 1岁及以上
|
||||||
|
if (iYear < 5) {
|
||||||
|
// 1-4岁
|
||||||
|
if (iMonth <= 0) {
|
||||||
|
// 无整月,返回X岁X天
|
||||||
|
return iYear + "岁" + iDay + "天";
|
||||||
|
} else {
|
||||||
|
// 有整月,返回X岁X月
|
||||||
|
return iYear + "岁" + iMonth + "月";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 5岁及以上,仅返回X岁
|
||||||
|
return iYear + "岁";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (years > 0) {
|
|
||||||
return String.format("%d岁", years);
|
|
||||||
}
|
|
||||||
if (months > 0 && days > 0) {
|
|
||||||
return String.format("%d月%d天", months, days);
|
|
||||||
}
|
|
||||||
if (months > 0) {
|
|
||||||
return String.format("%d月", months);
|
|
||||||
}
|
|
||||||
if (days > 0 && hours > 0) {
|
|
||||||
return String.format("%d天%d小时", days, hours);
|
|
||||||
}
|
|
||||||
if (days > 0) {
|
|
||||||
return String.format("%d天", days);
|
|
||||||
}
|
|
||||||
if (hours > 0) {
|
|
||||||
return String.format("%d小时", hours);
|
|
||||||
}
|
|
||||||
return "1小时";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
||||||
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
|||||||
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@@ -14,6 +16,8 @@ import java.util.Date;
|
|||||||
@Component
|
@Component
|
||||||
public class AuditFieldUtil {
|
public class AuditFieldUtil {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AuditFieldUtil.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为实体设置创建相关的审计字段
|
* 为实体设置创建相关的审计字段
|
||||||
* @param entity 实体对象
|
* @param entity 实体对象
|
||||||
@@ -65,8 +69,7 @@ public class AuditFieldUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("设置创建审计字段时发生异常: " + e.getMessage());
|
log.error("设置创建审计字段时发生异常", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +113,7 @@ public class AuditFieldUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("设置更新审计字段时发生异常: " + e.getMessage());
|
log.error("设置更新审计字段时发生异常", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.core.common.utils;
|
package com.core.common.utils;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
@@ -17,6 +19,9 @@ import java.util.Date;
|
|||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DateUtils.class);
|
||||||
|
|
||||||
public static String YYYY = "yyyy";
|
public static String YYYY = "yyyy";
|
||||||
|
|
||||||
public static String YYYY_MM = "yyyy-MM";
|
public static String YYYY_MM = "yyyy-MM";
|
||||||
@@ -227,7 +232,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
return endTime;
|
return endTime;
|
||||||
}
|
}
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
e.printStackTrace();
|
log.warn("日期解析失败: {}", strDate, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -250,7 +255,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
// 检查日期是否是未来的时间
|
// 检查日期是否是未来的时间
|
||||||
return dateToCheck.isAfter(currentDate);
|
return dateToCheck.isAfter(currentDate);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.warn("日期解析失败: {}", dateString, e);
|
||||||
// 解析失败或其他异常,返回 false 或根据需要处理异常
|
// 解析失败或其他异常,返回 false 或根据需要处理异常
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
package com.core.common.utils;
|
package com.core.common.utils;
|
||||||
|
|
||||||
import com.core.common.annotation.Excel;
|
import java.io.*;
|
||||||
import com.core.common.annotation.Excel.ColumnType;
|
import java.lang.reflect.Field;
|
||||||
import com.core.common.annotation.Excel.Type;
|
import java.lang.reflect.Method;
|
||||||
import com.core.common.annotation.ExcelExtra;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import com.core.common.annotation.Excels;
|
import java.math.BigDecimal;
|
||||||
import com.core.common.config.CoreConfig;
|
import java.text.DecimalFormat;
|
||||||
import com.core.common.core.domain.AjaxResult;
|
import java.time.LocalDate;
|
||||||
import com.core.common.core.text.Convert;
|
import java.time.LocalDateTime;
|
||||||
import com.core.common.exception.UtilException;
|
import java.util.*;
|
||||||
import com.core.common.utils.file.FileTypeUtils;
|
import java.util.stream.Collectors;
|
||||||
import com.core.common.utils.file.FileUtils;
|
|
||||||
import com.core.common.utils.file.ImageUtils;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
|
||||||
import com.core.common.utils.reflect.ReflectUtils;
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.RegExUtils;
|
import org.apache.commons.lang3.RegExUtils;
|
||||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
@@ -30,17 +29,20 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import com.core.common.annotation.Excel;
|
||||||
import java.io.*;
|
import com.core.common.annotation.Excel.ColumnType;
|
||||||
import java.lang.reflect.Field;
|
import com.core.common.annotation.Excel.Type;
|
||||||
import java.lang.reflect.Method;
|
import com.core.common.annotation.ExcelExtra;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import com.core.common.annotation.Excels;
|
||||||
import java.math.BigDecimal;
|
import com.core.common.config.CoreConfig;
|
||||||
import java.text.DecimalFormat;
|
import com.core.common.core.domain.AjaxResult;
|
||||||
import java.time.LocalDate;
|
import com.core.common.core.text.Convert;
|
||||||
import java.time.LocalDateTime;
|
import com.core.common.exception.UtilException;
|
||||||
import java.util.*;
|
import com.core.common.utils.file.FileTypeUtils;
|
||||||
import java.util.stream.Collectors;
|
import com.core.common.utils.file.FileUtils;
|
||||||
|
import com.core.common.utils.file.ImageUtils;
|
||||||
|
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||||
|
import com.core.common.utils.reflect.ReflectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel相关处理
|
* Excel相关处理
|
||||||
@@ -1164,6 +1166,11 @@ public class NewExcelUtil<T> {
|
|||||||
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
||||||
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
||||||
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
||||||
|
if (StringUtils.isNotEmpty(includeFields)) {
|
||||||
|
this.subFields = this.subFields.stream().filter(f -> ArrayUtils.contains(includeFields, f.getName())).collect(Collectors.toList());
|
||||||
|
} else if (StringUtils.isNotEmpty(excludeFields)) {
|
||||||
|
this.subFields = this.subFields.stream().filter(f -> !ArrayUtils.contains(excludeFields, f.getName())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,7 +1448,28 @@ public class NewExcelUtil<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 计算表格体总列数
|
||||||
|
int totalCols = 0;
|
||||||
|
for (Object[] os : fields) {
|
||||||
|
Field field = (Field)os[0];
|
||||||
|
if (Collection.class.isAssignableFrom(field.getType()) && subFields != null) {
|
||||||
|
long subCount = subFields.stream().filter(f -> f.isAnnotationPresent(Excel.class)).count();
|
||||||
|
totalCols += subCount;
|
||||||
|
} else {
|
||||||
|
totalCols++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalCols == 0) totalCols = 1;
|
||||||
|
|
||||||
int currentRowNum = rownum;
|
int currentRowNum = rownum;
|
||||||
|
int colIndex = 0;
|
||||||
|
Row row = null;
|
||||||
|
boolean hasVisible = false;
|
||||||
|
|
||||||
|
// 布局配置:Label占用1列,Value占用2列,共3列
|
||||||
|
int labelCols = 1;
|
||||||
|
int valueCols = 2;
|
||||||
|
int itemCols = labelCols + valueCols;
|
||||||
|
|
||||||
for (Object[] os : extraFields) {
|
for (Object[] os : extraFields) {
|
||||||
Field field = (Field)os[0];
|
Field field = (Field)os[0];
|
||||||
@@ -1451,43 +1479,50 @@ public class NewExcelUtil<T> {
|
|||||||
if (isExtraFieldHidden(field.getName())) {
|
if (isExtraFieldHidden(field.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
hasVisible = true;
|
||||||
|
|
||||||
Row row = sheet.createRow(currentRowNum++);
|
// 自动换行:如果不是行首,且剩余空间不足,则换行
|
||||||
|
if (row == null) {
|
||||||
|
row = sheet.createRow(currentRowNum);
|
||||||
|
} else if (colIndex > 0 && colIndex + itemCols > totalCols) {
|
||||||
|
currentRowNum++;
|
||||||
|
row = sheet.createRow(currentRowNum);
|
||||||
|
colIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建标签单元格(第0列)
|
// 1. 创建 Label 单元格
|
||||||
Cell labelCell = row.createCell(0);
|
Cell labelCell = row.createCell(colIndex);
|
||||||
labelCell.setCellValue(attr.name());
|
labelCell.setCellValue(attr.name());
|
||||||
labelCell.setCellStyle(styles.get("extraLabel"));
|
labelCell.setCellStyle(styles.get("extraLabel"));
|
||||||
|
|
||||||
// 创建值单元格(第1列)
|
// 2. 创建 Value 单元格
|
||||||
Cell valueCell = row.createCell(1);
|
int valueStartCol = colIndex + labelCols;
|
||||||
|
Cell valueCell = row.createCell(valueStartCol);
|
||||||
Object value = field.get(entity);
|
Object value = field.get(entity);
|
||||||
String cellValue = formatExtraCellValue(value, attr);
|
String cellValue = formatExtraCellValue(value, attr);
|
||||||
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
||||||
valueCell.setCellStyle(styles.get("extraValue"));
|
valueCell.setCellStyle(styles.get("extraValue"));
|
||||||
|
|
||||||
// 创建合并区域(第1列到第2列)
|
// 3. 合并 Value 单元格
|
||||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), 1, 2);
|
if (valueCols > 1) {
|
||||||
sheet.addMergedRegion(mergedRegion);
|
int valueEndCol = valueStartCol + valueCols - 1;
|
||||||
|
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), valueStartCol, valueEndCol);
|
||||||
|
sheet.addMergedRegion(mergedRegion);
|
||||||
|
|
||||||
// 手动设置合并区域的边框,确保完整显示
|
// 设置边框
|
||||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
}
|
||||||
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
colIndex += itemCols;
|
||||||
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
sheet.setColumnWidth(0, 15 * 256); // 标签列宽
|
|
||||||
sheet.setColumnWidth(1, 20 * 256); // 值列宽
|
|
||||||
sheet.setColumnWidth(2, 20 * 256); // 值列宽
|
|
||||||
|
|
||||||
// 更新当前行号,在额外表头和数据表头之间空一行
|
// 更新当前行号,在额外表头和数据表头之间空一行
|
||||||
rownum = currentRowNum + 1;
|
if (hasVisible) {
|
||||||
|
rownum = currentRowNum + 2;
|
||||||
|
}
|
||||||
subMergedFirstRowNum = rownum;
|
subMergedFirstRowNum = rownum;
|
||||||
subMergedLastRowNum = rownum;
|
subMergedLastRowNum = rownum;
|
||||||
|
|
||||||
@@ -1508,6 +1543,10 @@ public class NewExcelUtil<T> {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value instanceof BigDecimal && attr.scale() >= 0) {
|
||||||
|
return ((BigDecimal) value).setScale(attr.scale(), attr.roundingMode()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
||||||
return parseDateToStr(attr.dateFormat(), value);
|
return parseDateToStr(attr.dateFormat(), value);
|
||||||
}
|
}
|
||||||
@@ -1808,7 +1847,7 @@ public class NewExcelUtil<T> {
|
|||||||
row = sheet.createRow(rowNo);
|
row = sheet.createRow(rowNo);
|
||||||
}
|
}
|
||||||
// 子字段也要排序
|
// 子字段也要排序
|
||||||
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
|
List<Field> subFields = this.subFields;
|
||||||
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
||||||
Excel subExcel = subField.getAnnotation(Excel.class);
|
Excel subExcel = subField.getAnnotation(Excel.class);
|
||||||
return subExcel.sort();
|
return subExcel.sort();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
|||||||
|
|
||||||
import com.core.common.constant.Constants;
|
import com.core.common.constant.Constants;
|
||||||
import com.core.common.core.text.Convert;
|
import com.core.common.core.text.Convert;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
@@ -24,6 +26,9 @@ import java.util.Map;
|
|||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class ServletUtils {
|
public class ServletUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ServletUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取String参数
|
* 获取String参数
|
||||||
*/
|
*/
|
||||||
@@ -130,7 +135,7 @@ public class ServletUtils {
|
|||||||
response.setCharacterEncoding("utf-8");
|
response.setCharacterEncoding("utf-8");
|
||||||
response.getWriter().print(string);
|
response.getWriter().print(string);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("渲染响应字符串失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.core.common.utils.bean;
|
package com.core.common.utils.bean;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -12,6 +15,9 @@ import java.util.regex.Pattern;
|
|||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class BeanUtils extends org.springframework.beans.BeanUtils {
|
public class BeanUtils extends org.springframework.beans.BeanUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BeanUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean方法名中属性名开始的下标
|
* Bean方法名中属性名开始的下标
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +43,7 @@ public class BeanUtils extends org.springframework.beans.BeanUtils {
|
|||||||
try {
|
try {
|
||||||
copyProperties(src, dest);
|
copyProperties(src, dest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("Bean属性复制失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class FlowDefinitionController extends BaseController {
|
|||||||
ImageIO.write(image, "png", os);
|
ImageIO.write(image, "png", os);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("读取流程图片失败, deployId: {}", deployId, e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
@@ -123,7 +123,7 @@ public class FlowDefinitionController extends BaseController {
|
|||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("关闭输出流失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ public class FlowTaskController extends BaseController {
|
|||||||
ImageIO.write(image, "png", os);
|
ImageIO.write(image, "png", os);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("读取流程图片失败", e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
@@ -219,7 +219,7 @@ public class FlowTaskController extends BaseController {
|
|||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("关闭输出流失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -722,7 +722,7 @@ public class FlowableUtils {
|
|||||||
// 反射设置属性值
|
// 反射设置属性值
|
||||||
field.set(propertyDto, attribute.getValue());
|
field.set(propertyDto, attribute.getValue());
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
log.warn("反射设置属性值失败", e);
|
||||||
// 如果反射设置失败则忽略该属性
|
// 如果反射设置失败则忽略该属性
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -730,7 +730,7 @@ public class FlowableUtils {
|
|||||||
return propertyDto;
|
return propertyDto;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("解析流程属性失败", e);
|
||||||
return Collections.emptyList(); // 如果发生异常则返回空列表
|
return Collections.emptyList(); // 如果发生异常则返回空列表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl
|
|||||||
}
|
}
|
||||||
return AjaxResult.success("流程启动成功");
|
return AjaxResult.success("流程启动成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("流程启动错误", e);
|
||||||
return AjaxResult.error("流程启动错误");
|
return AjaxResult.error("流程启动错误");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class FlowInstanceServiceImpl extends FlowServiceFactory implements IFlow
|
|||||||
runtimeService.startProcessInstanceById(procDefId, variables);
|
runtimeService.startProcessInstanceById(procDefId, variables);
|
||||||
return AjaxResult.success("流程启动成功");
|
return AjaxResult.success("流程启动成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("流程启动错误, procDefId: {}", procDefId, e);
|
||||||
return AjaxResult.error("流程启动错误");
|
return AjaxResult.error("流程启动错误");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import com.core.generator.service.IGenTableColumnService;
|
|||||||
import com.core.generator.service.IGenTableService;
|
import com.core.generator.service.IGenTableService;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -43,6 +45,8 @@ import java.util.Map;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/tool/gen")
|
@RequestMapping("/tool/gen")
|
||||||
public class GenController extends BaseController {
|
public class GenController extends BaseController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GenController.class);
|
||||||
@Autowired
|
@Autowired
|
||||||
private IGenTableService genTableService;
|
private IGenTableService genTableService;
|
||||||
|
|
||||||
@@ -436,7 +440,7 @@ public class GenController extends BaseController {
|
|||||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
||||||
writer.write(str);
|
writer.write(str);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("写入文件失败: {}", filePath, e);
|
||||||
} finally {
|
} finally {
|
||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.core.system.domain.SysUserConfig;
|
|||||||
import com.core.common.utils.StringUtils;
|
import com.core.common.utils.StringUtils;
|
||||||
import com.core.system.mapper.SysUserConfigMapper;
|
import com.core.system.mapper.SysUserConfigMapper;
|
||||||
import com.core.system.service.ISysUserConfigService;
|
import com.core.system.service.ISysUserConfigService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, SysUserConfig> implements ISysUserConfigService
|
public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, SysUserConfig> implements ISysUserConfigService
|
||||||
{
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SysUserConfigServiceImpl.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysUserConfigMapper sysUserConfigMapper;
|
private SysUserConfigMapper sysUserConfigMapper;
|
||||||
|
|
||||||
@@ -170,8 +174,7 @@ public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, S
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 记录错误日志以便调试
|
// 记录错误日志以便调试
|
||||||
System.err.println("保存用户配置时发生错误: " + e.getMessage());
|
log.error("保存用户配置时发生错误, userId: {}, configKey: {}", userId, configKey, e);
|
||||||
e.printStackTrace();
|
|
||||||
throw e; // 重新抛出异常让上层处理
|
throw e; // 重新抛出异常让上层处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Web Layer - API Controllers
|
||||||
|
|
||||||
|
**Module**: `openhis-application/web`
|
||||||
|
**Role**: API endpoint layer - all REST controllers for frontend communication
|
||||||
|
|
||||||
|
## OVERVIEW
|
||||||
|
46 web modules serving REST APIs for all business functionality.
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
```
|
||||||
|
web/
|
||||||
|
├── [module-name]/
|
||||||
|
│ ├── controller/ # REST endpoints (@RestController)
|
||||||
|
│ ├── dto/ # Data transfer objects
|
||||||
|
│ ├── mapper/ # MyBatis mappers (if module-specific)
|
||||||
|
│ └── appservice/ # Application service layer
|
||||||
|
│ └── impl/
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
| Task | Location |
|
||||||
|
|------|----------|
|
||||||
|
| API endpoints | `*/controller/*Controller.java` |
|
||||||
|
| Request/Response schemas | `*/dto/*.java` |
|
||||||
|
| Business logic orchestration | `*/appservice/*.java` |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
- Controllers: `@RestController`, `@RequestMapping("/module-name")`
|
||||||
|
- Standard response: `AjaxResult` from core-common
|
||||||
|
- DTO naming: `XxxRequest`, `XxxResponse`, `XxxDTO`
|
||||||
|
- Service pattern: interface in `appservice/`, impl in `appservice/impl/`
|
||||||
|
- API naming: `listXxx()`, `getXxx()`, `addXxx()`, `updateXxx()`, `deleteXxx()`
|
||||||
|
|
||||||
|
## ANTI-PATTERNS
|
||||||
|
- Never put business logic in controllers - delegate to appservice
|
||||||
|
- Never return raw entities - use DTOs
|
||||||
|
- Never bypass `AjaxResult` wrapper
|
||||||
|
- Never create module-specific mappers without justification
|
||||||
@@ -104,4 +104,8 @@ public class InstrumentManageDto {
|
|||||||
/** 备注 */
|
/** 备注 */
|
||||||
private String remarks;
|
private String remarks;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getInstrumentTypeEnum() {
|
||||||
|
return instrumentTypeEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ public class InstrumentManageInitDto {
|
|||||||
private List<InstrumentType> InstrumentTypeList;
|
private List<InstrumentType> InstrumentTypeList;
|
||||||
private List<InstrumentStatusEnumOption> InstrumentStatusEnumList;
|
private List<InstrumentStatusEnumOption> InstrumentStatusEnumList;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
||||||
|
this.statusFlagOptions = statusFlagOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentTypeList(List<InstrumentType> InstrumentTypeList) {
|
||||||
|
this.InstrumentTypeList = InstrumentTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentStatusEnumList(List<InstrumentStatusEnumOption> InstrumentStatusEnumList) {
|
||||||
|
this.InstrumentStatusEnumList = InstrumentStatusEnumList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class InstrumentStatusRequest {
|
public class InstrumentStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,36 @@ public class LisConfigManageDto {
|
|||||||
|
|
||||||
private List<ActivityDefSpecimenDef> activityDefSpecimenDefs;
|
private List<ActivityDefSpecimenDef> activityDefSpecimenDefs;
|
||||||
|
|
||||||
|
// 手动添加 getter 和 setter 方法
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefDeviceDef> getActivityDefDeviceDefs() {
|
||||||
|
return activityDefDeviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefDeviceDefs(List<ActivityDefDeviceDef> activityDefDeviceDefs) {
|
||||||
|
this.activityDefDeviceDefs = activityDefDeviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefObservationDef> getActivityDefObservationDefs() {
|
||||||
|
return activityDefObservationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefObservationDefs(List<ActivityDefObservationDef> activityDefObservationDefs) {
|
||||||
|
this.activityDefObservationDefs = activityDefObservationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefSpecimenDef> getActivityDefSpecimenDefs() {
|
||||||
|
return activityDefSpecimenDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefSpecimenDefs(List<ActivityDefSpecimenDef> activityDefSpecimenDefs) {
|
||||||
|
this.activityDefSpecimenDefs = activityDefSpecimenDefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,16 @@ public class LisConfigManageInitDto {
|
|||||||
|
|
||||||
private List<SpecimenDefinition> specimenDefs;
|
private List<SpecimenDefinition> specimenDefs;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setDeviceDefs(List<DeviceDefinition> deviceDefs) {
|
||||||
|
this.deviceDefs = deviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObservationDefs(List<ObservationDefinition> observationDefs) {
|
||||||
|
this.observationDefs = observationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpecimenDefs(List<SpecimenDefinition> specimenDefs) {
|
||||||
|
this.specimenDefs = specimenDefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,16 @@ public class ObservationDefManageDto {
|
|||||||
/** 删除状态) */
|
/** 删除状态) */
|
||||||
private String deleteFlag;
|
private String deleteFlag;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Long getInstrumentId() {
|
||||||
|
return instrumentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatusEnum() {
|
||||||
|
return statusEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getObservationTypeEnum() {
|
||||||
|
return observationTypeEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ public class ObservationDefManageInitDto {
|
|||||||
private List<ObservationTypeEnumOption> ObservationTypeList;
|
private List<ObservationTypeEnumOption> ObservationTypeList;
|
||||||
private List<InstrumentEnumOption> instrumentEnumOptionList;
|
private List<InstrumentEnumOption> instrumentEnumOptionList;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
||||||
|
this.statusFlagOptions = statusFlagOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObservationTypeList(List<ObservationTypeEnumOption> ObservationTypeList) {
|
||||||
|
this.ObservationTypeList = ObservationTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentEnumOptionList(List<InstrumentEnumOption> instrumentEnumOptionList) {
|
||||||
|
this.instrumentEnumOptionList = instrumentEnumOptionList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class ObservationDefStatusRequest {
|
public class ObservationDefStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,8 @@ public class ReportResultManageDto {
|
|||||||
private String authoredTime; // 开单时间
|
private String authoredTime; // 开单时间
|
||||||
|
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getGenderEnum() {
|
||||||
|
return genderEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,12 @@ public class SampleCollectManageDto {
|
|||||||
private String authoredTime; // 开单时间
|
private String authoredTime; // 开单时间
|
||||||
|
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getGenderEnum() {
|
||||||
|
return genderEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectionStatusEnum() {
|
||||||
|
return collectionStatusEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class SampleCollectStatusRequest {
|
public class SampleCollectStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,4 +59,12 @@ public class SpecimenDefManageDto {
|
|||||||
private Integer statusEnum;
|
private Integer statusEnum;
|
||||||
private String statusEnumText;
|
private String statusEnumText;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getSpecimenTypeEnum() {
|
||||||
|
return specimenTypeEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatusEnum() {
|
||||||
|
return statusEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ public class SpecimenDefManageInitDto {
|
|||||||
private List<statusEnumOption> statusFlagOptions;
|
private List<statusEnumOption> statusFlagOptions;
|
||||||
private List<SpecimenType> SpecimenTypeList;
|
private List<SpecimenType> SpecimenTypeList;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
||||||
|
this.statusFlagOptions = statusFlagOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpecimenTypeList(List<SpecimenType> SpecimenTypeList) {
|
||||||
|
this.SpecimenTypeList = SpecimenTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class SpecimenDefStatusRequest {
|
public class SpecimenDefStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,4 +69,13 @@ public class AdjustPriceDataVo {
|
|||||||
private Long locationId;
|
private Long locationId;
|
||||||
|
|
||||||
private BigDecimal finalTotalQuantity;
|
private BigDecimal finalTotalQuantity;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Long getItemId() {
|
||||||
|
return itemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCategoryType() {
|
||||||
|
return categoryType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public interface IDoctorScheduleAppService {
|
|||||||
|
|
||||||
R<?> getTodayDoctorScheduleList();
|
R<?> getTodayDoctorScheduleList();
|
||||||
|
|
||||||
|
R<?> getTodayMySchedule();
|
||||||
|
|
||||||
R<?> getDoctorScheduleListByDeptId(Long deptId);
|
R<?> getDoctorScheduleListByDeptId(Long deptId);
|
||||||
|
|
||||||
R<?> getDoctorScheduleListByDeptIdAndDateRange(Long deptId, String startDate, String endDate);
|
R<?> getDoctorScheduleListByDeptIdAndDateRange(Long deptId, String startDate, String endDate);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.openhis.web.appointmentmanage.appservice.impl;
|
package com.openhis.web.appointmentmanage.appservice.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
||||||
|
import com.openhis.appointmentmanage.domain.DoctorScheduleWithDateDto;
|
||||||
import com.openhis.appointmentmanage.domain.SchedulePool;
|
import com.openhis.appointmentmanage.domain.SchedulePool;
|
||||||
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
import com.openhis.appointmentmanage.domain.ScheduleSlot;
|
||||||
import com.openhis.appointmentmanage.mapper.DoctorScheduleMapper;
|
import com.openhis.appointmentmanage.mapper.DoctorScheduleMapper;
|
||||||
@@ -16,13 +17,9 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Collections;
|
||||||
import java.time.DayOfWeek;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -39,10 +36,6 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
@Resource
|
@Resource
|
||||||
private DoctorScheduleMapper doctorScheduleMapper;
|
private DoctorScheduleMapper doctorScheduleMapper;
|
||||||
|
|
||||||
// 转换 LocalDateTime 为 Date 的通用方法
|
|
||||||
Date now = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
|
|
||||||
Date tomorrow = Date.from(LocalDateTime.now().plusDays(1).atZone(ZoneId.systemDefault()).toInstant());
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> getDoctorScheduleList() {
|
public R<?> getDoctorScheduleList() {
|
||||||
List<DoctorSchedule> list = doctorScheduleService.list();
|
List<DoctorSchedule> list = doctorScheduleService.list();
|
||||||
@@ -52,65 +45,46 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
@Override
|
@Override
|
||||||
public R<?> getDoctorScheduleListByDeptId(Long deptId) {
|
public R<?> getDoctorScheduleListByDeptId(Long deptId) {
|
||||||
List<DoctorSchedule> list = doctorScheduleService.list(
|
List<DoctorSchedule> list = doctorScheduleService.list(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<DoctorSchedule>()
|
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<DoctorSchedule>()
|
||||||
.eq("dept_id", deptId)
|
.eq("dept_id", deptId));
|
||||||
);
|
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> getDoctorScheduleListByDeptIdAndDateRange(Long deptId, String startDate, String endDate) {
|
public R<?> getDoctorScheduleListByDeptIdAndDateRange(Long deptId, String startDate, String endDate) {
|
||||||
// 暂时返回所有科室的排班数据,直到我们确定日期范围过滤逻辑
|
// 联表查询 adm_doctor_schedule LEFT JOIN adm_schedule_pool,
|
||||||
List<DoctorSchedule> list = doctorScheduleService.list(
|
// 通过 schedule_date 获取具体出诊日期,解决按星期匹配导致日期错位的问题
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<DoctorSchedule>()
|
List<DoctorScheduleWithDateDto> list = doctorScheduleMapper.selectScheduleWithDateByDeptAndRange(
|
||||||
.eq("dept_id", deptId)
|
deptId, startDate, endDate);
|
||||||
);
|
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public R<?> getTodayDoctorScheduleList() {
|
public R<?> getTodayDoctorScheduleList() {
|
||||||
// 获取今天的日期
|
// 联表查询 adm_schedule_pool,按今日具体日期查询排班,替代原来按星期匹配的方式
|
||||||
LocalDate today = LocalDate.now();
|
String todayStr = LocalDate.now().toString(); // yyyy-MM-dd
|
||||||
DayOfWeek dayOfWeek = today.getDayOfWeek();
|
List<DoctorScheduleWithDateDto> list = doctorScheduleMapper.selectTodaySchedule(todayStr);
|
||||||
|
|
||||||
// 将 Java 的 DayOfWeek 转换为字符串表示
|
|
||||||
String weekdayStr = convertDayOfWeekToString(dayOfWeek);
|
|
||||||
|
|
||||||
// 查询今天排班的医生
|
|
||||||
LambdaQueryWrapper<DoctorSchedule> queryWrapper = new LambdaQueryWrapper<>();
|
|
||||||
queryWrapper.eq(DoctorSchedule::getWeekday, weekdayStr) // 根据星期几查询
|
|
||||||
.eq(DoctorSchedule::getIsStopped, false); // 只查询未停止的排班
|
|
||||||
|
|
||||||
List<DoctorSchedule> list = doctorScheduleService.list(queryWrapper);
|
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Transactional(readOnly = true)
|
||||||
* 将 DayOfWeek 转换为字符串表示
|
@Override
|
||||||
* @param dayOfWeek DayOfWeek枚举
|
public R<?> getTodayMySchedule() {
|
||||||
* @return 对应的字符串表示
|
// 联表查询 adm_schedule_pool,按今日具体日期 + 医生ID 查询个人排班
|
||||||
*/
|
String todayStr = LocalDate.now().toString(); // yyyy-MM-dd
|
||||||
private String convertDayOfWeekToString(DayOfWeek dayOfWeek) {
|
|
||||||
switch (dayOfWeek) {
|
// 从 SecurityUtils 获取当前登录医生ID
|
||||||
case MONDAY:
|
Long currentDoctorId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
return "1"; // 或者 "星期一" 或 "Monday",取决于数据库中的实际存储格式
|
|
||||||
case TUESDAY:
|
if (currentDoctorId != null) {
|
||||||
return "2";
|
List<DoctorScheduleWithDateDto> list = doctorScheduleMapper.selectTodayMySchedule(todayStr,
|
||||||
case WEDNESDAY:
|
currentDoctorId);
|
||||||
return "3";
|
return R.ok(list);
|
||||||
case THURSDAY:
|
|
||||||
return "4";
|
|
||||||
case FRIDAY:
|
|
||||||
return "5";
|
|
||||||
case SATURDAY:
|
|
||||||
return "6";
|
|
||||||
case SUNDAY:
|
|
||||||
return "7";
|
|
||||||
default:
|
|
||||||
return "1"; // 默认为星期一
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果未绑定医生,则返回空列表
|
||||||
|
return R.ok(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -133,10 +107,12 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
newSchedule.setEndTime(doctorSchedule.getEndTime());
|
newSchedule.setEndTime(doctorSchedule.getEndTime());
|
||||||
newSchedule.setLimitNumber(doctorSchedule.getLimitNumber());
|
newSchedule.setLimitNumber(doctorSchedule.getLimitNumber());
|
||||||
// call_sign_record 字段不能为null,设置默认值为空字符串
|
// call_sign_record 字段不能为null,设置默认值为空字符串
|
||||||
newSchedule.setCallSignRecord(doctorSchedule.getCallSignRecord() != null ? doctorSchedule.getCallSignRecord() : "");
|
newSchedule.setCallSignRecord(
|
||||||
|
doctorSchedule.getCallSignRecord() != null ? doctorSchedule.getCallSignRecord() : "");
|
||||||
newSchedule.setRegisterItem(doctorSchedule.getRegisterItem() != null ? doctorSchedule.getRegisterItem() : "");
|
newSchedule.setRegisterItem(doctorSchedule.getRegisterItem() != null ? doctorSchedule.getRegisterItem() : "");
|
||||||
newSchedule.setRegisterFee(doctorSchedule.getRegisterFee() != null ? doctorSchedule.getRegisterFee() : 0);
|
newSchedule.setRegisterFee(doctorSchedule.getRegisterFee() != null ? doctorSchedule.getRegisterFee() : 0);
|
||||||
newSchedule.setDiagnosisItem(doctorSchedule.getDiagnosisItem() != null ? doctorSchedule.getDiagnosisItem() : "");
|
newSchedule
|
||||||
|
.setDiagnosisItem(doctorSchedule.getDiagnosisItem() != null ? doctorSchedule.getDiagnosisItem() : "");
|
||||||
newSchedule.setDiagnosisFee(doctorSchedule.getDiagnosisFee() != null ? doctorSchedule.getDiagnosisFee() : 0);
|
newSchedule.setDiagnosisFee(doctorSchedule.getDiagnosisFee() != null ? doctorSchedule.getDiagnosisFee() : 0);
|
||||||
newSchedule.setIsOnline(doctorSchedule.getIsOnline() != null ? doctorSchedule.getIsOnline() : true);
|
newSchedule.setIsOnline(doctorSchedule.getIsOnline() != null ? doctorSchedule.getIsOnline() : true);
|
||||||
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
|
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
|
||||||
@@ -156,7 +132,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
if (poolSaved) {
|
if (poolSaved) {
|
||||||
// 创建号源槽
|
// 创建号源槽
|
||||||
List<ScheduleSlot> slots = createScheduleSlots(pool.getId().intValue(), newSchedule.getLimitNumber(),
|
List<ScheduleSlot> slots = createScheduleSlots(pool.getId().intValue(), newSchedule.getLimitNumber(),
|
||||||
newSchedule.getStartTime(), newSchedule.getEndTime());
|
newSchedule.getStartTime(), newSchedule.getEndTime());
|
||||||
boolean slotsSaved = scheduleSlotService.saveBatch(slots);
|
boolean slotsSaved = scheduleSlotService.saveBatch(slots);
|
||||||
|
|
||||||
if (slotsSaved) {
|
if (slotsSaved) {
|
||||||
@@ -168,8 +144,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("创建号源槽失败");
|
throw new RuntimeException("创建号源槽失败");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throw new RuntimeException("创建号源池失败");
|
throw new RuntimeException("创建号源池失败");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -197,10 +172,12 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
newSchedule.setEndTime(doctorSchedule.getEndTime());
|
newSchedule.setEndTime(doctorSchedule.getEndTime());
|
||||||
newSchedule.setLimitNumber(doctorSchedule.getLimitNumber());
|
newSchedule.setLimitNumber(doctorSchedule.getLimitNumber());
|
||||||
// call_sign_record 字段不能为null,设置默认值为空字符串
|
// call_sign_record 字段不能为null,设置默认值为空字符串
|
||||||
newSchedule.setCallSignRecord(doctorSchedule.getCallSignRecord() != null ? doctorSchedule.getCallSignRecord() : "");
|
newSchedule.setCallSignRecord(
|
||||||
|
doctorSchedule.getCallSignRecord() != null ? doctorSchedule.getCallSignRecord() : "");
|
||||||
newSchedule.setRegisterItem(doctorSchedule.getRegisterItem() != null ? doctorSchedule.getRegisterItem() : "");
|
newSchedule.setRegisterItem(doctorSchedule.getRegisterItem() != null ? doctorSchedule.getRegisterItem() : "");
|
||||||
newSchedule.setRegisterFee(doctorSchedule.getRegisterFee() != null ? doctorSchedule.getRegisterFee() : 0);
|
newSchedule.setRegisterFee(doctorSchedule.getRegisterFee() != null ? doctorSchedule.getRegisterFee() : 0);
|
||||||
newSchedule.setDiagnosisItem(doctorSchedule.getDiagnosisItem() != null ? doctorSchedule.getDiagnosisItem() : "");
|
newSchedule
|
||||||
|
.setDiagnosisItem(doctorSchedule.getDiagnosisItem() != null ? doctorSchedule.getDiagnosisItem() : "");
|
||||||
newSchedule.setDiagnosisFee(doctorSchedule.getDiagnosisFee() != null ? doctorSchedule.getDiagnosisFee() : 0);
|
newSchedule.setDiagnosisFee(doctorSchedule.getDiagnosisFee() != null ? doctorSchedule.getDiagnosisFee() : 0);
|
||||||
newSchedule.setIsOnline(doctorSchedule.getIsOnline() != null ? doctorSchedule.getIsOnline() : true);
|
newSchedule.setIsOnline(doctorSchedule.getIsOnline() != null ? doctorSchedule.getIsOnline() : true);
|
||||||
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
|
newSchedule.setIsStopped(doctorSchedule.getIsStopped() != null ? doctorSchedule.getIsStopped() : false);
|
||||||
@@ -220,7 +197,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
if (poolSaved) {
|
if (poolSaved) {
|
||||||
// 创建号源槽
|
// 创建号源槽
|
||||||
List<ScheduleSlot> slots = createScheduleSlots(pool.getId().intValue(), newSchedule.getLimitNumber(),
|
List<ScheduleSlot> slots = createScheduleSlots(pool.getId().intValue(), newSchedule.getLimitNumber(),
|
||||||
newSchedule.getStartTime(), newSchedule.getEndTime());
|
newSchedule.getStartTime(), newSchedule.getEndTime());
|
||||||
boolean slotsSaved = scheduleSlotService.saveBatch(slots);
|
boolean slotsSaved = scheduleSlotService.saveBatch(slots);
|
||||||
|
|
||||||
if (slotsSaved) {
|
if (slotsSaved) {
|
||||||
@@ -228,8 +205,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("创建号源槽失败");
|
throw new RuntimeException("创建号源槽失败");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throw new RuntimeException("创建号源池失败");
|
throw new RuntimeException("创建号源池失败");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -343,7 +319,8 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
/**
|
/**
|
||||||
* 创建号源槽
|
* 创建号源槽
|
||||||
*/
|
*/
|
||||||
private List<ScheduleSlot> createScheduleSlots(Integer poolId, Integer limitNumber, LocalTime startTime, LocalTime endTime) {
|
private List<ScheduleSlot> createScheduleSlots(Integer poolId, Integer limitNumber, LocalTime startTime,
|
||||||
|
LocalTime endTime) {
|
||||||
List<ScheduleSlot> slots = new ArrayList<>();
|
List<ScheduleSlot> slots = new ArrayList<>();
|
||||||
|
|
||||||
// 计算时间间隔
|
// 计算时间间隔
|
||||||
@@ -353,7 +330,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
for (int i = 1; i <= limitNumber; i++) {
|
for (int i = 1; i <= limitNumber; i++) {
|
||||||
ScheduleSlot slot = new ScheduleSlot();
|
ScheduleSlot slot = new ScheduleSlot();
|
||||||
slot.setPoolId(poolId);
|
slot.setPoolId(poolId);
|
||||||
slot.setSeqNo(i); // 序号
|
slot.setSeqNo(i); // 序号
|
||||||
slot.setStatus(0); // 0表示可用
|
slot.setStatus(0); // 0表示可用
|
||||||
// 计算预计叫号时间,均匀分布在开始时间和结束时间之间
|
// 计算预计叫号时间,均匀分布在开始时间和结束时间之间
|
||||||
LocalTime expectTime = startTime.plusMinutes(interval * (i - 1));
|
LocalTime expectTime = startTime.plusMinutes(interval * (i - 1));
|
||||||
@@ -387,14 +364,22 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
*/
|
*/
|
||||||
private int getDayOfWeekNumber(String weekday) {
|
private int getDayOfWeekNumber(String weekday) {
|
||||||
switch (weekday) {
|
switch (weekday) {
|
||||||
case "周一": return 1;
|
case "周一":
|
||||||
case "周二": return 2;
|
return 1;
|
||||||
case "周三": return 3;
|
case "周二":
|
||||||
case "周四": return 4;
|
return 2;
|
||||||
case "周五": return 5;
|
case "周三":
|
||||||
case "周六": return 6;
|
return 3;
|
||||||
case "周日": return 7;
|
case "周四":
|
||||||
default: return 1; // 默认周一
|
return 4;
|
||||||
|
case "周五":
|
||||||
|
return 5;
|
||||||
|
case "周六":
|
||||||
|
return 6;
|
||||||
|
case "周日":
|
||||||
|
return 7;
|
||||||
|
default:
|
||||||
|
return 1; // 默认周一
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,8 +393,7 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
// 1. 根据排班ID找到关联的号源池
|
// 1. 根据排班ID找到关联的号源池
|
||||||
List<SchedulePool> pools = schedulePoolService.list(
|
List<SchedulePool> pools = schedulePoolService.list(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SchedulePool>()
|
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SchedulePool>()
|
||||||
.eq("schedule_id", doctorScheduleId)
|
.eq("schedule_id", doctorScheduleId));
|
||||||
);
|
|
||||||
|
|
||||||
if (ObjectUtil.isNotEmpty(pools)) {
|
if (ObjectUtil.isNotEmpty(pools)) {
|
||||||
List<Long> poolIds = pools.stream().map(SchedulePool::getId).collect(java.util.stream.Collectors.toList());
|
List<Long> poolIds = pools.stream().map(SchedulePool::getId).collect(java.util.stream.Collectors.toList());
|
||||||
@@ -417,11 +401,11 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
// 2. 根据号源池ID找到所有关联的号源槽
|
// 2. 根据号源池ID找到所有关联的号源槽
|
||||||
List<ScheduleSlot> slots = scheduleSlotService.list(
|
List<ScheduleSlot> slots = scheduleSlotService.list(
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<ScheduleSlot>()
|
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<ScheduleSlot>()
|
||||||
.in("pool_id", poolIds)
|
.in("pool_id", poolIds));
|
||||||
);
|
|
||||||
|
|
||||||
if (ObjectUtil.isNotEmpty(slots)) {
|
if (ObjectUtil.isNotEmpty(slots)) {
|
||||||
List<Integer> slotIds = slots.stream().map(ScheduleSlot::getId).collect(java.util.stream.Collectors.toList());
|
List<Integer> slotIds = slots.stream().map(ScheduleSlot::getId)
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
// 3. 逻辑删除所有号源槽
|
// 3. 逻辑删除所有号源槽
|
||||||
scheduleSlotService.removeByIds(slotIds);
|
scheduleSlotService.removeByIds(slotIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,4 +89,13 @@ public class DoctorScheduleController {
|
|||||||
return R.ok(doctorScheduleAppService.getTodayDoctorScheduleList());
|
return R.ok(doctorScheduleAppService.getTodayDoctorScheduleList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 获取当前登录医生今日排班List
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
@GetMapping("/today-my-schedule")
|
||||||
|
public R<?> getTodayMySchedule() {
|
||||||
|
return doctorScheduleAppService.getTodayMySchedule();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.openhis.web.basedatamanage.appservice;
|
|||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -15,18 +14,19 @@ public interface IOrganizationAppService {
|
|||||||
/**
|
/**
|
||||||
* 查询机构树
|
* 查询机构树
|
||||||
*
|
*
|
||||||
* @param pageNo 当前页码
|
* @param pageNo 当前页码
|
||||||
* @param pageSize 查询条数
|
* @param pageSize 查询条数
|
||||||
* @param name 科室名称
|
* @param name 科室名称
|
||||||
* @param typeEnum 科室类型
|
* @param typeEnum 科室类型
|
||||||
* @param classEnumList 科室分类列表(逗号分隔的值)
|
* @param classEnumList 科室分类列表(逗号分隔的值)
|
||||||
* @param sortField 排序字段
|
* @param sortField 排序字段
|
||||||
* @param sortOrder 排序方向
|
* @param sortOrder 排序方向
|
||||||
* @param request 请求数据
|
* @param request 请求数据
|
||||||
* @return 机构树分页列表
|
* @return 机构树分页列表
|
||||||
*/
|
*/
|
||||||
Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, List<String> classEnumList,
|
Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum,
|
||||||
String sortField, String sortOrder, HttpServletRequest request);
|
List<String> classEnumList,
|
||||||
|
String sortField, String sortOrder, HttpServletRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机构信息详情
|
* 机构信息详情
|
||||||
@@ -71,9 +71,12 @@ public interface IOrganizationAppService {
|
|||||||
/**
|
/**
|
||||||
* 获取挂号科室列表
|
* 获取挂号科室列表
|
||||||
*
|
*
|
||||||
|
* @param pageNum 当前页码
|
||||||
|
* @param pageSize 查询条数
|
||||||
|
* @param name 机构/科室名称
|
||||||
|
* @param orgName 机构名称
|
||||||
* @return 挂号科室列表
|
* @return 挂号科室列表
|
||||||
*/
|
*/
|
||||||
R<?> getRegisterOrganizations(@RequestParam(required = false) String name,
|
R<?> getRegisterOrganizations(Integer pageNum, Integer pageSize, String name, String orgName);
|
||||||
@RequestParam(required = false) String orgName);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.openhis.web.basedatamanage.appservice.impl;
|
package com.openhis.web.basedatamanage.appservice.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.core.common.utils.AssignSeqUtil;
|
import com.core.common.utils.AssignSeqUtil;
|
||||||
@@ -20,7 +19,6 @@ import com.openhis.web.basedatamanage.appservice.IOrganizationAppService;
|
|||||||
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -28,8 +26,6 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
||||||
|
|
||||||
@@ -40,8 +36,9 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
private AssignSeqUtil assignSeqUtil;
|
private AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, List<String> classEnumList,
|
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum,
|
||||||
String sortField, String sortOrder, HttpServletRequest request) {
|
List<String> classEnumList,
|
||||||
|
String sortField, String sortOrder, HttpServletRequest request) {
|
||||||
|
|
||||||
// 使用Page对象进行分页查询
|
// 使用Page对象进行分页查询
|
||||||
Page<Organization> page = new Page<>(pageNo, pageSize);
|
Page<Organization> page = new Page<>(pageNo, pageSize);
|
||||||
@@ -64,24 +61,24 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// 第一个条件
|
// 第一个条件
|
||||||
wrapper.and(subWrapper -> {
|
wrapper.and(subWrapper -> {
|
||||||
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 后续条件使用OR连接
|
// 后续条件使用OR连接
|
||||||
wrapper.or(subWrapper -> {
|
wrapper.or(subWrapper -> {
|
||||||
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
||||||
.or() // 或者
|
.or() // 或者
|
||||||
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +111,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
private List<OrganizationDto> buildTree(List<Organization> records) {
|
private List<OrganizationDto> buildTree(List<Organization> records) {
|
||||||
// 按b_no的层级排序,确保父节点先处理
|
// 按b_no的层级排序,确保父节点先处理
|
||||||
List<Organization> sortedRecords = records.stream()
|
List<Organization> sortedRecords = records.stream()
|
||||||
.sorted(Comparator.comparingInt(r -> r.getBusNo().split("\\.").length)).collect(Collectors.toList());
|
.sorted(Comparator.comparingInt(r -> r.getBusNo().split("\\.").length)).collect(Collectors.toList());
|
||||||
|
|
||||||
Map<String, OrganizationDto> nodeMap = new HashMap<>();
|
Map<String, OrganizationDto> nodeMap = new HashMap<>();
|
||||||
List<OrganizationDto> tree = new ArrayList<>();
|
List<OrganizationDto> tree = new ArrayList<>();
|
||||||
@@ -159,17 +156,20 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
public R<?> getOrgInfo(Long orgId) {
|
public R<?> getOrgInfo(Long orgId) {
|
||||||
Organization organization = organizationService.getById(orgId);
|
Organization organization = organizationService.getById(orgId);
|
||||||
if (organization == null) {
|
if (organization == null) {
|
||||||
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, new Object[] {"机构信息"}));
|
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, new Object[] { "机构信息" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换为DTO对象,确保数据格式一致
|
// 转换为DTO对象,确保数据格式一致
|
||||||
OrganizationDto organizationDto = new OrganizationDto();
|
OrganizationDto organizationDto = new OrganizationDto();
|
||||||
BeanUtils.copyProperties(organization, organizationDto);
|
BeanUtils.copyProperties(organization, organizationDto);
|
||||||
organizationDto.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, organizationDto.getTypeEnum()));
|
organizationDto
|
||||||
|
.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, organizationDto.getTypeEnum()));
|
||||||
organizationDto.setClassEnum_dictText(formatClassEnumDictText(organizationDto.getClassEnum()));
|
organizationDto.setClassEnum_dictText(formatClassEnumDictText(organizationDto.getClassEnum()));
|
||||||
organizationDto.setActiveFlag_dictText(EnumUtils.getInfoByValue(AccountStatus.class, organizationDto.getActiveFlag()));
|
organizationDto
|
||||||
|
.setActiveFlag_dictText(EnumUtils.getInfoByValue(AccountStatus.class, organizationDto.getActiveFlag()));
|
||||||
|
|
||||||
return R.ok(organizationDto, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"机构信息查询"}));
|
return R.ok(organizationDto,
|
||||||
|
MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] { "机构信息查询" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -196,7 +196,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
// 如果传了上级科室 把当前的code拼到后边
|
// 如果传了上级科室 把当前的code拼到后边
|
||||||
if (StringUtils.isNotEmpty(organization.getBusNo())) {
|
if (StringUtils.isNotEmpty(organization.getBusNo())) {
|
||||||
organization.setBusNo(String.format(CommonConstants.Common.MONTAGE_FORMAT, organization.getBusNo(),
|
organization.setBusNo(String.format(CommonConstants.Common.MONTAGE_FORMAT, organization.getBusNo(),
|
||||||
CommonConstants.Common.POINT, code));
|
CommonConstants.Common.POINT, code));
|
||||||
} else {
|
} else {
|
||||||
organization.setBusNo(code);
|
organization.setBusNo(code);
|
||||||
}
|
}
|
||||||
@@ -205,7 +205,7 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
}
|
}
|
||||||
// 返回机构id
|
// 返回机构id
|
||||||
return R.ok(organization.getId(),
|
return R.ok(organization.getId(),
|
||||||
MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"机构信息更新添加"}));
|
MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] { "机构信息更新添加" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,8 +225,8 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
// 删除机构信息
|
// 删除机构信息
|
||||||
boolean deleteOrgSuccess = organizationService.removeByIds(orgIdList);
|
boolean deleteOrgSuccess = organizationService.removeByIds(orgIdList);
|
||||||
return deleteOrgSuccess
|
return deleteOrgSuccess
|
||||||
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00005, new Object[] {"机构信息"}))
|
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00005, new Object[] { "机构信息" }))
|
||||||
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] {"机构信息"}));
|
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] { "机构信息" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -239,8 +239,9 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
public R<?> activeOrg(Long orgId) {
|
public R<?> activeOrg(Long orgId) {
|
||||||
// 机构启用
|
// 机构启用
|
||||||
boolean result = organizationService.activeOrg(orgId);
|
boolean result = organizationService.activeOrg(orgId);
|
||||||
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"机构信息启用"}))
|
return result
|
||||||
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] {"机构信息启用"}));
|
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] { "机构信息启用" }))
|
||||||
|
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] { "机构信息启用" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,8 +254,9 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
public R<?> inactiveOrg(Long orgId) {
|
public R<?> inactiveOrg(Long orgId) {
|
||||||
// 机构停用
|
// 机构停用
|
||||||
boolean result = organizationService.inactiveOrg(orgId);
|
boolean result = organizationService.inactiveOrg(orgId);
|
||||||
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"机构信息停用"}))
|
return result
|
||||||
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] {"机构信息停用"}));
|
? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] { "机构信息停用" }))
|
||||||
|
: R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00007, new Object[] { "机构信息停用" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -289,27 +291,41 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
/**
|
/**
|
||||||
* 获取挂号科室列表
|
* 获取挂号科室列表
|
||||||
*
|
*
|
||||||
* @param name 机构/科室名称
|
* @param pageNo 当前页码
|
||||||
* @param orgName 机构名称
|
* @param pageSize 查询条数
|
||||||
|
* @param name 机构/科室名称
|
||||||
|
* @param orgName 机构名称
|
||||||
* @return 挂号科室列表
|
* @return 挂号科室列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> getRegisterOrganizations(@RequestParam(required = false) String name,
|
public R<?> getRegisterOrganizations(Integer pageNo, Integer pageSize, String name, String orgName) {
|
||||||
@RequestParam(required = false) String orgName) {
|
// 使用Page对象进行分页查询
|
||||||
|
Page<Organization> page = new Page<>(pageNo != null ? pageNo : 1, pageSize != null ? pageSize : 10);
|
||||||
|
|
||||||
// 创建查询条件,只查询register_flag为1的组织机构
|
// 创建查询条件,只查询register_flag为1的组织机构
|
||||||
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(Organization::getRegisterFlag, 1); // 只获取挂号科室
|
queryWrapper.eq(Organization::getRegisterFlag, 1); // 只获取挂号科室
|
||||||
|
queryWrapper.eq(Organization::getDeleteFlag, "0"); // 确保未删除
|
||||||
|
|
||||||
// 添加名称过滤条件
|
// 添加名称过滤条件
|
||||||
if (StringUtils.isNotEmpty(name)) {
|
if (StringUtils.isNotEmpty(name)) {
|
||||||
queryWrapper.like(Organization::getName, name);
|
queryWrapper.like(Organization::getName, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行查询
|
// 如果有机构名称筛选
|
||||||
List<Organization> organizationList = organizationService.list(queryWrapper);
|
if (StringUtils.isNotEmpty(orgName)) {
|
||||||
|
// 这里假设 orgName 是父机构名称,如果需要更复杂的关联查询可在此扩展
|
||||||
|
// 当前逻辑暂保持与原逻辑一致的过滤方式或根据需求调整
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按编码排序
|
||||||
|
queryWrapper.orderByAsc(Organization::getBusNo);
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
Page<Organization> resultPage = organizationService.page(page, queryWrapper);
|
||||||
|
|
||||||
// 转换为DTO对象并设置字典文本
|
// 转换为DTO对象并设置字典文本
|
||||||
List<OrganizationDto> organizationDtoList = organizationList.stream().map(org -> {
|
List<OrganizationDto> organizationDtoList = resultPage.getRecords().stream().map(org -> {
|
||||||
OrganizationDto dto = new OrganizationDto();
|
OrganizationDto dto = new OrganizationDto();
|
||||||
BeanUtils.copyProperties(org, dto);
|
BeanUtils.copyProperties(org, dto);
|
||||||
dto.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, dto.getTypeEnum()));
|
dto.setTypeEnum_dictText(EnumUtils.getInfoByValue(OrganizationType.class, dto.getTypeEnum()));
|
||||||
@@ -318,7 +334,14 @@ public class OrganizationAppServiceImpl implements IOrganizationAppService {
|
|||||||
return dto;
|
return dto;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
return R.ok(organizationDtoList);
|
// 创建返回分页对象
|
||||||
|
Page<OrganizationDto> finalResult = new Page<>();
|
||||||
|
finalResult.setRecords(organizationDtoList);
|
||||||
|
finalResult.setTotal(resultPage.getTotal());
|
||||||
|
finalResult.setSize(resultPage.getSize());
|
||||||
|
finalResult.setCurrent(resultPage.getCurrent());
|
||||||
|
|
||||||
|
return R.ok(finalResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,24 +42,24 @@ public class OrganizationController {
|
|||||||
/**
|
/**
|
||||||
* 机构分页列表
|
* 机构分页列表
|
||||||
*
|
*
|
||||||
* @param pageNo 当前页码
|
* @param pageNo 当前页码
|
||||||
* @param pageSize 查询条数
|
* @param pageSize 查询条数
|
||||||
* @param name 科室名称
|
* @param name 科室名称
|
||||||
* @param typeEnum 科室类型
|
* @param typeEnum 科室类型
|
||||||
* @param classEnum 科室分类(支持多选,逗号分隔)
|
* @param classEnum 科室分类(支持多选,逗号分隔)
|
||||||
* @param sortField 排序字段
|
* @param sortField 排序字段
|
||||||
* @param sortOrder 排序方向
|
* @param sortOrder 排序方向
|
||||||
* @param request 请求对象
|
* @param request 请求对象
|
||||||
* @return 机构分页列表
|
* @return 机构分页列表
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/organization")
|
@GetMapping(value = "/organization")
|
||||||
public R<?> getOrganizationPage(@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
public R<?> getOrganizationPage(@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(value = "pageSize", defaultValue = "100") Integer pageSize,
|
@RequestParam(value = "pageSize", defaultValue = "100") Integer pageSize,
|
||||||
@RequestParam(value = "name", required = false) String name,
|
@RequestParam(value = "name", required = false) String name,
|
||||||
@RequestParam(value = "typeEnum", required = false) Integer typeEnum,
|
@RequestParam(value = "typeEnum", required = false) Integer typeEnum,
|
||||||
@RequestParam(value = "classEnum", required = false) String classEnum,
|
@RequestParam(value = "classEnum", required = false) String classEnum,
|
||||||
@RequestParam(value = "sortField", required = false) String sortField,
|
@RequestParam(value = "sortField", required = false) String sortField,
|
||||||
@RequestParam(value = "sortOrder", required = false) String sortOrder, HttpServletRequest request) {
|
@RequestParam(value = "sortOrder", required = false) String sortOrder, HttpServletRequest request) {
|
||||||
|
|
||||||
// 解析classEnum参数,支持逗号分隔的多个值
|
// 解析classEnum参数,支持逗号分隔的多个值
|
||||||
List<String> classEnumList = null;
|
List<String> classEnumList = null;
|
||||||
@@ -67,10 +67,10 @@ public class OrganizationController {
|
|||||||
classEnumList = Arrays.asList(classEnum.split(","));
|
classEnumList = Arrays.asList(classEnum.split(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
Page<OrganizationDto> organizationTree =
|
Page<OrganizationDto> organizationTree = iOrganizationAppService.getOrganizationTree(pageNo, pageSize, name,
|
||||||
iOrganizationAppService.getOrganizationTree(pageNo, pageSize, name, typeEnum, classEnumList, sortField, sortOrder, request);
|
typeEnum, classEnumList, sortField, sortOrder, request);
|
||||||
return R.ok(organizationTree,
|
return R.ok(organizationTree,
|
||||||
MessageUtils.createMessage(PromptMsgConstant.Common.M00009, new Object[] {"机构信息"}));
|
MessageUtils.createMessage(PromptMsgConstant.Common.M00009, new Object[] { "机构信息" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,14 +133,18 @@ public class OrganizationController {
|
|||||||
/**
|
/**
|
||||||
* 获取挂号科室列表
|
* 获取挂号科室列表
|
||||||
*
|
*
|
||||||
* @param name 机构/科室名称
|
* @param pageNum 当前页码
|
||||||
* @param orgName 机构名称
|
* @param pageSize 查询条数
|
||||||
|
* @param name 机构/科室名称
|
||||||
|
* @param orgName 机构名称
|
||||||
* @return 挂号科室列表
|
* @return 挂号科室列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/register-organizations")
|
@GetMapping("/register-organizations")
|
||||||
public R<?> getRegisterOrganizations(@RequestParam(required = false) String name,
|
public R<?> getRegisterOrganizations(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
|
||||||
@RequestParam(required = false) String orgName) {
|
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
|
||||||
return iOrganizationAppService.getRegisterOrganizations(name, orgName);
|
@RequestParam(required = false) String name,
|
||||||
|
@RequestParam(required = false) String orgName) {
|
||||||
|
return iOrganizationAppService.getRegisterOrganizations(pageNum, pageSize, name, orgName);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.appservice;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.web.cardmanagement.dto.*;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报卡管理 Service接口
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
public interface ICardManageAppService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计数据
|
||||||
|
*
|
||||||
|
* @return 统计数据
|
||||||
|
*/
|
||||||
|
CardStatisticsDto getStatistics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return 分页数据
|
||||||
|
*/
|
||||||
|
R<?> getCardPage(CardQueryDto queryParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取报卡详情
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 报卡详情
|
||||||
|
*/
|
||||||
|
InfectiousCardDto getCardDetail(String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核记录
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 审核记录列表
|
||||||
|
*/
|
||||||
|
List<AuditRecordDto> getAuditRecords(String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量审核
|
||||||
|
*
|
||||||
|
* @param batchAuditDto 批量审核参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> batchAudit(BatchAuditDto batchAuditDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量退回
|
||||||
|
*
|
||||||
|
* @param batchReturnDto 批量退回参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> batchReturn(BatchReturnDto batchReturnDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条审核通过
|
||||||
|
*
|
||||||
|
* @param auditDto 审核参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> auditPass(SingleAuditDto auditDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条退回
|
||||||
|
*
|
||||||
|
* @param returnDto 退回参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> auditReturn(SingleReturnDto returnDto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
void exportCards(CardQueryDto queryParams, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取科室树
|
||||||
|
*
|
||||||
|
* @return 科室树数据
|
||||||
|
*/
|
||||||
|
R<?> getDeptTree();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取医生个人报卡统计数据
|
||||||
|
*
|
||||||
|
* @return 统计数据
|
||||||
|
*/
|
||||||
|
DoctorCardStatisticsDto getDoctorCardStatistics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询医生个人报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return 分页数据
|
||||||
|
*/
|
||||||
|
R<?> getDoctorCardPage(DoctorCardQueryDto queryParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交报卡
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> submitCard(String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回报卡
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> withdrawCard(String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除报卡(状态变为作废)
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> deleteCard(String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量提交报卡
|
||||||
|
*
|
||||||
|
* @param cardNos 卡片编号列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> batchSubmitCards(List<String> cardNos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除报卡
|
||||||
|
*
|
||||||
|
* @param cardNos 卡片编号列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> batchDeleteCards(List<String> cardNos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出报卡为Word文档
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
void exportCardToWord(String cardNo, HttpServletResponse response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新医生报卡
|
||||||
|
*
|
||||||
|
* @param updateDto 更新参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
R<?> updateDoctorCard(DoctorCardUpdateDto updateDto);
|
||||||
|
}
|
||||||
@@ -0,0 +1,692 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.appservice.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
|
import com.openhis.infectious.domain.InfectiousAudit;
|
||||||
|
import com.openhis.infectious.domain.InfectiousCard;
|
||||||
|
import com.openhis.web.cardmanagement.appservice.ICardManageAppService;
|
||||||
|
import com.openhis.web.cardmanagement.dto.*;
|
||||||
|
import com.openhis.web.cardmanagement.mapper.InfectiousAuditMapper;
|
||||||
|
import com.openhis.web.cardmanagement.mapper.InfectiousCardMapper;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报卡管理 Service 实现
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CardManageAppServiceImpl implements ICardManageAppService {
|
||||||
|
|
||||||
|
private final InfectiousCardMapper infectiousCardMapper;
|
||||||
|
private final InfectiousAuditMapper infectiousAuditMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CardStatisticsDto getStatistics() {
|
||||||
|
CardStatisticsDto dto = new CardStatisticsDto();
|
||||||
|
dto.setTodayPending(infectiousCardMapper.countTodayPending());
|
||||||
|
dto.setMonthFailed(infectiousCardMapper.countMonthFailed());
|
||||||
|
dto.setMonthSuccess(infectiousCardMapper.countMonthSuccess());
|
||||||
|
dto.setMonthReported(infectiousCardMapper.countMonthReported());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> getCardPage(CardQueryDto queryParams) {
|
||||||
|
Page<InfectiousCard> page = new Page<>(queryParams.getPageNo(), queryParams.getPageSize());
|
||||||
|
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 登记来源
|
||||||
|
if (queryParams.getRegistrationSource() != null) {
|
||||||
|
wrapper.eq(InfectiousCard::getRegistrationSource, queryParams.getRegistrationSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
if (StringUtils.hasText(queryParams.getStatus())) {
|
||||||
|
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 患者姓名模糊查询
|
||||||
|
if (StringUtils.hasText(queryParams.getPatientName())) {
|
||||||
|
wrapper.like(InfectiousCard::getPatName, queryParams.getPatientName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 科室
|
||||||
|
if (queryParams.getDeptId() != null) {
|
||||||
|
wrapper.eq(InfectiousCard::getDeptId, queryParams.getDeptId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围
|
||||||
|
if (StringUtils.hasText(queryParams.getStartDate())) {
|
||||||
|
LocalDateTime startDateTime = LocalDateTime.parse(queryParams.getStartDate() + "T00:00:00");
|
||||||
|
wrapper.ge(InfectiousCard::getCreateTime, startDateTime);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getEndDate())) {
|
||||||
|
LocalDateTime endDateTime = LocalDateTime.parse(queryParams.getEndDate() + "T23:59:59");
|
||||||
|
wrapper.le(InfectiousCard::getCreateTime, endDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间倒序
|
||||||
|
wrapper.orderByDesc(InfectiousCard::getCreateTime);
|
||||||
|
|
||||||
|
IPage<InfectiousCard> result = infectiousCardMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
// 转换为 DTO
|
||||||
|
List<InfectiousCardDto> list = result.getRecords().stream().map(this::convertToDto).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("list", list);
|
||||||
|
resultMap.put("total", result.getTotal());
|
||||||
|
return R.ok(resultMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InfectiousCardDto getCardDetail(String cardNo) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return convertToDto(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuditRecordDto> getAuditRecords(String cardNo) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<InfectiousAudit> records = infectiousAuditMapper.selectByCardId(card.getId());
|
||||||
|
return records.stream().map(this::convertAuditToDto).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> batchAudit(BatchAuditDto batchAuditDto) {
|
||||||
|
if (batchAuditDto.getCardNos() == null || batchAuditDto.getCardNos().isEmpty()) {
|
||||||
|
return R.fail("请选择要审核的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
String auditorId = SecurityUtils.getUserId().toString();
|
||||||
|
String auditorName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
for (String cardNo : batchAuditDto.getCardNos()) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) continue;
|
||||||
|
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
|
||||||
|
|
||||||
|
// 更新状态为已审核
|
||||||
|
String oldStatus = card.getStatus();
|
||||||
|
card.setStatus("2");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
// 创建审核记录
|
||||||
|
createAuditRecord(card.getId(), oldStatus, "2", "1", batchAuditDto.getAuditOpinion(),
|
||||||
|
null, auditorId, auditorName, true, batchAuditDto.getCardNos().size());
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok("批量审核成功,共审核" + successCount + "条");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> batchReturn(BatchReturnDto batchReturnDto) {
|
||||||
|
if (batchReturnDto.getCardNos() == null || batchReturnDto.getCardNos().isEmpty()) {
|
||||||
|
return R.fail("请选择要退回的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
String auditorId = SecurityUtils.getUserId().toString();
|
||||||
|
String auditorName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
for (String cardNo : batchReturnDto.getCardNos()) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) continue;
|
||||||
|
if ("2".equals(card.getStatus()) || "3".equals(card.getStatus())) continue;
|
||||||
|
|
||||||
|
// 更新状态为退回 (审核失败)
|
||||||
|
String oldStatus = card.getStatus();
|
||||||
|
card.setStatus("5");
|
||||||
|
card.setReturnReason(batchReturnDto.getReturnReason());
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
// 创建审核记录
|
||||||
|
createAuditRecord(card.getId(), oldStatus, "5", "3", null,
|
||||||
|
batchReturnDto.getReturnReason(), auditorId, auditorName, true, batchReturnDto.getCardNos().size());
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok("批量退回成功,共退回" + successCount + "条");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> auditPass(SingleAuditDto auditDto) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(auditDto.getCardNo());
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
String auditorId = SecurityUtils.getUserId().toString();
|
||||||
|
String auditorName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
String oldStatus = card.getStatus();
|
||||||
|
card.setStatus("2");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
// 创建审核记录
|
||||||
|
createAuditRecord(card.getId(), oldStatus, "2", "2", auditDto.getAuditOpinion(),
|
||||||
|
null, auditorId, auditorName, false, 1);
|
||||||
|
|
||||||
|
return R.ok("审核通过");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> auditReturn(SingleReturnDto returnDto) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(returnDto.getCardNo());
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
String auditorId = SecurityUtils.getUserId().toString();
|
||||||
|
String auditorName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
String oldStatus = card.getStatus();
|
||||||
|
card.setStatus("5");
|
||||||
|
card.setReturnReason(returnDto.getReturnReason());
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
// 创建审核记录
|
||||||
|
createAuditRecord(card.getId(), oldStatus, "5", "4", null,
|
||||||
|
returnDto.getReturnReason(), auditorId, auditorName, false, 1);
|
||||||
|
|
||||||
|
return R.ok("已退回");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportCards(CardQueryDto queryParams, HttpServletResponse response) {
|
||||||
|
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 应用查询条件
|
||||||
|
if (queryParams.getRegistrationSource() != null) {
|
||||||
|
wrapper.eq(InfectiousCard::getRegistrationSource, queryParams.getRegistrationSource());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getStatus())) {
|
||||||
|
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getPatientName())) {
|
||||||
|
wrapper.like(InfectiousCard::getPatName, queryParams.getPatientName());
|
||||||
|
}
|
||||||
|
if (queryParams.getDeptId() != null) {
|
||||||
|
wrapper.eq(InfectiousCard::getDeptId, queryParams.getDeptId());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getStartDate())) {
|
||||||
|
LocalDateTime startDateTime = LocalDateTime.parse(queryParams.getStartDate() + "T00:00:00");
|
||||||
|
wrapper.ge(InfectiousCard::getCreateTime, startDateTime);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getEndDate())) {
|
||||||
|
LocalDateTime endDateTime = LocalDateTime.parse(queryParams.getEndDate() + "T23:59:59");
|
||||||
|
wrapper.le(InfectiousCard::getCreateTime, endDateTime);
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(InfectiousCard::getCreateTime);
|
||||||
|
|
||||||
|
List<InfectiousCard> cards = infectiousCardMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
try (Workbook workbook = new XSSFWorkbook()) {
|
||||||
|
Sheet sheet = workbook.createSheet("报卡列表");
|
||||||
|
|
||||||
|
// 创建表头
|
||||||
|
Row headerRow = sheet.createRow(0);
|
||||||
|
String[] headers = {"报卡编号", "患者姓名", "性别", "年龄", "疾病名称", "科室", "上报时间", "状态"};
|
||||||
|
for (int i = 0; i < headers.length; i++) {
|
||||||
|
Cell cell = headerRow.createCell(i);
|
||||||
|
cell.setCellValue(headers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充数据
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
for (int i = 0; i < cards.size(); i++) {
|
||||||
|
InfectiousCard card = cards.get(i);
|
||||||
|
Row row = sheet.createRow(i + 1);
|
||||||
|
row.createCell(0).setCellValue(card.getCardNo());
|
||||||
|
row.createCell(1).setCellValue(card.getPatName());
|
||||||
|
row.createCell(2).setCellValue("1".equals(card.getSex()) ? "男" : "2".equals(card.getSex()) ? "女" : "未知");
|
||||||
|
row.createCell(3).setCellValue(card.getAge() != null ? card.getAge() + "岁" : "");
|
||||||
|
row.createCell(4).setCellValue(card.getDiseaseName());
|
||||||
|
row.createCell(5).setCellValue(card.getDeptName());
|
||||||
|
row.createCell(6).setCellValue(card.getCreateTime() != null ? dateFormat.format(card.getCreateTime()) : "");
|
||||||
|
row.createCell(7).setCellValue(getStatusText(card.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" +
|
||||||
|
URLEncoder.encode("报卡列表.xlsx", StandardCharsets.UTF_8));
|
||||||
|
workbook.write(response.getOutputStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("导出报卡列表失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> getDeptTree() {
|
||||||
|
// 返回科室树数据,实际应从科室服务获取
|
||||||
|
return R.ok(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DoctorCardStatisticsDto getDoctorCardStatistics() {
|
||||||
|
Long doctorId = SecurityUtils.getUserId();
|
||||||
|
|
||||||
|
DoctorCardStatisticsDto dto = new DoctorCardStatisticsDto();
|
||||||
|
Integer totalCount = infectiousCardMapper.countByDoctorId(doctorId);
|
||||||
|
Integer pendingFailedCount = infectiousCardMapper.countPendingFailedByDoctorId(doctorId);
|
||||||
|
Integer reportedCount = infectiousCardMapper.countReportedByDoctorId(doctorId);
|
||||||
|
|
||||||
|
dto.setTotalCount(totalCount != null ? totalCount : 0);
|
||||||
|
dto.setPendingFailedCount(pendingFailedCount != null ? pendingFailedCount : 0);
|
||||||
|
dto.setReportedCount(reportedCount != null ? reportedCount : 0);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> getDoctorCardPage(DoctorCardQueryDto queryParams) {
|
||||||
|
Long doctorId = SecurityUtils.getUserId();
|
||||||
|
|
||||||
|
Page<InfectiousCard> page = new Page<>(queryParams.getPageNo(), queryParams.getPageSize());
|
||||||
|
LambdaQueryWrapper<InfectiousCard> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 只查询当前医生的报卡
|
||||||
|
wrapper.eq(InfectiousCard::getDoctorId, doctorId);
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
if (StringUtils.hasText(queryParams.getStatus())) {
|
||||||
|
wrapper.eq(InfectiousCard::getStatus, queryParams.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间范围筛选
|
||||||
|
if (StringUtils.hasText(queryParams.getStartDate())) {
|
||||||
|
LocalDateTime startDateTime = LocalDateTime.parse(queryParams.getStartDate() + "T00:00:00");
|
||||||
|
wrapper.ge(InfectiousCard::getCreateTime, startDateTime);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(queryParams.getEndDate())) {
|
||||||
|
LocalDateTime endDateTime = LocalDateTime.parse(queryParams.getEndDate() + "T23:59:59");
|
||||||
|
wrapper.le(InfectiousCard::getCreateTime, endDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键词搜索(患者姓名或报卡名称)
|
||||||
|
if (StringUtils.hasText(queryParams.getKeyword())) {
|
||||||
|
wrapper.and(w -> w
|
||||||
|
.like(InfectiousCard::getPatName, queryParams.getKeyword())
|
||||||
|
.or()
|
||||||
|
.like(InfectiousCard::getDiseaseName, queryParams.getKeyword())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间倒序
|
||||||
|
wrapper.orderByDesc(InfectiousCard::getCreateTime);
|
||||||
|
|
||||||
|
IPage<InfectiousCard> result = infectiousCardMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
// 转换为 DTO
|
||||||
|
List<DoctorCardListDto> list = result.getRecords().stream()
|
||||||
|
.map(this::convertToDoctorCardListDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("list", list);
|
||||||
|
resultMap.put("total", result.getTotal());
|
||||||
|
return R.ok(resultMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> submitCard(String cardNo) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证权限:只能提交自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
|
||||||
|
return R.fail("无权操作此报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证状态:只有暂存状态可以提交
|
||||||
|
if (!"0".equals(card.getStatus())) {
|
||||||
|
return R.fail("只能提交暂存状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态为已提交
|
||||||
|
card.setStatus("1");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
return R.ok("提交成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> withdrawCard(String cardNo) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证权限:只能撤回自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
|
||||||
|
return R.fail("无权操作此报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证状态:只有已提交状态可以撤回
|
||||||
|
if (!"1".equals(card.getStatus())) {
|
||||||
|
return R.fail("只能撤回已提交状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态为暂存
|
||||||
|
card.setStatus("0");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
return R.ok("撤回成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> deleteCard(String cardNo) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证权限:只能删除自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
|
||||||
|
return R.fail("无权操作此报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证状态:只有暂存状态可以删除
|
||||||
|
if (!"0".equals(card.getStatus())) {
|
||||||
|
return R.fail("只能删除暂存状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态为作废
|
||||||
|
card.setStatus("6");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
|
||||||
|
return R.ok("删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> batchSubmitCards(List<String> cardNos) {
|
||||||
|
if (cardNos == null || cardNos.isEmpty()) {
|
||||||
|
return R.fail("请选择要提交的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long doctorId = SecurityUtils.getUserId();
|
||||||
|
int successCount = 0;
|
||||||
|
|
||||||
|
for (String cardNo : cardNos) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) continue;
|
||||||
|
|
||||||
|
// 验证权限:只能提交自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(doctorId)) continue;
|
||||||
|
|
||||||
|
// 验证状态:只有暂存状态可以提交
|
||||||
|
if (!"0".equals(card.getStatus())) continue;
|
||||||
|
|
||||||
|
// 更新状态为已提交
|
||||||
|
card.setStatus("1");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount == 0) {
|
||||||
|
return R.fail("没有可提交的报卡,只能提交暂存状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok("批量提交成功,共提交" + successCount + "条");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> batchDeleteCards(List<String> cardNos) {
|
||||||
|
if (cardNos == null || cardNos.isEmpty()) {
|
||||||
|
return R.fail("请选择要删除的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long doctorId = SecurityUtils.getUserId();
|
||||||
|
int successCount = 0;
|
||||||
|
|
||||||
|
for (String cardNo : cardNos) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) continue;
|
||||||
|
|
||||||
|
// 验证权限:只能删除自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(doctorId)) continue;
|
||||||
|
|
||||||
|
// 验证状态:只有暂存状态可以删除
|
||||||
|
if (!"0".equals(card.getStatus())) continue;
|
||||||
|
|
||||||
|
// 更新状态为作废
|
||||||
|
card.setStatus("6");
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
infectiousCardMapper.updateById(card);
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successCount == 0) {
|
||||||
|
return R.fail("没有可删除的报卡,只能删除暂存状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok("批量删除成功,共删除" + successCount + "条");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> updateDoctorCard(DoctorCardUpdateDto updateDto) {
|
||||||
|
// 获取当前登录用户信息
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
Long currentUserId = loginUser.getUserId();
|
||||||
|
|
||||||
|
// 查询报卡
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(updateDto.getCardNo());
|
||||||
|
if (card == null) {
|
||||||
|
return R.fail("报卡不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否当前医生的报卡 - 根据 doctorId 字段验证
|
||||||
|
if (!currentUserId.equals(card.getDoctorId())) {
|
||||||
|
return R.fail("只能修改自己的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证状态是否允许修改(只能修改暂存状态的报卡)
|
||||||
|
if (!"0".equals(card.getStatus())) {
|
||||||
|
return R.fail("只能修改暂存状态的报卡");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新字段
|
||||||
|
card.setPhone(updateDto.getPhone());
|
||||||
|
card.setOnsetDate(updateDto.getOnsetDate());
|
||||||
|
card.setDiagDate(updateDto.getDiagDate());
|
||||||
|
card.setDiseaseType(updateDto.getDiseaseType()); // 使用 diseaseType 字段
|
||||||
|
card.setAddressProv(updateDto.getAddressProv());
|
||||||
|
card.setAddressCity(updateDto.getAddressCity());
|
||||||
|
card.setAddressCounty(updateDto.getAddressCounty());
|
||||||
|
card.setAddressHouse(updateDto.getAddressHouse());
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
card.setUpdateBy(loginUser.getUsername()); // 使用username作为更新者
|
||||||
|
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
|
||||||
|
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
|
||||||
|
|
||||||
|
card.setUpdateTime(new Date());
|
||||||
|
card.setUpdateBy(loginUser.getUsername()); // 使用 username 作为更新者
|
||||||
|
|
||||||
|
int rows = infectiousCardMapper.updateById(card);
|
||||||
|
if (rows > 0) {
|
||||||
|
return R.ok("更新成功");
|
||||||
|
}
|
||||||
|
return R.fail("更新失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportCardToWord(String cardNo, HttpServletResponse response) {
|
||||||
|
InfectiousCard card = infectiousCardMapper.selectByCardNo(cardNo);
|
||||||
|
if (card == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证权限:只能导出自己的报卡
|
||||||
|
if (!card.getDoctorId().equals(SecurityUtils.getUserId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证状态:只有已上报状态可以导出
|
||||||
|
if (!"3".equals(card.getStatus())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: 实现 Word 导出逻辑,使用 Apache POI 或其他库
|
||||||
|
// 这里简化为返回文本内容
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" +
|
||||||
|
URLEncoder.encode("传染病报告卡-" + cardNo + ".docx", StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// 实际应生成 Word 文档内容
|
||||||
|
response.getWriter().write("报卡编号:" + cardNo);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("导出报卡失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为医生报卡列表 DTO
|
||||||
|
*/
|
||||||
|
private DoctorCardListDto convertToDoctorCardListDto(InfectiousCard card) {
|
||||||
|
DoctorCardListDto dto = new DoctorCardListDto();
|
||||||
|
BeanUtils.copyProperties(card, dto);
|
||||||
|
dto.setCardName(getCardName(card.getCardNameCode()));
|
||||||
|
dto.setSubmitTime(card.getCreateTime() != null ?
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd HH:mm").format(card.getCreateTime()) : null);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取报卡名称
|
||||||
|
*/
|
||||||
|
private String getCardName(Integer cardNameCode) {
|
||||||
|
if (cardNameCode == null) return "中华人民共和国传染病报告卡";
|
||||||
|
switch (cardNameCode) {
|
||||||
|
case 1: return "中华人民共和国传染病报告卡";
|
||||||
|
case 2: return "甲类传染病报告卡";
|
||||||
|
case 3: return "乙类传染病报告卡";
|
||||||
|
case 4: return "丙类传染病报告卡";
|
||||||
|
default: return "中华人民共和国传染病报告卡";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换审核记录为 DTO
|
||||||
|
*/
|
||||||
|
private AuditRecordDto convertAuditToDto(InfectiousAudit audit) {
|
||||||
|
AuditRecordDto dto = new AuditRecordDto();
|
||||||
|
BeanUtils.copyProperties(audit, dto);
|
||||||
|
dto.setCardId(audit.getCardId() != null ? audit.getCardId().toString() : null);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为报卡 DTO
|
||||||
|
*/
|
||||||
|
private InfectiousCardDto convertToDto(InfectiousCard card) {
|
||||||
|
InfectiousCardDto dto = new InfectiousCardDto();
|
||||||
|
BeanUtils.copyProperties(card, dto);
|
||||||
|
dto.setStatusText(getStatusText(card.getStatus()));
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建审核记录
|
||||||
|
*/
|
||||||
|
private void createAuditRecord(Long cardId, String statusFrom, String statusTo, String auditType,
|
||||||
|
String auditOpinion, String returnReason, String auditorId, String auditorName,
|
||||||
|
Boolean isBatch, Integer batchSize) {
|
||||||
|
InfectiousAudit audit = new InfectiousAudit();
|
||||||
|
audit.setCardId(cardId);
|
||||||
|
audit.setAuditSeq(infectiousAuditMapper.getNextAuditSeq(cardId));
|
||||||
|
audit.setAuditType(auditType);
|
||||||
|
audit.setAuditStatusFrom(statusFrom);
|
||||||
|
audit.setAuditStatusTo(statusTo);
|
||||||
|
audit.setAuditTime(LocalDateTime.now());
|
||||||
|
audit.setAuditorId(auditorId);
|
||||||
|
audit.setAuditorName(auditorName);
|
||||||
|
audit.setAuditOpinion(auditOpinion);
|
||||||
|
audit.setReasonForReturn(returnReason);
|
||||||
|
audit.setIsBatch(isBatch);
|
||||||
|
audit.setBatchSize(batchSize);
|
||||||
|
infectiousAuditMapper.insert(audit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取状态文本
|
||||||
|
*/
|
||||||
|
private String getStatusText(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case "0": return "暂存";
|
||||||
|
case "1": return "已提交";
|
||||||
|
case "2": return "审核通过";
|
||||||
|
case "3": return "已上报";
|
||||||
|
case "4": return "失败";
|
||||||
|
case "5": return "审核失败";
|
||||||
|
case "6": return "作废";
|
||||||
|
default: return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.controller;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.web.cardmanagement.appservice.ICardManageAppService;
|
||||||
|
import com.openhis.web.cardmanagement.dto.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报卡管理 Controller
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/card-management")
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CardManageController {
|
||||||
|
|
||||||
|
private final ICardManageAppService cardManageAppService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取统计数据
|
||||||
|
*
|
||||||
|
* @return 统计数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public R<CardStatisticsDto> getStatistics() {
|
||||||
|
return R.ok(cardManageAppService.getStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return 分页数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/page")
|
||||||
|
public R<?> getCardPage(CardQueryDto queryParams) {
|
||||||
|
return cardManageAppService.getCardPage(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取报卡详情
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 报卡详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail/{cardNo}")
|
||||||
|
public R<InfectiousCardDto> getCardDetail(@PathVariable String cardNo) {
|
||||||
|
return R.ok(cardManageAppService.getCardDetail(cardNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审核记录
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 审核记录列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/audit-records/{cardNo}")
|
||||||
|
public R<List<AuditRecordDto>> getAuditRecords(@PathVariable String cardNo) {
|
||||||
|
return R.ok(cardManageAppService.getAuditRecords(cardNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量审核
|
||||||
|
*
|
||||||
|
* @param batchAuditDto 批量审核参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/batch-audit")
|
||||||
|
public R<?> batchAudit(@RequestBody BatchAuditDto batchAuditDto) {
|
||||||
|
return cardManageAppService.batchAudit(batchAuditDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量退回
|
||||||
|
*
|
||||||
|
* @param batchReturnDto 批量退回参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/batch-return")
|
||||||
|
public R<?> batchReturn(@RequestBody BatchReturnDto batchReturnDto) {
|
||||||
|
return cardManageAppService.batchReturn(batchReturnDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条审核通过
|
||||||
|
*
|
||||||
|
* @param auditDto 审核参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/audit-pass")
|
||||||
|
public R<?> auditPass(@RequestBody SingleAuditDto auditDto) {
|
||||||
|
return cardManageAppService.auditPass(auditDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条退回
|
||||||
|
*
|
||||||
|
* @param returnDto 退回参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/audit-return")
|
||||||
|
public R<?> auditReturn(@RequestBody SingleReturnDto returnDto) {
|
||||||
|
return cardManageAppService.auditReturn(returnDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
@GetMapping("/export")
|
||||||
|
public void exportCards(CardQueryDto queryParams, HttpServletResponse response) {
|
||||||
|
cardManageAppService.exportCards(queryParams, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取科室树
|
||||||
|
*
|
||||||
|
* @return 科室树数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/dept-tree")
|
||||||
|
public R<?> getDeptTree() {
|
||||||
|
return cardManageAppService.getDeptTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 医生个人报卡管理 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取医生个人报卡统计数据
|
||||||
|
*
|
||||||
|
* @return 统计数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/doctor/statistics")
|
||||||
|
public R<DoctorCardStatisticsDto> getDoctorCardStatistics() {
|
||||||
|
return R.ok(cardManageAppService.getDoctorCardStatistics());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询医生个人报卡列表
|
||||||
|
*
|
||||||
|
* @param queryParams 查询参数
|
||||||
|
* @return 分页数据
|
||||||
|
*/
|
||||||
|
@GetMapping("/doctor/page")
|
||||||
|
public R<?> getDoctorCardPage(DoctorCardQueryDto queryParams) {
|
||||||
|
return cardManageAppService.getDoctorCardPage(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交报卡
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/doctor/submit/{cardNo}")
|
||||||
|
public R<?> submitCard(@PathVariable String cardNo) {
|
||||||
|
return cardManageAppService.submitCard(cardNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤回报卡
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/doctor/withdraw/{cardNo}")
|
||||||
|
public R<?> withdrawCard(@PathVariable String cardNo) {
|
||||||
|
return cardManageAppService.withdrawCard(cardNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除报卡(状态变为作废)
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/doctor/{cardNo}")
|
||||||
|
public R<?> deleteCard(@PathVariable String cardNo) {
|
||||||
|
return cardManageAppService.deleteCard(cardNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量提交报卡
|
||||||
|
*
|
||||||
|
* @param cardNos 卡片编号列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/doctor/batch-submit")
|
||||||
|
public R<?> batchSubmitCards(@RequestBody List<String> cardNos) {
|
||||||
|
return cardManageAppService.batchSubmitCards(cardNos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除报卡
|
||||||
|
*
|
||||||
|
* @param cardNos 卡片编号列表
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/doctor/batch-delete")
|
||||||
|
public R<?> batchDeleteCards(@RequestBody List<String> cardNos) {
|
||||||
|
return cardManageAppService.batchDeleteCards(cardNos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新医生报卡
|
||||||
|
*
|
||||||
|
* @param updateDto 更新参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/doctor/update")
|
||||||
|
public R<?> updateDoctorCard(@RequestBody DoctorCardUpdateDto updateDto) {
|
||||||
|
return cardManageAppService.updateDoctorCard(updateDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出报卡为Word文档
|
||||||
|
*
|
||||||
|
* @param cardNo 卡片编号
|
||||||
|
* @param response 响应
|
||||||
|
*/
|
||||||
|
@GetMapping("/doctor/export-word/{cardNo}")
|
||||||
|
public void exportCardToWord(@PathVariable String cardNo, HttpServletResponse response) {
|
||||||
|
cardManageAppService.exportCardToWord(cardNo, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核记录DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AuditRecordDto {
|
||||||
|
|
||||||
|
/** 审核记录ID */
|
||||||
|
private Long auditId;
|
||||||
|
|
||||||
|
/** 报卡ID */
|
||||||
|
private String cardId;
|
||||||
|
|
||||||
|
/** 审核序号 */
|
||||||
|
private Integer auditSeq;
|
||||||
|
|
||||||
|
/** 审核类型 */
|
||||||
|
private String auditType;
|
||||||
|
|
||||||
|
/** 审核前状态 */
|
||||||
|
private String auditStatusFrom;
|
||||||
|
|
||||||
|
/** 审核后状态 */
|
||||||
|
private String auditStatusTo;
|
||||||
|
|
||||||
|
/** 审核时间 */
|
||||||
|
private LocalDateTime auditTime;
|
||||||
|
|
||||||
|
/** 审核人账号 */
|
||||||
|
private String auditorId;
|
||||||
|
|
||||||
|
/** 审核人姓名 */
|
||||||
|
private String auditorName;
|
||||||
|
|
||||||
|
/** 审核意见 */
|
||||||
|
private String auditOpinion;
|
||||||
|
|
||||||
|
/** 退回原因 */
|
||||||
|
private String reasonForReturn;
|
||||||
|
|
||||||
|
/** 是否批量 */
|
||||||
|
private Boolean isBatch;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量审核参数DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BatchAuditDto {
|
||||||
|
|
||||||
|
/** 卡片编号列表 */
|
||||||
|
private List<String> cardNos;
|
||||||
|
|
||||||
|
/** 审核意见 */
|
||||||
|
private String auditOpinion;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量退回参数DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BatchReturnDto {
|
||||||
|
|
||||||
|
/** 卡片编号列表 */
|
||||||
|
private List<String> cardNos;
|
||||||
|
|
||||||
|
/** 退回原因 */
|
||||||
|
private String returnReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报卡查询参数DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CardQueryDto {
|
||||||
|
|
||||||
|
/** 当前页 */
|
||||||
|
private Integer pageNo = 1;
|
||||||
|
|
||||||
|
/** 每页条数 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
/** 登记来源 */
|
||||||
|
private Integer registrationSource;
|
||||||
|
|
||||||
|
/** 开始日期 */
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
/** 结束日期 */
|
||||||
|
private String endDate;
|
||||||
|
|
||||||
|
/** 患者姓名 */
|
||||||
|
private String patientName;
|
||||||
|
|
||||||
|
/** 审核状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 科室ID */
|
||||||
|
private Long deptId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报卡统计数据DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CardStatisticsDto {
|
||||||
|
|
||||||
|
/** 今日待审核 */
|
||||||
|
private Integer todayPending;
|
||||||
|
|
||||||
|
/** 本月审核失败 */
|
||||||
|
private Integer monthFailed;
|
||||||
|
|
||||||
|
/** 本月审核成功 */
|
||||||
|
private Integer monthSuccess;
|
||||||
|
|
||||||
|
/** 本月已上报 */
|
||||||
|
private Integer monthReported;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生个人报卡列表DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-08
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DoctorCardListDto {
|
||||||
|
|
||||||
|
/** 卡片ID */
|
||||||
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 卡片编号 */
|
||||||
|
private String cardNo;
|
||||||
|
|
||||||
|
/** 患者姓名 */
|
||||||
|
private String patName;
|
||||||
|
|
||||||
|
/** 身份证号 */
|
||||||
|
private String idNo;
|
||||||
|
|
||||||
|
/** 联系电话 */
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/** 就诊卡号(暂时不展示,字段保留) */
|
||||||
|
private String visitCardNo;
|
||||||
|
|
||||||
|
/** 报卡名称 */
|
||||||
|
private String cardName;
|
||||||
|
|
||||||
|
/** 提交时间 */
|
||||||
|
private String submitTime;
|
||||||
|
|
||||||
|
/** 状态 */
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生个人报卡查询参数
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-08
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DoctorCardQueryDto {
|
||||||
|
|
||||||
|
/** 页码 */
|
||||||
|
private Integer pageNo = 1;
|
||||||
|
|
||||||
|
/** 每页数量 */
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
/** 开始日期 */
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
/** 结束日期 */
|
||||||
|
private String endDate;
|
||||||
|
|
||||||
|
/** 状态(0暂存/1已提交/2已审核/3已上报/4失败/5退回) */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 患者姓名或报卡名称 */
|
||||||
|
private String keyword;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright ©2026 CJB-CNIT Team. All rights reserved
|
||||||
|
*/
|
||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生个人报卡统计数据
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-08
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DoctorCardStatisticsDto {
|
||||||
|
|
||||||
|
/** 总报卡数 */
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/** 待处理失败数 */
|
||||||
|
private Integer pendingFailedCount;
|
||||||
|
|
||||||
|
/** 已成功上报数 */
|
||||||
|
private Integer reportedCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DoctorCardUpdateDto {
|
||||||
|
private String cardNo;
|
||||||
|
private String phone;
|
||||||
|
private LocalDate onsetDate;
|
||||||
|
private LocalDateTime diagDate;
|
||||||
|
private String diseaseType; // 修改为diseaseType,对应InfectiousCard中的diseaseType字段
|
||||||
|
private String addressProv;
|
||||||
|
private String addressCity;
|
||||||
|
private String addressCounty;
|
||||||
|
private String addressHouse;
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传染病报卡详情DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class InfectiousCardDto {
|
||||||
|
|
||||||
|
/** 卡片编号 */
|
||||||
|
private String cardNo;
|
||||||
|
|
||||||
|
/** 患者姓名 */
|
||||||
|
private String patName;
|
||||||
|
|
||||||
|
/** 家长姓名 */
|
||||||
|
private String parentName;
|
||||||
|
|
||||||
|
/** 证件号码 */
|
||||||
|
private String idNo;
|
||||||
|
|
||||||
|
/** 性别(1男/2女/0未知) */
|
||||||
|
private String sex;
|
||||||
|
|
||||||
|
/** 出生日期 */
|
||||||
|
private LocalDate birthday;
|
||||||
|
|
||||||
|
/** 实足年龄 */
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
|
/** 年龄单位(1岁/2月/3天) */
|
||||||
|
private String ageUnit;
|
||||||
|
|
||||||
|
/** 工作单位 */
|
||||||
|
private String workplace;
|
||||||
|
|
||||||
|
/** 联系电话 */
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
/** 紧急联系人电话 */
|
||||||
|
private String contactPhone;
|
||||||
|
|
||||||
|
/** 现住址省 */
|
||||||
|
private String addressProv;
|
||||||
|
|
||||||
|
/** 现住址市 */
|
||||||
|
private String addressCity;
|
||||||
|
|
||||||
|
/** 现住址县 */
|
||||||
|
private String addressCounty;
|
||||||
|
|
||||||
|
/** 现住址街道 */
|
||||||
|
private String addressTown;
|
||||||
|
|
||||||
|
/** 现住址村/居委 */
|
||||||
|
private String addressVillage;
|
||||||
|
|
||||||
|
/** 现住址门牌号 */
|
||||||
|
private String addressHouse;
|
||||||
|
|
||||||
|
/** 病人属于 */
|
||||||
|
private String patientbelong;
|
||||||
|
|
||||||
|
/** 职业 */
|
||||||
|
private String occupation;
|
||||||
|
|
||||||
|
/** 疾病编码 */
|
||||||
|
private String diseaseCode;
|
||||||
|
|
||||||
|
/** 疾病名称 */
|
||||||
|
private String diseaseName;
|
||||||
|
|
||||||
|
/** 分型 */
|
||||||
|
private String diseaseSubtype;
|
||||||
|
|
||||||
|
/** 病例分类 */
|
||||||
|
private String diseaseType;
|
||||||
|
|
||||||
|
/** 发病日期 */
|
||||||
|
private LocalDate onsetDate;
|
||||||
|
|
||||||
|
/** 诊断日期 */
|
||||||
|
private LocalDateTime diagDate;
|
||||||
|
|
||||||
|
/** 死亡日期 */
|
||||||
|
private LocalDate deathDate;
|
||||||
|
|
||||||
|
/** 订正病名 */
|
||||||
|
private String revisedDiseaseName;
|
||||||
|
|
||||||
|
/** 退卡原因 */
|
||||||
|
private String returnReason;
|
||||||
|
|
||||||
|
/** 报告单位 */
|
||||||
|
private String reportOrg;
|
||||||
|
|
||||||
|
/** 联系电话 */
|
||||||
|
private String reportOrgPhone;
|
||||||
|
|
||||||
|
/** 报告医生 */
|
||||||
|
private String reportDoc;
|
||||||
|
|
||||||
|
/** 填卡日期 */
|
||||||
|
private LocalDate reportDate;
|
||||||
|
|
||||||
|
/** 状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 状态文本 */
|
||||||
|
private String statusText;
|
||||||
|
|
||||||
|
/** 报卡名称代码 */
|
||||||
|
private Integer cardNameCode;
|
||||||
|
|
||||||
|
/** 登记来源 */
|
||||||
|
private Integer registrationSource;
|
||||||
|
|
||||||
|
/** 科室名称 */
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
/** 创建时间 */
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条审核参数DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SingleAuditDto {
|
||||||
|
|
||||||
|
/** 卡片编号 */
|
||||||
|
private String cardNo;
|
||||||
|
|
||||||
|
/** 审核意见 */
|
||||||
|
private String auditOpinion;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.openhis.web.cardmanagement.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单条退回参数DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SingleReturnDto {
|
||||||
|
|
||||||
|
/** 卡片编号 */
|
||||||
|
private String cardNo;
|
||||||
|
|
||||||
|
/** 退回原因 */
|
||||||
|
private String returnReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.openhis.web.cardmanagement.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.openhis.infectious.domain.InfectiousAudit;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核记录Mapper
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface InfectiousAuditMapper extends BaseMapper<InfectiousAudit> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据报卡ID查询审核记录
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM infectious_audit WHERE card_id = #{cardId} ORDER BY audit_time DESC")
|
||||||
|
List<InfectiousAudit> selectByCardId(@Param("cardId") Long cardId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下一个审核序号
|
||||||
|
*/
|
||||||
|
@Select("SELECT COALESCE(MAX(audit_seq), 0) + 1 FROM infectious_audit WHERE card_id = #{cardId}")
|
||||||
|
Integer getNextAuditSeq(@Param("cardId") Long cardId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.openhis.web.cardmanagement.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.openhis.infectious.domain.InfectiousCard;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 传染病报卡Mapper
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-03-05
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计今日待审核数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE(create_time) = CURRENT_DATE AND status = '1'")
|
||||||
|
Integer countTodayPending();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计本月审核失败数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '5'")
|
||||||
|
Integer countMonthFailed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计本月审核成功数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '2'")
|
||||||
|
Integer countMonthSuccess();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计本月已上报数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE DATE_TRUNC('month', create_time) = DATE_TRUNC('month', CURRENT_DATE) AND status = '3'")
|
||||||
|
Integer countMonthReported();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据卡片编号查询
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM infectious_card WHERE card_no = #{cardNo}")
|
||||||
|
InfectiousCard selectByCardNo(@Param("cardNo") String cardNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计医生个人总报卡数
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId}")
|
||||||
|
Integer countByDoctorId(@Param("doctorId") Long doctorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计医生待处理失败数(状态为0暂存或4失败)
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status IN ('0', '4')")
|
||||||
|
Integer countPendingFailedByDoctorId(@Param("doctorId") Long doctorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计医生已成功上报数(状态为3已上报)
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM infectious_card WHERE doctor_id = #{doctorId} AND status = '3'")
|
||||||
|
Integer countReportedByDoctorId(@Param("doctorId") Long doctorId);
|
||||||
|
}
|
||||||
@@ -247,6 +247,10 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
if (EncounterStatus.CANCELLED.getValue().equals(byId.getStatusEnum())) {
|
if (EncounterStatus.CANCELLED.getValue().equals(byId.getStatusEnum())) {
|
||||||
return R.fail(null, "该患者已经退号,请勿重复退号");
|
return R.fail(null, "该患者已经退号,请勿重复退号");
|
||||||
}
|
}
|
||||||
|
// 只有待诊状态才能退号
|
||||||
|
if (!EncounterStatus.PLANNED.getValue().equals(byId.getStatusEnum())) {
|
||||||
|
return R.fail(null, "该患者医生已接诊,不能退号!");
|
||||||
|
}
|
||||||
iEncounterService.returnRegister(cancelRegPaymentDto.getEncounterId());
|
iEncounterService.returnRegister(cancelRegPaymentDto.getEncounterId());
|
||||||
// 查询账户信息
|
// 查询账户信息
|
||||||
Account account = iAccountService
|
Account account = iAccountService
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ public class OutpatientRefundController {
|
|||||||
* @return 患者账单列表
|
* @return 患者账单列表
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/patient-payment")
|
@GetMapping(value = "/patient-payment")
|
||||||
public R<?> getEncounterPatientPayment(@RequestParam Long encounterId) {
|
public R<?> getEncounterPatientPayment(@RequestParam(required = false) Long encounterId) {
|
||||||
|
if (encounterId == null) {
|
||||||
|
return R.fail(null, "请先选择患者后再进行退费操作");
|
||||||
|
}
|
||||||
return outpatientRefundAppService.getEncounterPatientPayment(encounterId);
|
return outpatientRefundAppService.getEncounterPatientPayment(encounterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public class CheckMethodAppServiceImpl implements ICheckMethodAppService {
|
|||||||
// 导出到Excel
|
// 导出到Excel
|
||||||
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
|
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
|
||||||
} catch (IOException | IllegalAccessException e) {
|
} catch (IOException | IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
log.error("导出Excel失败", e);
|
||||||
return R.fail("导出Excel失败:" + e.getMessage());
|
return R.fail("导出Excel失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class CheckPartAppServiceImpl implements ICheckPartAppService {
|
|||||||
// 导出到Excel
|
// 导出到Excel
|
||||||
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
|
ExcelFillerUtil.makeExcelFile(response, list, headers, excelName, null);
|
||||||
} catch (IOException | IllegalAccessException e) {
|
} catch (IOException | IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
log.error("导出Excel失败", e);
|
||||||
return R.fail("导出Excel失败:" + e.getMessage());
|
return R.fail("导出Excel失败:" + e.getMessage());
|
||||||
}
|
}
|
||||||
return R.ok(null, "导出Excel成功");
|
return R.ok(null, "导出Excel成功");
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.core.common.core.controller.BaseController;
|
import com.core.common.core.controller.BaseController;
|
||||||
import com.core.common.core.domain.AjaxResult;
|
import com.core.common.core.domain.AjaxResult;
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.check.domain.CheckMethod;
|
import com.openhis.check.domain.CheckMethod;
|
||||||
import com.openhis.check.domain.CheckPackage;
|
import com.openhis.check.domain.CheckPackage;
|
||||||
|
import com.openhis.check.domain.CheckPackageDetail;
|
||||||
import com.openhis.check.domain.CheckPart;
|
import com.openhis.check.domain.CheckPart;
|
||||||
import com.openhis.check.domain.CheckType;
|
import com.openhis.check.domain.CheckType;
|
||||||
import com.openhis.check.service.ICheckMethodService;
|
import com.openhis.check.service.ICheckMethodService;
|
||||||
|
import com.openhis.check.service.ICheckPackageDetailService;
|
||||||
import com.openhis.check.service.ICheckPackageService;
|
import com.openhis.check.service.ICheckPackageService;
|
||||||
import com.openhis.check.service.ICheckPartService;
|
import com.openhis.check.service.ICheckPartService;
|
||||||
import com.openhis.check.service.ICheckTypeService;
|
import com.openhis.check.service.ICheckTypeService;
|
||||||
@@ -33,7 +36,7 @@ import java.util.stream.Collectors;
|
|||||||
* @updated 2025-11-26 - 增加套餐设置相关接口
|
* @updated 2025-11-26 - 增加套餐设置相关接口
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping({"/system/check-type", "/system"})
|
@RequestMapping({ "/system/check-type", "/system" })
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CheckTypeController extends BaseController {
|
public class CheckTypeController extends BaseController {
|
||||||
@@ -42,8 +45,19 @@ public class CheckTypeController extends BaseController {
|
|||||||
private final ICheckMethodService checkMethodService;
|
private final ICheckMethodService checkMethodService;
|
||||||
private final ICheckPartService checkPartService;
|
private final ICheckPartService checkPartService;
|
||||||
private final ICheckPackageService checkPackageService;
|
private final ICheckPackageService checkPackageService;
|
||||||
|
private final ICheckPackageDetailService checkPackageDetailService;
|
||||||
private final ICheckPackageAppService checkPackageAppService;
|
private final ICheckPackageAppService checkPackageAppService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有检查类型列表(不分页,用于下拉选项)
|
||||||
|
*/
|
||||||
|
@GetMapping("/all")
|
||||||
|
public AjaxResult getAllCheckTypes() {
|
||||||
|
List<CheckType> list = checkTypeService.list(
|
||||||
|
new QueryWrapper<CheckType>().orderByAsc("id"));
|
||||||
|
return AjaxResult.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取检查类型列表(支持分页)
|
* 获取检查类型列表(支持分页)
|
||||||
*/
|
*/
|
||||||
@@ -52,15 +66,15 @@ public class CheckTypeController extends BaseController {
|
|||||||
@RequestParam(defaultValue = "1") Integer pageNo,
|
@RequestParam(defaultValue = "1") Integer pageNo,
|
||||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||||
|
|
||||||
if (pageSize > 10) pageSize = 10;
|
if (pageSize > 10)
|
||||||
|
pageSize = 10;
|
||||||
|
|
||||||
// 1. 分页查询父节点(NULL + 0 都算父)
|
// 1. 分页查询父节点(NULL + 0 都算父)
|
||||||
Page<CheckType> parentPage = checkTypeService.page(
|
Page<CheckType> parentPage = checkTypeService.page(
|
||||||
new Page<>(pageNo, pageSize),
|
new Page<>(pageNo, pageSize),
|
||||||
new QueryWrapper<CheckType>()
|
new QueryWrapper<CheckType>()
|
||||||
.and(w -> w.isNull("parent_id").or().eq("parent_id", 0))
|
.and(w -> w.isNull("parent_id").or().eq("parent_id", 0))
|
||||||
.orderByAsc("id")
|
.orderByAsc("id"));
|
||||||
);
|
|
||||||
|
|
||||||
if (parentPage.getRecords().isEmpty()) {
|
if (parentPage.getRecords().isEmpty()) {
|
||||||
return AjaxResult.success(parentPage);
|
return AjaxResult.success(parentPage);
|
||||||
@@ -74,12 +88,10 @@ public class CheckTypeController extends BaseController {
|
|||||||
|
|
||||||
// 3. 查询子节点
|
// 3. 查询子节点
|
||||||
List<CheckType> children = checkTypeService.list(
|
List<CheckType> children = checkTypeService.list(
|
||||||
new QueryWrapper<CheckType>().in("parent_id", parentIds)
|
new QueryWrapper<CheckType>().in("parent_id", parentIds));
|
||||||
);
|
|
||||||
|
|
||||||
// 4. 分组
|
// 4. 分组
|
||||||
Map<Long, List<CheckType>> childMap =
|
Map<Long, List<CheckType>> childMap = children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
|
||||||
children.stream().collect(Collectors.groupingBy(CheckType::getParentId));
|
|
||||||
|
|
||||||
// 5. 拼接父 + 子
|
// 5. 拼接父 + 子
|
||||||
List<CheckType> result = new ArrayList<>();
|
List<CheckType> result = new ArrayList<>();
|
||||||
@@ -92,16 +104,16 @@ public class CheckTypeController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. 返回(total 是父节点总数)
|
// 6. 返回(total 是父节点总数)
|
||||||
Page<CheckType> page =
|
Page<CheckType> page = new Page<>(pageNo, pageSize, parentPage.getTotal());
|
||||||
new Page<>(pageNo, pageSize, parentPage.getTotal());
|
|
||||||
page.setRecords(result);
|
page.setRecords(result);
|
||||||
|
|
||||||
return AjaxResult.success(page);
|
return AjaxResult.success(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取检查方法列表
|
* 获取检查方法列表
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/method/list", "/check-method/list"})
|
@GetMapping({ "/method/list", "/check-method/list" })
|
||||||
public AjaxResult methodList() {
|
public AjaxResult methodList() {
|
||||||
List<CheckMethod> list = checkMethodService.list();
|
List<CheckMethod> list = checkMethodService.list();
|
||||||
return AjaxResult.success(list);
|
return AjaxResult.success(list);
|
||||||
@@ -110,16 +122,119 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 获取检查部位列表
|
* 获取检查部位列表
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/part/list", "/check-part/list"})
|
@GetMapping({ "/part/list", "/check-part/list" })
|
||||||
public AjaxResult partList() {
|
public AjaxResult partList() {
|
||||||
List<CheckPart> list = checkPartService.list();
|
List<CheckPart> list = checkPartService.list();
|
||||||
return AjaxResult.success(list);
|
return AjaxResult.success(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前登录用户过滤可见的检查套餐项目(差异化显示)
|
||||||
|
* <p>
|
||||||
|
* 过滤规则:
|
||||||
|
* 1. 全院套餐(packageLevel="1"):任何医生可见
|
||||||
|
* 2. 科室套餐(packageLevel="2"):仅当前科室医生可见(需传 deptName)
|
||||||
|
* 3. 个人套餐(packageLevel="3"):仅当前登录用户可见
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param deptName 当前登录用户所在科室名称(由前端传入)
|
||||||
|
* @return 可用套餐列表(含明细检查项目)
|
||||||
|
*/
|
||||||
|
@GetMapping({ "/package/filtered", "/check-package/filtered" })
|
||||||
|
public AjaxResult getFilteredPackages(@RequestParam(required = false) String deptName) {
|
||||||
|
// 获取当前登录用户名,用于匹配个人套餐
|
||||||
|
String currentUsername;
|
||||||
|
try {
|
||||||
|
currentUsername = SecurityUtils.getUsername();
|
||||||
|
} catch (Exception e) {
|
||||||
|
currentUsername = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询未停用(isDisabled=0)的全部套餐
|
||||||
|
LambdaQueryWrapper<CheckPackage> wrapper = new LambdaQueryWrapper<CheckPackage>()
|
||||||
|
.eq(CheckPackage::getIsDisabled, 0)
|
||||||
|
.orderByAsc(CheckPackage::getNumber);
|
||||||
|
List<CheckPackage> allPackages = checkPackageService.list(wrapper);
|
||||||
|
|
||||||
|
// 按照三级规则筛选出本次登录用户可见的套餐
|
||||||
|
final String finalUsername = currentUsername;
|
||||||
|
final String finalDept = (deptName != null) ? deptName.trim() : "";
|
||||||
|
List<CheckPackage> visiblePackages = allPackages.stream().filter(pkg -> {
|
||||||
|
String level = pkg.getPackageLevel(); // 1:全院 2:科室 3:个人
|
||||||
|
if ("1".equals(level)) {
|
||||||
|
// 全院套餐 - 所有人可见
|
||||||
|
return true;
|
||||||
|
} else if ("2".equals(level)) {
|
||||||
|
// 科室套餐 - department 字段包含当前科室
|
||||||
|
String dept = pkg.getDepartment();
|
||||||
|
return dept != null && !finalDept.isEmpty() && dept.contains(finalDept);
|
||||||
|
} else if ("3".equals(level)) {
|
||||||
|
// 个人套餐 - user 字段包含当前账号
|
||||||
|
String user = pkg.getUser();
|
||||||
|
return user != null && finalUsername != null && user.contains(finalUsername);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (visiblePackages.isEmpty()) {
|
||||||
|
return AjaxResult.success(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查明细,拼装返回结构
|
||||||
|
List<Long> packageIds = visiblePackages.stream().map(CheckPackage::getId).collect(Collectors.toList());
|
||||||
|
List<CheckPackageDetail> details = checkPackageDetailService.list(
|
||||||
|
new LambdaQueryWrapper<CheckPackageDetail>().in(CheckPackageDetail::getPackageId, packageIds));
|
||||||
|
// 按套餐ID分组明细
|
||||||
|
Map<Long, List<CheckPackageDetail>> detailMap = details.stream()
|
||||||
|
.collect(Collectors.groupingBy(CheckPackageDetail::getPackageId));
|
||||||
|
|
||||||
|
// 构造返回视图:每个套餐是一个分组,下挂若干检查项目
|
||||||
|
List<Map<String, Object>> result = visiblePackages.stream().map(pkg -> {
|
||||||
|
Map<String, Object> group = new LinkedHashMap<>();
|
||||||
|
group.put("packageId", pkg.getId());
|
||||||
|
group.put("packageName", pkg.getPackageName());
|
||||||
|
group.put("packageLevel", pkg.getPackageLevel()); // 1/2/3
|
||||||
|
group.put("packageLevelText", parseLevelText(pkg.getPackageLevel()));
|
||||||
|
group.put("packagePrice", pkg.getPackagePrice());
|
||||||
|
group.put("serviceFee", pkg.getServiceFee());
|
||||||
|
group.put("packagePriceEnabled", pkg.getPackagePriceEnabled());
|
||||||
|
// 明细列表
|
||||||
|
List<Map<String, Object>> items = (detailMap.getOrDefault(pkg.getId(), Collections.emptyList()))
|
||||||
|
.stream().map(d -> {
|
||||||
|
Map<String, Object> item = new LinkedHashMap<>();
|
||||||
|
item.put("id", d.getId());
|
||||||
|
item.put("packageId", d.getPackageId());
|
||||||
|
item.put("itemCode", d.getItemCode());
|
||||||
|
item.put("itemName", d.getItemName());
|
||||||
|
item.put("unitPrice", d.getUnitPrice()); // 单价
|
||||||
|
item.put("amount", d.getAmount()); // 金额
|
||||||
|
item.put("serviceCharge", d.getServiceCharge()); // 服务费
|
||||||
|
item.put("quantity", d.getQuantity());
|
||||||
|
item.put("orderNum", d.getOrderNum());
|
||||||
|
return item;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
group.put("items", items);
|
||||||
|
return group;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 套餐级别文字映射 */
|
||||||
|
private String parseLevelText(String level) {
|
||||||
|
if ("1".equals(level))
|
||||||
|
return "全院";
|
||||||
|
if ("2".equals(level))
|
||||||
|
return "科室";
|
||||||
|
if ("3".equals(level))
|
||||||
|
return "个人";
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取检查套餐列表(支持分页和筛选)
|
* 获取检查套餐列表(支持分页和筛选)
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/package/list", "/check-package/list"})
|
@GetMapping({ "/package/list", "/check-package/list" })
|
||||||
public AjaxResult packageList(
|
public AjaxResult packageList(
|
||||||
@RequestParam(required = false) Integer pageNo,
|
@RequestParam(required = false) Integer pageNo,
|
||||||
@RequestParam(required = false) Integer pageSize,
|
@RequestParam(required = false) Integer pageSize,
|
||||||
@@ -165,14 +280,14 @@ public class CheckTypeController extends BaseController {
|
|||||||
|
|
||||||
// 如果需要分页
|
// 如果需要分页
|
||||||
if (pageNo != null && pageSize != null) {
|
if (pageNo != null && pageSize != null) {
|
||||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> page =
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(
|
||||||
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNo, pageSize);
|
pageNo, pageSize);
|
||||||
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result =
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CheckPackage> result = checkPackageService
|
||||||
checkPackageService.page(page, wrapper);
|
.page(page, wrapper);
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
} else {
|
} else {
|
||||||
List<CheckPackage> list = checkPackageService.list(wrapper);
|
List<CheckPackage> list = checkPackageService.list(wrapper);
|
||||||
return AjaxResult.success(list);
|
return AjaxResult.success(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +318,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 根据ID获取检查套餐详情
|
* 根据ID获取检查套餐详情
|
||||||
*/
|
*/
|
||||||
@GetMapping({"/package/{id}", "/check-package/{id}"})
|
@GetMapping({ "/package/{id}", "/check-package/{id}" })
|
||||||
public R<?> getCheckPackageById(@PathVariable Long id) {
|
public R<?> getCheckPackageById(@PathVariable Long id) {
|
||||||
return checkPackageAppService.getCheckPackageById(id);
|
return checkPackageAppService.getCheckPackageById(id);
|
||||||
}
|
}
|
||||||
@@ -211,7 +326,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 新增检查套餐
|
* 新增检查套餐
|
||||||
*/
|
*/
|
||||||
@PostMapping({"/package", "/check-package"})
|
@PostMapping({ "/package", "/check-package" })
|
||||||
public R<?> addCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
public R<?> addCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
||||||
return checkPackageAppService.addCheckPackage(checkPackageDto);
|
return checkPackageAppService.addCheckPackage(checkPackageDto);
|
||||||
}
|
}
|
||||||
@@ -219,7 +334,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 更新检查套餐
|
* 更新检查套餐
|
||||||
*/
|
*/
|
||||||
@PutMapping({"/package", "/check-package"})
|
@PutMapping({ "/package", "/check-package" })
|
||||||
public R<?> updateCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
public R<?> updateCheckPackage(@Valid @RequestBody CheckPackageDto checkPackageDto) {
|
||||||
return checkPackageAppService.updateCheckPackage(checkPackageDto);
|
return checkPackageAppService.updateCheckPackage(checkPackageDto);
|
||||||
}
|
}
|
||||||
@@ -227,7 +342,7 @@ public class CheckTypeController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 删除检查套餐
|
* 删除检查套餐
|
||||||
*/
|
*/
|
||||||
@DeleteMapping({"/package/{id}", "/check-package/{id}"})
|
@DeleteMapping({ "/package/{id}", "/check-package/{id}" })
|
||||||
public R<?> deleteCheckPackage(@PathVariable Long id) {
|
public R<?> deleteCheckPackage(@PathVariable Long id) {
|
||||||
return checkPackageAppService.deleteCheckPackage(id);
|
return checkPackageAppService.deleteCheckPackage(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,478 @@
|
|||||||
|
package com.openhis.web.check.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.core.common.core.controller.BaseController;
|
||||||
|
import com.core.common.core.domain.AjaxResult;
|
||||||
|
import com.core.common.core.page.TableDataInfo;
|
||||||
|
import com.core.common.utils.AssignSeqUtil;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.openhis.administration.domain.ChargeItem;
|
||||||
|
import com.openhis.administration.service.IChargeItemService;
|
||||||
|
import com.openhis.check.domain.ExamApply;
|
||||||
|
import com.openhis.check.domain.ExamApplyItem;
|
||||||
|
import com.openhis.check.service.IExamApplyItemService;
|
||||||
|
import com.openhis.check.service.IExamApplyService;
|
||||||
|
import com.openhis.common.constant.CommonConstants;
|
||||||
|
import com.openhis.common.enums.AssignSeqEnum;
|
||||||
|
import com.openhis.common.enums.ChargeItemStatus;
|
||||||
|
import com.openhis.common.enums.GenerateSource;
|
||||||
|
import com.openhis.common.enums.RequestStatus;
|
||||||
|
import com.openhis.web.check.dto.ExamApplyDto;
|
||||||
|
import com.openhis.web.check.dto.ExamApplyItemDto;
|
||||||
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
|
import com.openhis.workflow.service.IServiceRequestService;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查申请主表 Controller
|
||||||
|
* <p>
|
||||||
|
* 核心职责:检查申请单的增删查,保存时同步写入门诊医嘱表(wor_service_request)
|
||||||
|
* 和费用项表(adm_charge_item),删除时级联清理,保证业务数据闭环。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/exam/apply")
|
||||||
|
public class ExamApplyController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IExamApplyService examApplyService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IExamApplyItemService examApplyItemService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IServiceRequestService serviceRequestService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IChargeItemService chargeItemService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询检查申请单列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(ExamApply examApply) {
|
||||||
|
startPage();
|
||||||
|
LambdaQueryWrapper<ExamApply> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (examApply.getVisitNo() != null) {
|
||||||
|
wrapper.eq(ExamApply::getVisitNo, examApply.getVisitNo());
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(ExamApply::getApplyTime);
|
||||||
|
List<ExamApply> list = examApplyService.list(wrapper);
|
||||||
|
|
||||||
|
// 为每条申请单计算总金额
|
||||||
|
for (ExamApply apply : list) {
|
||||||
|
List<ExamApplyItem> items = examApplyItemService.list(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>()
|
||||||
|
.eq(ExamApplyItem::getApplyNo, apply.getApplyNo()));
|
||||||
|
|
||||||
|
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
for (ExamApplyItem item : items) {
|
||||||
|
if (item.getItemFee() != null) {
|
||||||
|
totalAmount = totalAmount.add(item.getItemFee());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply.setTotalAmount(totalAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取申请单详细信息(包含明细)
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/{applyNo}")
|
||||||
|
public AjaxResult getInfo(@PathVariable("applyNo") String applyNo) {
|
||||||
|
ExamApply examApply = examApplyService.getById(applyNo);
|
||||||
|
if (examApply == null) {
|
||||||
|
return AjaxResult.error("未找到申请单信息");
|
||||||
|
}
|
||||||
|
AjaxResult ajax = AjaxResult.success();
|
||||||
|
ajax.put("data", examApply);
|
||||||
|
|
||||||
|
// 挂载项目明细
|
||||||
|
List<ExamApplyItem> items = examApplyItemService.list(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
|
||||||
|
ajax.put("items", items);
|
||||||
|
|
||||||
|
return ajax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增检查申请单
|
||||||
|
* <p>
|
||||||
|
* 核心级联保存逻辑:
|
||||||
|
* 1. 生成申请单号,保存 exam_apply 主表
|
||||||
|
* 2. 批量保存 exam_apply_item 明细表
|
||||||
|
* 3. 为每条明细写入 wor_service_request(门诊医嘱),使检查进入医嘱体系
|
||||||
|
* 4. 为每条医嘱写入 adm_charge_item(费用项),使检查费用进入收费系统
|
||||||
|
* 5. 回写 exam_apply_item.service_request_id,建立双向关联
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public AjaxResult add(@RequestBody @Validated ExamApplyDto dto) {
|
||||||
|
|
||||||
|
// ========== 1. 生成申请单号并保存主表 ==========
|
||||||
|
String dateStr = DateUtil.format(new Date(), "yyyyMMdd");
|
||||||
|
String random4 = String.format("%04d", (int) (Math.random() * 10000));
|
||||||
|
String applyNo = "EX" + dateStr + random4;
|
||||||
|
|
||||||
|
ExamApply examApply = new ExamApply();
|
||||||
|
BeanUtils.copyProperties(dto, examApply);
|
||||||
|
examApply.setApplyNo(applyNo);
|
||||||
|
examApply.setApplyTime(LocalDateTime.now());
|
||||||
|
examApply.setCreateTime(new Date());
|
||||||
|
examApply.setApplyStatus(0); // 0=已开单
|
||||||
|
|
||||||
|
// 操作员工号取当前登录用户
|
||||||
|
try {
|
||||||
|
examApply.setOperatorId(SecurityUtils.getUsername());
|
||||||
|
} catch (Exception e) {
|
||||||
|
examApply.setOperatorId("system");
|
||||||
|
}
|
||||||
|
examApplyService.save(examApply);
|
||||||
|
|
||||||
|
// ========== 2. 批量保存明细 + 写入门诊医嘱 + 写入费用项 ==========
|
||||||
|
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
|
||||||
|
// 获取当前登录用户信息,用于写入医嘱和费用项
|
||||||
|
Long currentUserId = null;
|
||||||
|
Long currentOrgId = null;
|
||||||
|
Integer tenantId = null;
|
||||||
|
String currentUsername = "system";
|
||||||
|
try {
|
||||||
|
currentUserId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
currentOrgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
currentUsername = SecurityUtils.getUsername();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 获取失败时使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
int seq = 1;
|
||||||
|
|
||||||
|
for (ExamApplyItemDto itemDto : dto.getItems()) {
|
||||||
|
// ----- 2a. 保存检查明细 -----
|
||||||
|
ExamApplyItem item = new ExamApplyItem();
|
||||||
|
BeanUtils.copyProperties(itemDto, item);
|
||||||
|
item.setApplyNo(applyNo);
|
||||||
|
item.setItemSeq(seq++);
|
||||||
|
item.setItemStatus(0); // 同主表状态:已开单
|
||||||
|
examApplyItemService.save(item);
|
||||||
|
|
||||||
|
// ----- 2b. 写入门诊医嘱表 wor_service_request -----
|
||||||
|
ServiceRequest serviceRequest = new ServiceRequest();
|
||||||
|
// 生成唯一服务编码(SR + 日期 + 4位序列)
|
||||||
|
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||||
|
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue()); // 草稿/待发送
|
||||||
|
serviceRequest.setIntentEnum(1); // 意图:医嘱
|
||||||
|
serviceRequest.setQuantity(BigDecimal.ONE); // 检查默认数量1
|
||||||
|
serviceRequest.setUnitCode("次"); // 单位
|
||||||
|
|
||||||
|
// 通过 basedOnTable + basedOnId 关联回检查申请主表
|
||||||
|
serviceRequest.setBasedOnTable("exam_apply");
|
||||||
|
serviceRequest.setBasedOnId(examApply.getId());
|
||||||
|
|
||||||
|
// 检查申请不走诊疗定义,设置为0占位(数据库有NOT NULL约束)
|
||||||
|
serviceRequest.setActivityId(0L);
|
||||||
|
|
||||||
|
// 患者和就诊信息 —— 使用前端传递的数字型ID
|
||||||
|
if (dto.getPatientIdNum() != null) {
|
||||||
|
serviceRequest.setPatientId(dto.getPatientIdNum());
|
||||||
|
}
|
||||||
|
if (dto.getEncounterId() != null) {
|
||||||
|
serviceRequest.setEncounterId(dto.getEncounterId());
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceRequest.setRequesterId(currentUserId); // 开单医生
|
||||||
|
serviceRequest.setOrgId(currentOrgId); // 执行科室
|
||||||
|
serviceRequest.setAuthoredTime(now); // 签发时间
|
||||||
|
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 来源=医生开立
|
||||||
|
|
||||||
|
// 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName
|
||||||
|
if (itemDto.getItemName() != null) {
|
||||||
|
serviceRequest
|
||||||
|
.setContentJson("{\"adviceName\":\"" + itemDto.getItemName().replace("\"", "\\\"") + "\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 租户和审计字段
|
||||||
|
if (tenantId != null) {
|
||||||
|
serviceRequest.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
serviceRequest.setCreateBy(currentUsername);
|
||||||
|
serviceRequest.setCreateTime(now);
|
||||||
|
|
||||||
|
serviceRequestService.save(serviceRequest);
|
||||||
|
|
||||||
|
// ----- 2c. 写入费用项表 adm_charge_item -----
|
||||||
|
ChargeItem chargeItem = new ChargeItem();
|
||||||
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix()
|
||||||
|
.concat(serviceRequest.getBusNo())); // 费用项编码 = CI + 服务编码
|
||||||
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 草稿状态,待收费
|
||||||
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||||
|
|
||||||
|
// 关联医嘱表
|
||||||
|
chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
||||||
|
chargeItem.setServiceId(serviceRequest.getId());
|
||||||
|
|
||||||
|
// 患者和就诊
|
||||||
|
if (dto.getPatientIdNum() != null) {
|
||||||
|
chargeItem.setPatientId(dto.getPatientIdNum());
|
||||||
|
}
|
||||||
|
if (dto.getEncounterId() != null) {
|
||||||
|
chargeItem.setEncounterId(dto.getEncounterId());
|
||||||
|
}
|
||||||
|
|
||||||
|
chargeItem.setEntererId(currentUserId); // 开立人
|
||||||
|
chargeItem.setRequestingOrgId(currentOrgId); // 开立科室
|
||||||
|
chargeItem.setEnteredDate(now); // 开立时间
|
||||||
|
|
||||||
|
// 以下字段均有 NOT NULL 约束,检查申请不走定价/账户体系,用0占位
|
||||||
|
chargeItem.setDefinitionId(0L); // 费用定价ID
|
||||||
|
chargeItem.setAccountId(0L); // 关联账户ID
|
||||||
|
chargeItem.setContextEnum(2); // 类型:2=诊疗
|
||||||
|
chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION); // 产品来源表
|
||||||
|
chargeItem.setProductId(0L); // 产品ID
|
||||||
|
|
||||||
|
// 金额:单价和总价取检查项目费用
|
||||||
|
BigDecimal fee = itemDto.getItemFee() != null ? itemDto.getItemFee() : BigDecimal.ZERO;
|
||||||
|
chargeItem.setQuantityValue(BigDecimal.ONE); // 数量
|
||||||
|
chargeItem.setQuantityUnit("次"); // 单位
|
||||||
|
chargeItem.setUnitPrice(fee); // 单价
|
||||||
|
chargeItem.setTotalPrice(fee); // 总价 = 单价 × 1
|
||||||
|
|
||||||
|
// 租户和审计字段
|
||||||
|
if (tenantId != null) {
|
||||||
|
chargeItem.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
chargeItem.setCreateBy(currentUsername);
|
||||||
|
chargeItem.setCreateTime(now);
|
||||||
|
|
||||||
|
chargeItemService.save(chargeItem);
|
||||||
|
|
||||||
|
// ----- 2d. 回写明细表的医嘱关联ID -----
|
||||||
|
item.setServiceRequestId(serviceRequest.getId());
|
||||||
|
examApplyItemService.updateById(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AjaxResult.success("开单成功", applyNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改检查申请单
|
||||||
|
* <p>
|
||||||
|
* 采用「先删旧关联再重建」策略:
|
||||||
|
* 1. 更新 exam_apply 主表基本字段
|
||||||
|
* 2. 删除旧的明细、医嘱、费用项
|
||||||
|
* 3. 用新提交的明细重新创建医嘱和费用项
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@PutMapping
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public AjaxResult edit(@RequestBody @Validated ExamApplyDto dto) {
|
||||||
|
String applyNo = dto.getApplyNo();
|
||||||
|
if (applyNo == null || applyNo.isEmpty()) {
|
||||||
|
return AjaxResult.error("修改时申请单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 1. 更新主表基本信息 ==========
|
||||||
|
ExamApply examApply = examApplyService.getById(applyNo);
|
||||||
|
if (examApply == null) {
|
||||||
|
return AjaxResult.error("未找到该申请单");
|
||||||
|
}
|
||||||
|
// 只更新可编辑字段,不覆盖系统字段(applyNo, createTime等)
|
||||||
|
examApply.setNatureofCost(dto.getNatureofCost());
|
||||||
|
examApply.setApplyDeptCode(dto.getApplyDeptCode());
|
||||||
|
examApply.setPerformDeptCode(dto.getPerformDeptCode());
|
||||||
|
examApply.setApplyDocCode(dto.getApplyDocCode());
|
||||||
|
examApply.setClinicDesc(dto.getClinicDesc());
|
||||||
|
examApply.setContraindication(dto.getContraindication());
|
||||||
|
examApply.setClinicalDiag(dto.getClinicalDiag());
|
||||||
|
examApply.setMedicalHistorySummary(dto.getMedicalHistorySummary());
|
||||||
|
examApply.setPurposeDesc(dto.getPurposeDesc());
|
||||||
|
examApply.setPurposeofInspection(dto.getPurposeofInspection());
|
||||||
|
examApply.setInspectionArea(dto.getInspectionArea());
|
||||||
|
examApply.setInspectionMethod(dto.getInspectionMethod());
|
||||||
|
examApply.setApplyRemark(dto.getApplyRemark());
|
||||||
|
examApply.setIsUrgent(dto.getIsUrgent());
|
||||||
|
examApplyService.updateById(examApply);
|
||||||
|
|
||||||
|
// ========== 2. 删除旧的明细、医嘱、费用项 ==========
|
||||||
|
List<ExamApplyItem> oldItems = examApplyItemService.list(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
|
||||||
|
for (ExamApplyItem oldItem : oldItems) {
|
||||||
|
if (oldItem.getServiceRequestId() != null) {
|
||||||
|
// 删除旧费用项
|
||||||
|
chargeItemService.deleteByServiceTableAndId(
|
||||||
|
CommonConstants.TableName.WOR_SERVICE_REQUEST,
|
||||||
|
oldItem.getServiceRequestId());
|
||||||
|
// 删除旧医嘱
|
||||||
|
serviceRequestService.removeById(oldItem.getServiceRequestId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 删除旧明细
|
||||||
|
examApplyItemService.remove(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
|
||||||
|
|
||||||
|
// ========== 3. 用新提交的明细重新创建 ==========
|
||||||
|
if (dto.getItems() != null && !dto.getItems().isEmpty()) {
|
||||||
|
Long currentUserId = null;
|
||||||
|
Long currentOrgId = null;
|
||||||
|
Integer tenantId = null;
|
||||||
|
String currentUsername = "system";
|
||||||
|
try {
|
||||||
|
currentUserId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
currentOrgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
currentUsername = SecurityUtils.getUsername();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 获取失败时使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
Date now = new Date();
|
||||||
|
int seq = 1;
|
||||||
|
|
||||||
|
for (ExamApplyItemDto itemDto : dto.getItems()) {
|
||||||
|
// 保存新明细
|
||||||
|
ExamApplyItem item = new ExamApplyItem();
|
||||||
|
BeanUtils.copyProperties(itemDto, item);
|
||||||
|
item.setApplyNo(applyNo);
|
||||||
|
item.setItemSeq(seq++);
|
||||||
|
item.setItemStatus(0);
|
||||||
|
examApplyItemService.save(item);
|
||||||
|
|
||||||
|
// 写入门诊医嘱
|
||||||
|
ServiceRequest serviceRequest = new ServiceRequest();
|
||||||
|
serviceRequest.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.SERVICE_RES_NO.getPrefix(), 4));
|
||||||
|
serviceRequest.setStatusEnum(RequestStatus.DRAFT.getValue());
|
||||||
|
serviceRequest.setIntentEnum(1);
|
||||||
|
serviceRequest.setQuantity(BigDecimal.ONE);
|
||||||
|
serviceRequest.setUnitCode("次");
|
||||||
|
serviceRequest.setBasedOnTable("exam_apply");
|
||||||
|
serviceRequest.setBasedOnId(examApply.getId());
|
||||||
|
serviceRequest.setActivityId(0L);
|
||||||
|
|
||||||
|
if (dto.getPatientIdNum() != null) {
|
||||||
|
serviceRequest.setPatientId(dto.getPatientIdNum());
|
||||||
|
}
|
||||||
|
if (dto.getEncounterId() != null) {
|
||||||
|
serviceRequest.setEncounterId(dto.getEncounterId());
|
||||||
|
}
|
||||||
|
serviceRequest.setRequesterId(currentUserId);
|
||||||
|
serviceRequest.setOrgId(currentOrgId);
|
||||||
|
serviceRequest.setAuthoredTime(now);
|
||||||
|
serviceRequest.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||||
|
|
||||||
|
// 将项目名称存入 contentJson,使医嘱列表能通过 JSON 字段回显 adviceName
|
||||||
|
if (itemDto.getItemName() != null) {
|
||||||
|
serviceRequest
|
||||||
|
.setContentJson("{\"adviceName\":\"" + itemDto.getItemName().replace("\"", "\\\"") + "\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantId != null) {
|
||||||
|
serviceRequest.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
serviceRequest.setCreateBy(currentUsername);
|
||||||
|
serviceRequest.setCreateTime(now);
|
||||||
|
serviceRequestService.save(serviceRequest);
|
||||||
|
|
||||||
|
// 写入费用项
|
||||||
|
ChargeItem chargeItem = new ChargeItem();
|
||||||
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix()
|
||||||
|
.concat(serviceRequest.getBusNo()));
|
||||||
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue());
|
||||||
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||||
|
chargeItem.setServiceTable(CommonConstants.TableName.WOR_SERVICE_REQUEST);
|
||||||
|
chargeItem.setServiceId(serviceRequest.getId());
|
||||||
|
|
||||||
|
if (dto.getPatientIdNum() != null) {
|
||||||
|
chargeItem.setPatientId(dto.getPatientIdNum());
|
||||||
|
}
|
||||||
|
if (dto.getEncounterId() != null) {
|
||||||
|
chargeItem.setEncounterId(dto.getEncounterId());
|
||||||
|
}
|
||||||
|
chargeItem.setEntererId(currentUserId);
|
||||||
|
chargeItem.setRequestingOrgId(currentOrgId);
|
||||||
|
chargeItem.setEnteredDate(now);
|
||||||
|
chargeItem.setDefinitionId(0L);
|
||||||
|
chargeItem.setAccountId(0L);
|
||||||
|
chargeItem.setContextEnum(2);
|
||||||
|
chargeItem.setProductTable(CommonConstants.TableName.WOR_ACTIVITY_DEFINITION);
|
||||||
|
chargeItem.setProductId(0L);
|
||||||
|
|
||||||
|
BigDecimal fee = itemDto.getItemFee() != null ? itemDto.getItemFee() : BigDecimal.ZERO;
|
||||||
|
chargeItem.setQuantityValue(BigDecimal.ONE);
|
||||||
|
chargeItem.setQuantityUnit("次");
|
||||||
|
chargeItem.setUnitPrice(fee);
|
||||||
|
chargeItem.setTotalPrice(fee);
|
||||||
|
|
||||||
|
if (tenantId != null) {
|
||||||
|
chargeItem.setTenantId(tenantId);
|
||||||
|
}
|
||||||
|
chargeItem.setCreateBy(currentUsername);
|
||||||
|
chargeItem.setCreateTime(now);
|
||||||
|
chargeItemService.save(chargeItem);
|
||||||
|
|
||||||
|
// 回写明细表的医嘱关联ID
|
||||||
|
item.setServiceRequestId(serviceRequest.getId());
|
||||||
|
examApplyItemService.updateById(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AjaxResult.success("修改成功", applyNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除检查申请单
|
||||||
|
* <p>
|
||||||
|
* 级联操作:
|
||||||
|
* 1. 查询并删除关联的门诊医嘱(wor_service_request)和费用项(adm_charge_item)
|
||||||
|
* 2. 删除检查明细(exam_apply_item)
|
||||||
|
* 3. 删除主表(exam_apply)
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{applyNo}")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public AjaxResult remove(@PathVariable String applyNo) {
|
||||||
|
// 1. 查询该申请单的所有明细,获取关联的医嘱ID
|
||||||
|
List<ExamApplyItem> items = examApplyItemService.list(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
|
||||||
|
|
||||||
|
for (ExamApplyItem item : items) {
|
||||||
|
if (item.getServiceRequestId() != null) {
|
||||||
|
// 删除费用项(按医嘱表名+医嘱ID定位)
|
||||||
|
chargeItemService.deleteByServiceTableAndId(
|
||||||
|
CommonConstants.TableName.WOR_SERVICE_REQUEST,
|
||||||
|
item.getServiceRequestId());
|
||||||
|
// 删除门诊医嘱
|
||||||
|
serviceRequestService.removeById(item.getServiceRequestId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 删除明细表
|
||||||
|
examApplyItemService.remove(
|
||||||
|
new LambdaQueryWrapper<ExamApplyItem>().eq(ExamApplyItem::getApplyNo, applyNo));
|
||||||
|
|
||||||
|
// 3. 删除主表
|
||||||
|
examApplyService.removeById(applyNo);
|
||||||
|
|
||||||
|
return AjaxResult.success("删除/作废成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.openhis.web.check.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查申请数据传输对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ExamApplyDto implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 申请单号 (EXYYYYMMDD####) */
|
||||||
|
private String applyNo;
|
||||||
|
|
||||||
|
/** 患者主索引 EMPI */
|
||||||
|
private String patientId;
|
||||||
|
|
||||||
|
/** 门诊就诊流水号 */
|
||||||
|
private String visitNo;
|
||||||
|
|
||||||
|
/** 申请科室代码 */
|
||||||
|
private String applyDeptCode;
|
||||||
|
|
||||||
|
/** 执行科室代码 */
|
||||||
|
private String performDeptCode;
|
||||||
|
|
||||||
|
/** 申请医生工号 */
|
||||||
|
private String applyDocCode;
|
||||||
|
|
||||||
|
/** 申请时间 */
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime applyTime;
|
||||||
|
|
||||||
|
/** 就诊卡号 */
|
||||||
|
private String medicalrecordNumber;
|
||||||
|
|
||||||
|
/** 费用性质 */
|
||||||
|
private String natureofCost;
|
||||||
|
|
||||||
|
/** 诊断描述 */
|
||||||
|
private String clinicDesc;
|
||||||
|
|
||||||
|
/** 禁忌症 */
|
||||||
|
private String contraindication;
|
||||||
|
|
||||||
|
/** 病史摘要 */
|
||||||
|
private String medicalHistorySummary;
|
||||||
|
|
||||||
|
/** 体格检查 */
|
||||||
|
private String purposeofInspection;
|
||||||
|
|
||||||
|
/** 申检部位 */
|
||||||
|
private String inspectionArea;
|
||||||
|
|
||||||
|
/** 检查方法 */
|
||||||
|
private String inspectionMethod;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
private String applyRemark;
|
||||||
|
|
||||||
|
/** 检查大类代码 */
|
||||||
|
private String examTypeCode;
|
||||||
|
|
||||||
|
/** 临床诊断 */
|
||||||
|
private String clinicalDiag;
|
||||||
|
|
||||||
|
/** 检查目的 */
|
||||||
|
private String purposeDesc;
|
||||||
|
|
||||||
|
/** 加急标志 0 普通 1 加急 */
|
||||||
|
private Integer isUrgent;
|
||||||
|
|
||||||
|
/** 妊娠状态 0未知 1未孕 2可能孕 3孕妇 */
|
||||||
|
private Integer pregnancyState;
|
||||||
|
|
||||||
|
/** 过敏史 */
|
||||||
|
private String allergyDesc;
|
||||||
|
|
||||||
|
/** 申请单状态 0已开单 1已收费 2已预约 3已签到 4部分报告 5已完告 6作废 */
|
||||||
|
private Integer applyStatus;
|
||||||
|
|
||||||
|
/** 就诊ID(用于写入门诊医嘱和费用项关联) */
|
||||||
|
private Long encounterId;
|
||||||
|
|
||||||
|
/** 患者数字ID(用于写入门诊医嘱和费用项,与 patientId 的 EMPI 字符串不同) */
|
||||||
|
private Long patientIdNum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定的明细项目列表
|
||||||
|
*/
|
||||||
|
private List<ExamApplyItemDto> items;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.openhis.web.check.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查申请明细项目数据传输对象
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ExamApplyItemDto implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 项目代码 (院内收费项目) */
|
||||||
|
private String itemCode;
|
||||||
|
|
||||||
|
/** 项目名称 */
|
||||||
|
private String itemName;
|
||||||
|
|
||||||
|
/** 国家项目代码 */
|
||||||
|
private String nationalItemCode;
|
||||||
|
|
||||||
|
/** 检查部位代码 */
|
||||||
|
private String bodyPartCode;
|
||||||
|
|
||||||
|
/** 检查方法代码 */
|
||||||
|
private String examMethodCode;
|
||||||
|
|
||||||
|
/** 对比剂药品 */
|
||||||
|
private String contrastDrug;
|
||||||
|
|
||||||
|
/** 对比剂剂量 */
|
||||||
|
private String contrastDose;
|
||||||
|
|
||||||
|
/** 执行科室代码 */
|
||||||
|
private String performDeptCode;
|
||||||
|
|
||||||
|
/** 预约号 */
|
||||||
|
private String appointmentNo;
|
||||||
|
|
||||||
|
/** 项目单价 */
|
||||||
|
private BigDecimal itemFee;
|
||||||
|
|
||||||
|
/** 行状态 0 已开单 1 已收费 ... */
|
||||||
|
private Integer itemStatus;
|
||||||
|
|
||||||
|
/** 检查备注 */
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -533,94 +533,94 @@ public class SurgeryAppServiceImpl implements ISurgeryAppService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 填充手术记录中的名称字段
|
* 填充手术记录中的名称字段
|
||||||
* 根据ID反向查询用户表、机构表、手术室表、患者表、就诊表,填充对应的名称字段
|
* 优化:使用批量查询减少数据库访问次数
|
||||||
*
|
*
|
||||||
* @param surgery 手术实体对象
|
* @param surgery 手术实体对象
|
||||||
*/
|
*/
|
||||||
private void fillSurgeryNameFields(Surgery surgery) {
|
private void fillSurgeryNameFields(Surgery surgery) {
|
||||||
|
// 收集所有需要查询的ID
|
||||||
|
Set<Long> practitionerIds = new HashSet<>();
|
||||||
|
Set<Long> orgIds = new HashSet<>();
|
||||||
|
Set<Long> otherIds = new HashSet<>();
|
||||||
|
|
||||||
|
// 收集Practitioner IDs
|
||||||
|
if (surgery.getMainSurgeonId() != null) practitionerIds.add(surgery.getMainSurgeonId());
|
||||||
|
if (surgery.getAnesthetistId() != null) practitionerIds.add(surgery.getAnesthetistId());
|
||||||
|
if (surgery.getAssistant1Id() != null) practitionerIds.add(surgery.getAssistant1Id());
|
||||||
|
if (surgery.getAssistant2Id() != null) practitionerIds.add(surgery.getAssistant2Id());
|
||||||
|
if (surgery.getScrubNurseId() != null) practitionerIds.add(surgery.getScrubNurseId());
|
||||||
|
if (surgery.getApplyDoctorId() != null) practitionerIds.add(surgery.getApplyDoctorId());
|
||||||
|
|
||||||
|
// 收集Organization IDs
|
||||||
|
if (surgery.getOrgId() != null) orgIds.add(surgery.getOrgId());
|
||||||
|
if (surgery.getApplyDeptId() != null) orgIds.add(surgery.getApplyDeptId());
|
||||||
|
|
||||||
|
// 批量查询并缓存结果
|
||||||
|
Map<Long, String> practitionerNameMap = new HashMap<>();
|
||||||
|
Map<Long, String> orgNameMap = new HashMap<>();
|
||||||
|
|
||||||
|
// 批量查询Practitioner
|
||||||
|
if (!practitionerIds.isEmpty()) {
|
||||||
|
List<com.openhis.administration.domain.Practitioner> practitioners = practitionerService.listByIds(practitionerIds);
|
||||||
|
for (com.openhis.administration.domain.Practitioner p : practitioners) {
|
||||||
|
practitionerNameMap.put(p.getId(), p.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询Organization
|
||||||
|
if (!orgIds.isEmpty()) {
|
||||||
|
List<Organization> orgs = organizationService.listByIds(orgIds);
|
||||||
|
for (Organization o : orgs) {
|
||||||
|
orgNameMap.put(o.getId(), o.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 填充患者姓名
|
// 填充患者姓名
|
||||||
if (surgery.getPatientId() != null) {
|
if (surgery.getPatientId() != null && surgery.getPatientName() == null) {
|
||||||
Patient patient = patientService.getById(surgery.getPatientId());
|
Patient patient = patientService.getById(surgery.getPatientId());
|
||||||
if (patient != null) {
|
if (patient != null) {
|
||||||
surgery.setPatientName(patient.getName());
|
surgery.setPatientName(patient.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 填充主刀医生姓名(使用practitionerId查询Practitioner表)
|
// 使用缓存填充名称
|
||||||
if (surgery.getMainSurgeonId() != null) {
|
if (surgery.getMainSurgeonId() != null && surgery.getMainSurgeonName() == null) {
|
||||||
com.openhis.administration.domain.Practitioner mainSurgeon = practitionerService.getById(surgery.getMainSurgeonId());
|
surgery.setMainSurgeonName(practitionerNameMap.get(surgery.getMainSurgeonId()));
|
||||||
if (mainSurgeon != null) {
|
|
||||||
surgery.setMainSurgeonName(mainSurgeon.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (surgery.getAnesthetistId() != null && surgery.getAnesthetistName() == null) {
|
||||||
// 填充麻醉医生姓名(使用practitionerId查询Practitioner表)
|
surgery.setAnesthetistName(practitionerNameMap.get(surgery.getAnesthetistId()));
|
||||||
if (surgery.getAnesthetistId() != null) {
|
|
||||||
com.openhis.administration.domain.Practitioner anesthetist = practitionerService.getById(surgery.getAnesthetistId());
|
|
||||||
if (anesthetist != null) {
|
|
||||||
surgery.setAnesthetistName(anesthetist.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (surgery.getAssistant1Id() != null && surgery.getAssistant1Name() == null) {
|
||||||
// 填充助手1姓名(使用practitionerId查询Practitioner表)
|
surgery.setAssistant1Name(practitionerNameMap.get(surgery.getAssistant1Id()));
|
||||||
if (surgery.getAssistant1Id() != null) {
|
|
||||||
com.openhis.administration.domain.Practitioner assistant1 = practitionerService.getById(surgery.getAssistant1Id());
|
|
||||||
if (assistant1 != null) {
|
|
||||||
surgery.setAssistant1Name(assistant1.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (surgery.getAssistant2Id() != null && surgery.getAssistant2Name() == null) {
|
||||||
// 填充助手2姓名(使用practitionerId查询Practitioner表)
|
surgery.setAssistant2Name(practitionerNameMap.get(surgery.getAssistant2Id()));
|
||||||
if (surgery.getAssistant2Id() != null) {
|
|
||||||
com.openhis.administration.domain.Practitioner assistant2 = practitionerService.getById(surgery.getAssistant2Id());
|
|
||||||
if (assistant2 != null) {
|
|
||||||
surgery.setAssistant2Name(assistant2.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (surgery.getScrubNurseId() != null && surgery.getScrubNurseName() == null) {
|
||||||
// 填充巡回护士姓名(使用practitionerId查询Practitioner表)
|
surgery.setScrubNurseName(practitionerNameMap.get(surgery.getScrubNurseId()));
|
||||||
if (surgery.getScrubNurseId() != null) {
|
}
|
||||||
com.openhis.administration.domain.Practitioner scrubNurse = practitionerService.getById(surgery.getScrubNurseId());
|
if (surgery.getApplyDoctorId() != null && surgery.getApplyDoctorName() == null) {
|
||||||
if (scrubNurse != null) {
|
surgery.setApplyDoctorName(practitionerNameMap.get(surgery.getApplyDoctorId()));
|
||||||
surgery.setScrubNurseName(scrubNurse.getName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 填充手术室名称
|
// 填充手术室名称
|
||||||
if (surgery.getOperatingRoomId() != null) {
|
if (surgery.getOperatingRoomId() != null && surgery.getOperatingRoomName() == null) {
|
||||||
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
|
OperatingRoom operatingRoom = operatingRoomService.getById(surgery.getOperatingRoomId());
|
||||||
if (operatingRoom != null) {
|
if (operatingRoom != null) {
|
||||||
surgery.setOperatingRoomName(operatingRoom.getName());
|
surgery.setOperatingRoomName(operatingRoom.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 填充执行科室名称
|
// 使用缓存填充组织名称
|
||||||
if (surgery.getOrgId() != null) {
|
if (surgery.getOrgId() != null && surgery.getOrgName() == null) {
|
||||||
Organization org = organizationService.getById(surgery.getOrgId());
|
surgery.setOrgName(orgNameMap.get(surgery.getOrgId()));
|
||||||
if (org != null) {
|
}
|
||||||
surgery.setOrgName(org.getName());
|
if (surgery.getApplyDeptId() != null && surgery.getApplyDeptName() == null) {
|
||||||
}
|
surgery.setApplyDeptName(orgNameMap.get(surgery.getApplyDeptId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 填充申请科室名称(如果还没有设置)
|
log.debug("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, orgName: {}",
|
||||||
if (surgery.getApplyDeptId() != null && (surgery.getApplyDeptName() == null || surgery.getApplyDeptName().isEmpty())) {
|
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getOrgName());
|
||||||
Organization applyDept = organizationService.getById(surgery.getApplyDeptId());
|
|
||||||
if (applyDept != null) {
|
|
||||||
surgery.setApplyDeptName(applyDept.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 填充申请医生姓名(如果还没有设置) - 使用practitionerId查询Practitioner表
|
|
||||||
if (surgery.getApplyDoctorId() != null && (surgery.getApplyDoctorName() == null || surgery.getApplyDoctorName().isEmpty())) {
|
|
||||||
com.openhis.administration.domain.Practitioner applyDoctor = practitionerService.getById(surgery.getApplyDoctorId());
|
|
||||||
if (applyDoctor != null) {
|
|
||||||
surgery.setApplyDoctorName(applyDoctor.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("填充手术名称字段完成 - patientName: {}, mainSurgeonName: {}, anesthetistName: {}, assistant1Name: {}, assistant2Name: {}, scrubNurseName: {}, operatingRoomName: {}, orgName: {}",
|
|
||||||
surgery.getPatientName(), surgery.getMainSurgeonName(), surgery.getAnesthetistName(), surgery.getAssistant1Name(),
|
|
||||||
surgery.getAssistant2Name(), surgery.getScrubNurseName(), surgery.getOperatingRoomName(), surgery.getOrgName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public class OpScheduleDto extends OpSchedule {
|
|||||||
*/
|
*/
|
||||||
private String patientName;
|
private String patientName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 就诊ID
|
||||||
|
*/
|
||||||
|
private Long encounterId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 性别
|
* 性别
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import com.openhis.workflow.domain.DeviceDispense;
|
|||||||
import com.openhis.workflow.domain.InventoryItem;
|
import com.openhis.workflow.domain.InventoryItem;
|
||||||
import com.openhis.workflow.service.IDeviceDispenseService;
|
import com.openhis.workflow.service.IDeviceDispenseService;
|
||||||
import com.openhis.workflow.service.IInventoryItemService;
|
import com.openhis.workflow.service.IInventoryItemService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -59,6 +61,8 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class CommonServiceImpl implements ICommonService {
|
public class CommonServiceImpl implements ICommonService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CommonServiceImpl.class);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private AssignSeqUtil assignSeqUtil;
|
private AssignSeqUtil assignSeqUtil;
|
||||||
|
|
||||||
@@ -410,7 +414,7 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
try {
|
try {
|
||||||
BeanUtils.copyProperties(contract, metadata);
|
BeanUtils.copyProperties(contract, metadata);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.warn("Bean复制失败", e);
|
||||||
}
|
}
|
||||||
return metadata;
|
return metadata;
|
||||||
}).collect(Collectors.toList()));
|
}).collect(Collectors.toList()));
|
||||||
@@ -460,17 +464,86 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<LocationDto> getPractitionerWard() {
|
public List<LocationDto> getPractitionerWard() {
|
||||||
// 查询当前登录者管理的病区
|
// 获取当前登录用户信息
|
||||||
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
Long currentOrgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
|
||||||
|
log.info("getPractitionerWard - practitionerId: {}, currentOrgId: {}", practitionerId, currentOrgId);
|
||||||
|
|
||||||
|
// 获取用户配置的位置 ID 列表(可能包含科室、病区等)
|
||||||
List<Long> locationIds = practitionerRoleService.getLocationIdsByPractitionerId(practitionerId);
|
List<Long> locationIds = practitionerRoleService.getLocationIdsByPractitionerId(practitionerId);
|
||||||
List<Location> locationList
|
// 获取用户配置的科室 ID 列表
|
||||||
= locationService.getLocationList(locationIds, Collections.singletonList(LocationStatus.ACTIVE.getValue()));
|
List<Long> orgIds = practitionerRoleService.getOrgIdsByPractitionerId(practitionerId);
|
||||||
|
|
||||||
|
log.info("getPractitionerWard - locationIds: {}, orgIds: {}", locationIds, orgIds);
|
||||||
|
|
||||||
List<Location> wardList = new ArrayList<>();
|
List<Location> wardList = new ArrayList<>();
|
||||||
for (Location ward : locationList) {
|
|
||||||
if (LocationForm.WARD.getValue().equals(ward.getFormEnum())) {
|
// 方式 1:从 locationIds 中过滤出病区
|
||||||
wardList.add(ward);
|
if (locationIds != null && !locationIds.isEmpty()) {
|
||||||
|
// 过滤掉 null 值
|
||||||
|
locationIds = locationIds.stream().filter(id -> id != null).collect(java.util.stream.Collectors.toList());
|
||||||
|
if (!locationIds.isEmpty()) {
|
||||||
|
List<Location> locationList
|
||||||
|
= locationService.getLocationList(locationIds, Collections.singletonList(LocationStatus.ACTIVE.getValue()));
|
||||||
|
log.info("getPractitionerWard - 从 locationIds 查询到的位置总数:{}", locationList != null ? locationList.size() : 0);
|
||||||
|
|
||||||
|
for (Location location : locationList) {
|
||||||
|
log.info("getPractitionerWard - 位置:id={}, name={}, formEnum={}, organizationId={}",
|
||||||
|
location.getId(), location.getName(), location.getFormEnum(), location.getOrganizationId());
|
||||||
|
if (LocationForm.WARD.getValue().equals(location.getFormEnum())) {
|
||||||
|
// 如果当前有选择科室,只添加当前科室的病区
|
||||||
|
if (currentOrgId == null || currentOrgId.equals(location.getOrganizationId())) {
|
||||||
|
wardList.add(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 方式 2:从 orgIds 查询病区(补充查询,确保能获取到病区)
|
||||||
|
if (orgIds != null && !orgIds.isEmpty()) {
|
||||||
|
// 过滤掉 null 值并去重
|
||||||
|
orgIds = orgIds.stream().filter(id -> id != null).distinct().collect(java.util.stream.Collectors.toList());
|
||||||
|
if (!orgIds.isEmpty()) {
|
||||||
|
log.info("getPractitionerWard - 从 orgIds 查询病区,orgIds: {}", orgIds);
|
||||||
|
for (Long orgId : orgIds) {
|
||||||
|
// 如果当前有选择科室,只查询当前科室的病区
|
||||||
|
if (currentOrgId != null && !currentOrgId.equals(orgId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<Location> orgWards = locationService.getWardList(orgId);
|
||||||
|
log.info("getPractitionerWard - orgId: {} 查询到病区数:{}", orgId, orgWards != null ? orgWards.size() : 0);
|
||||||
|
if (orgWards != null && !orgWards.isEmpty()) {
|
||||||
|
wardList.addAll(orgWards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式 3:如果仍然没有病区,且 currentOrgId 为空,尝试获取所有病区
|
||||||
|
if (wardList.isEmpty() && currentOrgId == null) {
|
||||||
|
log.info("getPractitionerWard - 尝试获取所有病区");
|
||||||
|
List<Location> allWards = locationService.getWardList(null);
|
||||||
|
log.info("getPractitionerWard - 所有病区数:{}", allWards != null ? allWards.size() : 0);
|
||||||
|
if (allWards != null && !allWards.isEmpty()) {
|
||||||
|
wardList.addAll(allWards);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去重:根据病区 ID 去重(因为方式 1 和方式 2 可能会查询到相同的病区)
|
||||||
|
if (!wardList.isEmpty()) {
|
||||||
|
wardList = wardList.stream()
|
||||||
|
.collect(java.util.stream.Collectors.collectingAndThen(
|
||||||
|
java.util.stream.Collectors.toCollection(() ->
|
||||||
|
new java.util.TreeSet<>(java.util.Comparator.comparing(Location::getId))),
|
||||||
|
ArrayList::new
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("getPractitionerWard - 最终病区数:{}", wardList.size());
|
||||||
|
|
||||||
|
// 转换为 DTO
|
||||||
List<LocationDto> locationDtoList = new ArrayList<>();
|
List<LocationDto> locationDtoList = new ArrayList<>();
|
||||||
LocationDto locationDto;
|
LocationDto locationDto;
|
||||||
for (Location ward : wardList) {
|
for (Location ward : wardList) {
|
||||||
@@ -478,6 +551,31 @@ public class CommonServiceImpl implements ICommonService {
|
|||||||
BeanUtils.copyProperties(ward, locationDto);
|
BeanUtils.copyProperties(ward, locationDto);
|
||||||
locationDtoList.add(locationDto);
|
locationDtoList.add(locationDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return locationDtoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Location列表转换为LocationDto列表
|
||||||
|
*
|
||||||
|
* @param locationList Location列表
|
||||||
|
* @return LocationDto列表
|
||||||
|
*/
|
||||||
|
private List<LocationDto> convertToLocationDtoList(List<Location> locationList) {
|
||||||
|
List<LocationDto> locationDtoList = new ArrayList<>();
|
||||||
|
if (locationList == null || locationList.isEmpty()) {
|
||||||
|
return locationDtoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationDto locationDto;
|
||||||
|
for (Location location : locationList) {
|
||||||
|
// 只返回病区类型的位置
|
||||||
|
if (LocationForm.WARD.getValue().equals(location.getFormEnum())) {
|
||||||
|
locationDto = new LocationDto();
|
||||||
|
BeanUtils.copyProperties(location, locationDto);
|
||||||
|
locationDtoList.add(locationDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
return locationDtoList;
|
return locationDtoList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ public class CommonAppController {
|
|||||||
return R.ok(commonService.getInventoryCabinetList());
|
return R.ok(commonService.getInventoryCabinetList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 耗材库列表
|
||||||
|
*
|
||||||
|
* @return 耗材库列表
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/warehouse-list")
|
||||||
|
public R<?> getWarehouseList() {
|
||||||
|
return R.ok(commonService.getWarehouseList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 药房药库列表
|
* 药房药库列表
|
||||||
*
|
*
|
||||||
@@ -192,8 +202,8 @@ public class CommonAppController {
|
|||||||
* @return 病区列表
|
* @return 病区列表
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/practitioner-ward")
|
@GetMapping(value = "/practitioner-ward")
|
||||||
public List<LocationDto> getPractitionerWard() {
|
public R<?> getPractitionerWard() {
|
||||||
return commonService.getPractitionerWard();
|
return R.ok(commonService.getPractitionerWard());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.openhis.web.consultation.appservice;
|
package com.openhis.web.consultation.appservice;
|
||||||
|
|
||||||
import com.openhis.web.consultation.dto.ConsultationActivityDto;
|
import com.openhis.web.consultation.dto.*;
|
||||||
import com.openhis.web.consultation.dto.ConsultationRequestDto;
|
|
||||||
import com.openhis.web.consultation.dto.DepartmentTreeDto;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -23,6 +21,24 @@ public interface IConsultationAppService {
|
|||||||
*/
|
*/
|
||||||
List<ConsultationRequestDto> getConsultationList(Long encounterId);
|
List<ConsultationRequestDto> getConsultationList(Long encounterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会诊申请列表(支持多条件查询)
|
||||||
|
*
|
||||||
|
* @param dto 查询条件DTO
|
||||||
|
* @return 会诊列表
|
||||||
|
*/
|
||||||
|
List<ConsultationRequestDto> queryConsultationList(ConsultationRequestDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询会诊申请列表(支持多条件查询)
|
||||||
|
*
|
||||||
|
* @param dto 查询条件DTO
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<ConsultationRequestDto> queryConsultationListPage(ConsultationRequestDto dto, Integer pageNum, Integer pageSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存会诊申请
|
* 保存会诊申请
|
||||||
*
|
*
|
||||||
@@ -84,6 +100,55 @@ public interface IConsultationAppService {
|
|||||||
* @return 会诊项目列表
|
* @return 会诊项目列表
|
||||||
*/
|
*/
|
||||||
List<ConsultationActivityDto> getConsultationActivities();
|
List<ConsultationActivityDto> getConsultationActivities();
|
||||||
|
|
||||||
|
// ==================== 会诊确认相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待确认的会诊列表(当前医生被邀请的会诊)
|
||||||
|
*
|
||||||
|
* @return 待确认的会诊列表
|
||||||
|
*/
|
||||||
|
List<ConsultationConfirmationDto> getPendingConfirmationList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认会诊
|
||||||
|
*
|
||||||
|
* @param dto 会诊确认DTO
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean confirmConsultation(ConsultationConfirmationDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消确认会诊
|
||||||
|
*
|
||||||
|
* @param consultationId 会诊申请单号
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean cancelConfirmation(String consultationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名会诊
|
||||||
|
*
|
||||||
|
* @param consultationId 会诊申请单号
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean signConsultation(String consultationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会诊确认详情
|
||||||
|
*
|
||||||
|
* @param consultationId 会诊申请单号
|
||||||
|
* @return 会诊确认详情
|
||||||
|
*/
|
||||||
|
ConsultationConfirmationDto getConfirmationDetail(String consultationId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会诊意见列表
|
||||||
|
*
|
||||||
|
* @param consultationId 会诊申请单号
|
||||||
|
* @return 会诊意见列表
|
||||||
|
*/
|
||||||
|
List<ConsultationOpinionDto> getConsultationOpinions(String consultationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.openhis.web.consultation.appservice.impl;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.openhis.administration.domain.Encounter;
|
import com.openhis.administration.domain.Encounter;
|
||||||
import com.openhis.administration.domain.EncounterDiagnosis;
|
import com.openhis.administration.domain.EncounterDiagnosis;
|
||||||
@@ -15,6 +16,7 @@ import com.openhis.administration.mapper.PatientMapper;
|
|||||||
import com.openhis.administration.mapper.PractitionerMapper;
|
import com.openhis.administration.mapper.PractitionerMapper;
|
||||||
import com.openhis.clinical.domain.Condition;
|
import com.openhis.clinical.domain.Condition;
|
||||||
import com.openhis.clinical.mapper.ConditionMapper;
|
import com.openhis.clinical.mapper.ConditionMapper;
|
||||||
|
import com.openhis.web.consultation.mapper.ConsultationRequestMapper;
|
||||||
import com.openhis.workflow.domain.ServiceRequest;
|
import com.openhis.workflow.domain.ServiceRequest;
|
||||||
import com.openhis.workflow.service.IServiceRequestService;
|
import com.openhis.workflow.service.IServiceRequestService;
|
||||||
import com.openhis.common.enums.RequestStatus;
|
import com.openhis.common.enums.RequestStatus;
|
||||||
@@ -24,16 +26,19 @@ import com.core.common.utils.AssignSeqUtil;
|
|||||||
import com.openhis.common.enums.AssignSeqEnum;
|
import com.openhis.common.enums.AssignSeqEnum;
|
||||||
import com.openhis.web.consultation.appservice.IConsultationAppService;
|
import com.openhis.web.consultation.appservice.IConsultationAppService;
|
||||||
import com.openhis.web.consultation.dto.ConsultationActivityDto;
|
import com.openhis.web.consultation.dto.ConsultationActivityDto;
|
||||||
|
import com.openhis.web.consultation.dto.ConsultationConfirmationDto;
|
||||||
|
import com.openhis.web.consultation.dto.ConsultationOpinionDto;
|
||||||
import com.openhis.web.consultation.dto.ConsultationRequestDto;
|
import com.openhis.web.consultation.dto.ConsultationRequestDto;
|
||||||
import com.openhis.web.consultation.dto.DepartmentTreeDto;
|
import com.openhis.web.consultation.dto.DepartmentTreeDto;
|
||||||
import com.openhis.web.consultation.dto.InvitedObjectDto;
|
import com.openhis.web.consultation.dto.InvitedObjectDto;
|
||||||
import com.openhis.web.consultation.dto.PhysicianNodeDto;
|
import com.openhis.web.consultation.dto.PhysicianNodeDto;
|
||||||
import com.openhis.web.consultation.mapper.ConsultationRequestMapper;
|
|
||||||
import com.openhis.web.consultation.mapper.ConsultationInvitedMapper;
|
import com.openhis.web.consultation.mapper.ConsultationInvitedMapper;
|
||||||
|
import com.openhis.web.consultation.mapper.ConsultationConfirmationMapper;
|
||||||
import com.openhis.web.consultation.domain.ConsultationRequest;
|
import com.openhis.web.consultation.domain.ConsultationRequest;
|
||||||
import com.openhis.web.consultation.domain.ConsultationInvited;
|
import com.openhis.web.consultation.domain.ConsultationInvited;
|
||||||
|
import com.openhis.web.consultation.domain.ConsultationConfirmation;
|
||||||
import com.openhis.web.consultation.enums.ConsultationStatusEnum;
|
import com.openhis.web.consultation.enums.ConsultationStatusEnum;
|
||||||
import com.openhis.web.consultation.enums.InvitedStatusEnum;
|
|
||||||
import com.openhis.web.consultation.enums.ConsultationUrgencyEnum;
|
import com.openhis.web.consultation.enums.ConsultationUrgencyEnum;
|
||||||
import com.openhis.workflow.domain.ActivityDefinition;
|
import com.openhis.workflow.domain.ActivityDefinition;
|
||||||
import com.openhis.workflow.mapper.ActivityDefinitionMapper;
|
import com.openhis.workflow.mapper.ActivityDefinitionMapper;
|
||||||
@@ -72,6 +77,9 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ConsultationInvitedMapper consultationInvitedMapper;
|
private ConsultationInvitedMapper consultationInvitedMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ConsultationConfirmationMapper consultationConfirmationMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private EncounterMapper encounterMapper;
|
private EncounterMapper encounterMapper;
|
||||||
|
|
||||||
@@ -124,7 +132,6 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
// 查询会诊列表
|
// 查询会诊列表
|
||||||
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
|
||||||
// 根据就诊ID,查询该患者的会诊申请
|
// 根据就诊ID,查询该患者的会诊申请
|
||||||
wrapper.eq(ConsultationRequest::getEncounterId, encounterId);
|
wrapper.eq(ConsultationRequest::getEncounterId, encounterId);
|
||||||
wrapper.orderByDesc(ConsultationRequest::getCreateTime);
|
wrapper.orderByDesc(ConsultationRequest::getCreateTime);
|
||||||
@@ -134,9 +141,125 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
return list.stream().map(this::convertToDto).collect(Collectors.toList());
|
return list.stream().map(this::convertToDto).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ConsultationRequestDto> queryConsultationList(ConsultationRequestDto dto) {
|
||||||
|
try {
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 只查询当前租户的数据
|
||||||
|
wrapper.eq(ConsultationRequest::getTenantId, SecurityUtils.getLoginUser().getTenantId().longValue());
|
||||||
|
|
||||||
|
// 时间范围查询(根据前端选择的时间类型)
|
||||||
|
// 注意:前端传递的 timeType 字段不在实体中,需要在 DTO 中添加
|
||||||
|
// 这里默认按会诊时间查询
|
||||||
|
if (dto.getConsultationRequestDate() != null) {
|
||||||
|
// 如果传递了开始时间,按开始时间查询
|
||||||
|
wrapper.ge(ConsultationRequest::getConsultationDate, dto.getConsultationRequestDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申请科室模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getDepartment())) {
|
||||||
|
wrapper.like(ConsultationRequest::getDepartment, dto.getDepartment());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申请医生模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getRequestingPhysician())) {
|
||||||
|
wrapper.like(ConsultationRequest::getRequestingPhysician, dto.getRequestingPhysician());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会诊状态查询
|
||||||
|
if (dto.getConsultationStatus() != null) {
|
||||||
|
wrapper.eq(ConsultationRequest::getConsultationStatus, dto.getConsultationStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 紧急程度查询
|
||||||
|
if (StringUtils.hasText(dto.getConsultationUrgency())) {
|
||||||
|
wrapper.eq(ConsultationRequest::getConsultationUrgency, dto.getConsultationUrgency());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 病人姓名模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getPatientName())) {
|
||||||
|
wrapper.like(ConsultationRequest::getPatientName, dto.getPatientName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间倒序排列
|
||||||
|
wrapper.orderByDesc(ConsultationRequest::getConsultationRequestDate);
|
||||||
|
|
||||||
|
List<ConsultationRequest> list = consultationRequestMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
// 转换为DTO
|
||||||
|
return list.stream().map(this::convertToDto).collect(Collectors.toList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询会诊申请列表失败", e);
|
||||||
|
throw new RuntimeException("查询会诊申请列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<ConsultationRequestDto> queryConsultationListPage(ConsultationRequestDto dto, Integer pageNum, Integer pageSize) {
|
||||||
|
try {
|
||||||
|
// 设置分页参数
|
||||||
|
Page<ConsultationRequest> page = new Page<>(pageNum, pageSize);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 只查询当前租户的数据
|
||||||
|
wrapper.eq(ConsultationRequest::getTenantId, SecurityUtils.getLoginUser().getTenantId().longValue());
|
||||||
|
|
||||||
|
// 时间范围查询
|
||||||
|
if (dto.getConsultationRequestDate() != null) {
|
||||||
|
wrapper.ge(ConsultationRequest::getConsultationDate, dto.getConsultationRequestDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申请科室模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getDepartment())) {
|
||||||
|
wrapper.like(ConsultationRequest::getDepartment, dto.getDepartment());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申请医生模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getRequestingPhysician())) {
|
||||||
|
wrapper.like(ConsultationRequest::getRequestingPhysician, dto.getRequestingPhysician());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会诊状态查询
|
||||||
|
if (dto.getConsultationStatus() != null) {
|
||||||
|
wrapper.eq(ConsultationRequest::getConsultationStatus, dto.getConsultationStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 紧急程度查询
|
||||||
|
if (StringUtils.hasText(dto.getConsultationUrgency())) {
|
||||||
|
wrapper.eq(ConsultationRequest::getConsultationUrgency, dto.getConsultationUrgency());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 病人姓名模糊查询
|
||||||
|
if (StringUtils.hasText(dto.getPatientName())) {
|
||||||
|
wrapper.like(ConsultationRequest::getPatientName, dto.getPatientName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间倒序排列
|
||||||
|
wrapper.orderByDesc(ConsultationRequest::getConsultationRequestDate);
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
Page<ConsultationRequest> resultPage = consultationRequestMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
// 转换为DTO分页结果
|
||||||
|
Page<ConsultationRequestDto> dtoPage = new Page<>(resultPage.getCurrent(), resultPage.getSize(), resultPage.getTotal());
|
||||||
|
List<ConsultationRequestDto> dtoList = resultPage.getRecords().stream()
|
||||||
|
.map(this::convertToDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
dtoPage.setRecords(dtoList);
|
||||||
|
|
||||||
|
return dtoPage;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分页查询会诊申请列表失败", e);
|
||||||
|
throw new RuntimeException("分页查询会诊申请列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean saveConsultation(ConsultationRequestDto dto) {
|
public Boolean saveConsultation(ConsultationRequestDto dto) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 判断是新增还是更新
|
// 判断是新增还是更新
|
||||||
boolean isUpdate = (dto.getId() != null);
|
boolean isUpdate = (dto.getId() != null);
|
||||||
@@ -148,6 +271,12 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new RuntimeException("会诊申请记录不存在,ID: " + dto.getId());
|
throw new RuntimeException("会诊申请记录不存在,ID: " + dto.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🎯 状态校验:只有"新开"状态的会诊申请才能修改
|
||||||
|
ConsultationStatusEnum currentStatus = ConsultationStatusEnum.getByCode(entity.getConsultationStatus());
|
||||||
|
if (currentStatus != null && !currentStatus.canEdit()) {
|
||||||
|
throw new IllegalArgumentException("只有新开状态的会诊申请才能修改,当前状态:" + currentStatus.getDescription());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增:创建新记录
|
// 新增:创建新记录
|
||||||
entity = new ConsultationRequest();
|
entity = new ConsultationRequest();
|
||||||
@@ -167,8 +296,8 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
// 设置邀请对象文本(拼接所有医生名称)
|
// 设置邀请对象文本(拼接所有医生名称)
|
||||||
if (dto.getInvitedList() != null && !dto.getInvitedList().isEmpty()) {
|
if (dto.getInvitedList() != null && !dto.getInvitedList().isEmpty()) {
|
||||||
String invitedObjectText = dto.getInvitedList().stream()
|
String invitedObjectText = dto.getInvitedList().stream()
|
||||||
.map(InvitedObjectDto::getPhysicianName)
|
.map(InvitedObjectDto::getPhysicianName)
|
||||||
.collect(Collectors.joining(","));
|
.collect(Collectors.joining(","));
|
||||||
entity.setInvitedObject(invitedObjectText);
|
entity.setInvitedObject(invitedObjectText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,11 +350,17 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
invited.setInvitedDepartmentName(invitedDto.getDeptName());
|
invited.setInvitedDepartmentName(invitedDto.getDeptName());
|
||||||
invited.setInvitedPhysicianId(invitedDto.getPhysicianId());
|
invited.setInvitedPhysicianId(invitedDto.getPhysicianId());
|
||||||
invited.setInvitedPhysicianName(invitedDto.getPhysicianName());
|
invited.setInvitedPhysicianName(invitedDto.getPhysicianName());
|
||||||
invited.setInvitedStatus(InvitedStatusEnum.PENDING.getCode());
|
invited.setInvitedStatus(ConsultationStatusEnum.SUBMITTED.getCode()); // 待确认状态
|
||||||
invited.setTenantId(entity.getTenantId());
|
invited.setTenantId(entity.getTenantId());
|
||||||
|
|
||||||
consultationInvitedMapper.insert(invited);
|
consultationInvitedMapper.insert(invited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化邀请医生总数
|
||||||
|
entity.setInvitedCount(dto.getInvitedList().size());
|
||||||
|
entity.setConfirmedCount(0);
|
||||||
|
entity.setSignedCount(0);
|
||||||
|
consultationRequestMapper.updateById(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -466,7 +601,7 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
|
|
||||||
// 如果有诊断描述且与名称不同,添加到括号中
|
// 如果有诊断描述且与名称不同,添加到括号中
|
||||||
if (StringUtils.hasText(ed.getDiagnosisDesc()) &&
|
if (StringUtils.hasText(ed.getDiagnosisDesc()) &&
|
||||||
!ed.getDiagnosisDesc().equals(diagName)) {
|
!ed.getDiagnosisDesc().equals(diagName)) {
|
||||||
itemBuilder.append("(").append(ed.getDiagnosisDesc()).append(")");
|
itemBuilder.append("(").append(ed.getDiagnosisDesc()).append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,10 +700,49 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
invitedDto.setDeptName(invited.getInvitedDepartmentName());
|
invitedDto.setDeptName(invited.getInvitedDepartmentName());
|
||||||
invitedDto.setPhysicianId(invited.getInvitedPhysicianId());
|
invitedDto.setPhysicianId(invited.getInvitedPhysicianId());
|
||||||
invitedDto.setPhysicianName(invited.getInvitedPhysicianName());
|
invitedDto.setPhysicianName(invited.getInvitedPhysicianName());
|
||||||
|
// 🎯 新增:填充邀请状态、确认时间、签名时间
|
||||||
|
invitedDto.setInvitedStatus(invited.getInvitedStatus());
|
||||||
|
invitedDto.setConfirmTime(invited.getConfirmTime());
|
||||||
|
invitedDto.setSignatureTime(invited.getSignatureTime());
|
||||||
return invitedDto;
|
return invitedDto;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
dto.setInvitedList(invitedDtoList);
|
dto.setInvitedList(invitedDtoList);
|
||||||
|
|
||||||
|
// 🎯 如果会诊已完成或已签名,填充会诊记录信息(从已签名的医生中获取)
|
||||||
|
if (entity.getConsultationStatus() != null &&
|
||||||
|
(entity.getConsultationStatus() == ConsultationStatusEnum.SIGNED.getCode() ||
|
||||||
|
entity.getConsultationStatus() == ConsultationStatusEnum.COMPLETED.getCode())) {
|
||||||
|
|
||||||
|
// 查询所有已签名的医生(invited_status >= 3)
|
||||||
|
List<ConsultationInvited> signedPhysicians = invitedList.stream()
|
||||||
|
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= 3)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!signedPhysicians.isEmpty()) {
|
||||||
|
// 1. 会诊邀请参加医师:拼接所有已签名医生的"科室-姓名"
|
||||||
|
String invitedPhysiciansText = signedPhysicians.stream()
|
||||||
|
.map(inv -> inv.getInvitedDepartmentName() + "-" + inv.getInvitedPhysicianName())
|
||||||
|
.collect(Collectors.joining("、"));
|
||||||
|
dto.setInvitedPhysiciansText(invitedPhysiciansText);
|
||||||
|
|
||||||
|
// 2. 会诊意见:汇总所有已签名医生的意见
|
||||||
|
String consultationOpinion = signedPhysicians.stream()
|
||||||
|
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
|
||||||
|
.map(ConsultationInvited::getConfirmOpinion)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
dto.setConsultationOpinion(consultationOpinion);
|
||||||
|
|
||||||
|
// 3. 所属医生、代表科室、签名医生、签名时间:使用第一个签名的医生
|
||||||
|
ConsultationInvited firstSigned = signedPhysicians.get(0);
|
||||||
|
dto.setAttendingPhysician(firstSigned.getInvitedPhysicianName());
|
||||||
|
dto.setRepresentDepartment(firstSigned.getInvitedDepartmentName());
|
||||||
|
dto.setSignPhysician(firstSigned.getInvitedPhysicianName());
|
||||||
|
dto.setSignTime(firstSigned.getSignatureTime());
|
||||||
|
|
||||||
|
log.info("填充会诊记录信息,已签名医生数:{}", signedPhysicians.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,7 +761,7 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
* 生成会诊申请单号(高性能版本)
|
* 生成会诊申请单号(高性能版本)
|
||||||
* 格式:CS + 年月日时分秒 + 4位序列号
|
* 格式:CS + 年月日时分秒 + 4位序列号
|
||||||
* 例如:CS202601301425300001
|
* 例如:CS202601301425300001
|
||||||
*
|
* <p>
|
||||||
* 优化说明:
|
* 优化说明:
|
||||||
* 1. 使用 AtomicInteger 保证线程安全,性能远高于 synchronized
|
* 1. 使用 AtomicInteger 保证线程安全,性能远高于 synchronized
|
||||||
* 2. 每秒最多支持 10000 个并发请求(0000-9999)
|
* 2. 每秒最多支持 10000 个并发请求(0000-9999)
|
||||||
@@ -792,10 +966,10 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
// 🎯 查询患者的费用性质(account_id)
|
// 🎯 查询患者的费用性质(account_id)
|
||||||
// 从 adm_account 表获取患者的 account_id(费用性质)
|
// 从 adm_account 表获取患者的 account_id(费用性质)
|
||||||
Long accountId = doctorStationAdviceAppMapper.getEncounterContract(consultationRequest.getEncounterId())
|
Long accountId = doctorStationAdviceAppMapper.getEncounterContract(consultationRequest.getEncounterId())
|
||||||
.stream()
|
.stream()
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(contract -> contract.getAccountId())
|
.map(contract -> contract.getAccountId())
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
if (accountId == null) {
|
if (accountId == null) {
|
||||||
log.warn("未找到患者的费用性质,encounterId: {}", consultationRequest.getEncounterId());
|
log.warn("未找到患者的费用性质,encounterId: {}", consultationRequest.getEncounterId());
|
||||||
@@ -886,7 +1060,7 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
* 更新门诊医嘱状态
|
* 更新门诊医嘱状态
|
||||||
*
|
*
|
||||||
* @param orderId 医嘱ID
|
* @param orderId 医嘱ID
|
||||||
* @param status 新状态
|
* @param status 新状态
|
||||||
*/
|
*/
|
||||||
private void updateServiceRequestStatus(Long orderId, Integer status) {
|
private void updateServiceRequestStatus(Long orderId, Integer status) {
|
||||||
try {
|
try {
|
||||||
@@ -963,5 +1137,643 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
|
|||||||
throw new RuntimeException("查询会诊项目列表失败: " + e.getMessage());
|
throw new RuntimeException("查询会诊项目列表失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 会诊确认相关实现 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ConsultationConfirmationDto> getPendingConfirmationList() {
|
||||||
|
try {
|
||||||
|
// 获取当前登录医生ID
|
||||||
|
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
log.info("获取待确认会诊列表,当前医生ID: {}", currentPhysicianId);
|
||||||
|
|
||||||
|
// 🎯 关键修改:查询当前医生个人状态为"待确认"、"已确认"或"已签名"的邀请记录
|
||||||
|
// 10=已提交(待确认)、20=已确认(待签名)、30=已签名,排除40=已完成
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
|
||||||
|
invitedWrapper.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId)
|
||||||
|
.in(ConsultationInvited::getInvitedStatus,
|
||||||
|
ConsultationStatusEnum.SUBMITTED.getCode(), // 10-待确认
|
||||||
|
ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认(待签名)
|
||||||
|
ConsultationStatusEnum.SIGNED.getCode()) // 30-已签名
|
||||||
|
.orderByDesc(ConsultationInvited::getCreateTime);
|
||||||
|
|
||||||
|
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper);
|
||||||
|
log.info("查询到待确认/待签名/已签名的邀请记录数量: {}", invitedList.size());
|
||||||
|
|
||||||
|
if (invitedList.isEmpty()) {
|
||||||
|
log.warn("当前医生没有待确认/待签名/已签名的会诊申请");
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有会诊申请ID
|
||||||
|
List<Long> requestIds = invitedList.stream()
|
||||||
|
.map(ConsultationInvited::getConsultationRequestId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.info("会诊申请ID列表: {}", requestIds);
|
||||||
|
|
||||||
|
// 🎯 查询会诊申请详情(白名单:只查询正在进行中的会诊,明确业务范围)
|
||||||
|
// 查询已提交、已确认、已签名状态的会诊,排除已完成(40)
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.in(ConsultationRequest::getId, requestIds)
|
||||||
|
.in(ConsultationRequest::getConsultationStatus,
|
||||||
|
ConsultationStatusEnum.SUBMITTED.getCode(), // 10-已提交
|
||||||
|
ConsultationStatusEnum.CONFIRMED.getCode(), // 20-已确认
|
||||||
|
ConsultationStatusEnum.SIGNED.getCode()) // 30-已签名
|
||||||
|
.orderByDesc(ConsultationRequest::getConsultationRequestDate);
|
||||||
|
|
||||||
|
List<ConsultationRequest> requests = consultationRequestMapper.selectList(requestWrapper);
|
||||||
|
log.info("查询到符合条件的会诊申请数量: {}", requests.size());
|
||||||
|
|
||||||
|
if (requests.isEmpty()) {
|
||||||
|
log.warn("没有符合条件的会诊申请");
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为DTO并添加当前医生的个人状态
|
||||||
|
Map<Long, ConsultationInvited> invitedMap = invitedList.stream()
|
||||||
|
.collect(Collectors.toMap(ConsultationInvited::getConsultationRequestId, v -> v, (v1, v2) -> v1));
|
||||||
|
|
||||||
|
List<ConsultationConfirmationDto> resultList = new ArrayList<>();
|
||||||
|
for (ConsultationRequest request : requests) {
|
||||||
|
ConsultationConfirmationDto dto = convertToConfirmationDto(request);
|
||||||
|
|
||||||
|
// 添加当前医生的个人状态
|
||||||
|
ConsultationInvited myInvited = invitedMap.get(request.getId());
|
||||||
|
if (myInvited != null) {
|
||||||
|
dto.setMyInvitedStatus(myInvited.getInvitedStatus());
|
||||||
|
dto.setMyConfirmTime(myInvited.getConfirmTime());
|
||||||
|
dto.setMySignatureTime(myInvited.getSignatureTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
resultList.add(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("返回待确认会诊列表数量: {}", resultList.size());
|
||||||
|
return resultList;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取待确认会诊列表失败", e);
|
||||||
|
throw new RuntimeException("获取待确认会诊列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean confirmConsultation(ConsultationConfirmationDto dto) {
|
||||||
|
try {
|
||||||
|
// 验证
|
||||||
|
if (!StringUtils.hasText(dto.getConsultationId())) {
|
||||||
|
throw new IllegalArgumentException("会诊申请单号不能为空");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(dto.getConsultationOpinion())) {
|
||||||
|
throw new IllegalArgumentException("会诊意见不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 1. 查询会诊申请
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.eq(ConsultationRequest::getConsultationId, dto.getConsultationId());
|
||||||
|
ConsultationRequest request = consultationRequestMapper.selectOne(requestWrapper);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
throw new IllegalArgumentException("会诊申请不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有已提交状态才能确认
|
||||||
|
if (request.getConsultationStatus() != ConsultationStatusEnum.SUBMITTED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("只有已提交状态的会诊申请才能确认");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取当前登录医生信息
|
||||||
|
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
String currentPhysicianName = SecurityUtils.getLoginUser().getUser().getNickName();
|
||||||
|
String currentDeptName = dto.getConfirmingDeptName(); // 前端传递
|
||||||
|
|
||||||
|
// 3. 查询当前医生的邀请记录
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
|
||||||
|
invitedWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId);
|
||||||
|
ConsultationInvited invited = consultationInvitedMapper.selectOne(invitedWrapper);
|
||||||
|
|
||||||
|
if (invited == null) {
|
||||||
|
throw new IllegalArgumentException("您不在被邀请的医生列表中");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invited.getInvitedStatus() != null && invited.getInvitedStatus() >= ConsultationStatusEnum.CONFIRMED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("您已经确认过了,无需重复确认");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 更新邀请记录(存储会诊意见)
|
||||||
|
// 格式:科室-医生:意见内容
|
||||||
|
String formattedOpinion = String.format("%s-%s:%s",
|
||||||
|
currentDeptName,
|
||||||
|
currentPhysicianName,
|
||||||
|
dto.getConsultationOpinion());
|
||||||
|
|
||||||
|
invited.setInvitedStatus(ConsultationStatusEnum.CONFIRMED.getCode()); // 已确认
|
||||||
|
invited.setConfirmOpinion(formattedOpinion);
|
||||||
|
invited.setConfirmTime(new Date());
|
||||||
|
consultationInvitedMapper.updateById(invited);
|
||||||
|
|
||||||
|
log.info("医生 {} 确认会诊,意见:{}", currentPhysicianName, formattedOpinion);
|
||||||
|
|
||||||
|
// 5. 更新会诊申请的确认计数
|
||||||
|
Integer confirmedCount = (request.getConfirmedCount() == null ? 0 : request.getConfirmedCount()) + 1;
|
||||||
|
request.setConfirmedCount(confirmedCount);
|
||||||
|
|
||||||
|
// 6. 检查是否所有医生都确认了
|
||||||
|
Integer invitedCount = request.getInvitedCount();
|
||||||
|
if (invitedCount == null || invitedCount == 0) {
|
||||||
|
// 如果没有初始化,查询实际邀请人数
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> countWrapper = new LambdaQueryWrapper<>();
|
||||||
|
countWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId());
|
||||||
|
invitedCount = consultationInvitedMapper.selectCount(countWrapper).intValue();
|
||||||
|
request.setInvitedCount(invitedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("会诊确认进度:{}/{}", confirmedCount, invitedCount);
|
||||||
|
// 7. 如果所有确认医生数量>= 邀请医生的数量,更新会诊申请状态为已确认
|
||||||
|
if (confirmedCount >= invitedCount) {
|
||||||
|
// 所有医生都确认了
|
||||||
|
request.setConsultationStatus(ConsultationStatusEnum.CONFIRMED.getCode());
|
||||||
|
|
||||||
|
// 创建确认记录
|
||||||
|
createConfirmationRecord(request);
|
||||||
|
|
||||||
|
// 更新医嘱状态为"已执行"
|
||||||
|
updateServiceRequestStatus(request.getOrderId(), RequestStatus.ACTIVE.getValue());
|
||||||
|
|
||||||
|
log.info("所有医生都已确认,会诊申请状态更新为:已确认");
|
||||||
|
}
|
||||||
|
|
||||||
|
consultationRequestMapper.updateById(request);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("确认会诊失败", e);
|
||||||
|
throw new RuntimeException("确认会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建确认记录
|
||||||
|
*
|
||||||
|
* 业务逻辑说明:
|
||||||
|
* 1. 只有当所有医生都确认后,才会调用此方法
|
||||||
|
* 2. "确认医生"应该是最后一个确认的医生(触发全局状态变为"已确认"的医生)
|
||||||
|
* 3. 也就是当前正在操作确认的医生(从SecurityUtils获取)
|
||||||
|
*/
|
||||||
|
private void createConfirmationRecord(ConsultationRequest request) {
|
||||||
|
try {
|
||||||
|
// 🎯 获取当前操作的医生信息(最后一个确认的医生)
|
||||||
|
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
String currentPhysicianName = SecurityUtils.getLoginUser().getUser().getNickName();
|
||||||
|
|
||||||
|
// 查询当前医生的邀请记录(获取科室信息)
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> currentWrapper = new LambdaQueryWrapper<>();
|
||||||
|
currentWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId);
|
||||||
|
ConsultationInvited currentInvited = consultationInvitedMapper.selectOne(currentWrapper);
|
||||||
|
|
||||||
|
if (currentInvited == null) {
|
||||||
|
log.error("未找到当前医生的邀请记录,医生ID: {}", currentPhysicianId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有已确认的医生(用于汇总意见和医生列表)
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.ge(ConsultationInvited::getInvitedStatus, ConsultationStatusEnum.CONFIRMED.getCode())
|
||||||
|
.orderByAsc(ConsultationInvited::getConfirmTime); // 按确认时间排序
|
||||||
|
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
if (invitedList.isEmpty()) {
|
||||||
|
log.warn("没有找到已确认的医生");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 汇总所有医生的意见
|
||||||
|
String allOpinions = invitedList.stream()
|
||||||
|
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
|
||||||
|
.map(ConsultationInvited::getConfirmOpinion)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
|
// 构建医生列表JSON(按确认时间排序)
|
||||||
|
List<Map<String, Object>> physicians = invitedList.stream()
|
||||||
|
.map(inv -> {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("physicianId", inv.getInvitedPhysicianId());
|
||||||
|
map.put("physicianName", inv.getInvitedPhysicianName());
|
||||||
|
map.put("deptName", inv.getInvitedDepartmentName());
|
||||||
|
map.put("confirmTime", inv.getConfirmTime());
|
||||||
|
return map;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 检查是否已存在确认记录
|
||||||
|
LambdaQueryWrapper<ConsultationConfirmation> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(ConsultationConfirmation::getConsultationRequestId, request.getId());
|
||||||
|
ConsultationConfirmation existingConfirmation = consultationConfirmationMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
|
if (existingConfirmation != null) {
|
||||||
|
// 🎯 更新现有记录(使用当前操作的医生信息)
|
||||||
|
existingConfirmation.setAllConfirmedDate(new Date());
|
||||||
|
existingConfirmation.setConfirmingPhysicianId(currentPhysicianId);
|
||||||
|
existingConfirmation.setConfirmingPhysicianName(currentPhysicianName);
|
||||||
|
existingConfirmation.setConfirmingDeptId(currentInvited.getInvitedDepartmentId());
|
||||||
|
existingConfirmation.setConfirmingDeptName(currentInvited.getInvitedDepartmentName());
|
||||||
|
existingConfirmation.setConfirmingDate(currentInvited.getConfirmTime());
|
||||||
|
existingConfirmation.setConsultationStatus(ConsultationStatusEnum.CONFIRMED.getCode()); // 使用枚举
|
||||||
|
existingConfirmation.setConsultationOpinion(allOpinions);
|
||||||
|
existingConfirmation.setConfirmingPhysicians(JSON.toJSONString(physicians));
|
||||||
|
|
||||||
|
consultationConfirmationMapper.updateById(existingConfirmation);
|
||||||
|
log.info("更新会诊确认记录成功,确认医生:{},参与医生数:{}", currentPhysicianName, physicians.size());
|
||||||
|
} else {
|
||||||
|
// 🎯 创建新记录(使用当前操作的医生信息)
|
||||||
|
ConsultationConfirmation confirmation = new ConsultationConfirmation();
|
||||||
|
confirmation.setConsultationRequestId(request.getId());
|
||||||
|
confirmation.setConsultationId(request.getConsultationId());
|
||||||
|
confirmation.setAllConfirmedDate(new Date());
|
||||||
|
|
||||||
|
// 设置确认医生信息(当前操作的医生 = 最后一个确认的医生)
|
||||||
|
confirmation.setConfirmingPhysicianId(currentPhysicianId);
|
||||||
|
confirmation.setConfirmingPhysicianName(currentPhysicianName);
|
||||||
|
confirmation.setConfirmingDeptId(currentInvited.getInvitedDepartmentId());
|
||||||
|
confirmation.setConfirmingDeptName(currentInvited.getInvitedDepartmentName());
|
||||||
|
confirmation.setConfirmingDate(currentInvited.getConfirmTime());
|
||||||
|
|
||||||
|
// 设置会诊状态(使用枚举)
|
||||||
|
confirmation.setConsultationStatus(ConsultationStatusEnum.CONFIRMED.getCode());
|
||||||
|
|
||||||
|
confirmation.setConsultationOpinion(allOpinions);
|
||||||
|
confirmation.setConfirmingPhysicians(JSON.toJSONString(physicians));
|
||||||
|
confirmation.setTenantId(SecurityUtils.getLoginUser().getTenantId().longValue());
|
||||||
|
|
||||||
|
consultationConfirmationMapper.insert(confirmation);
|
||||||
|
log.info("创建会诊确认记录成功,确认医生:{},参与医生数:{}", currentPhysicianName, physicians.size());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建确认记录失败", e);
|
||||||
|
throw new RuntimeException("创建确认记录失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean cancelConfirmation(String consultationId) {
|
||||||
|
try {
|
||||||
|
if (!StringUtils.hasText(consultationId)) {
|
||||||
|
throw new IllegalArgumentException("会诊申请单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询会诊申请
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.eq(ConsultationRequest::getConsultationId, consultationId);
|
||||||
|
ConsultationRequest request = consultationRequestMapper.selectOne(requestWrapper);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
throw new IllegalArgumentException("会诊申请不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 2. 获取当前登录医生信息
|
||||||
|
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
|
||||||
|
// 3. 查询当前医生的邀请记录
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
|
||||||
|
invitedWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId);
|
||||||
|
ConsultationInvited invited = consultationInvitedMapper.selectOne(invitedWrapper);
|
||||||
|
|
||||||
|
if (invited == null) {
|
||||||
|
throw new IllegalArgumentException("您不在被邀请的医生列表中");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invited.getInvitedStatus() == null || invited.getInvitedStatus() < ConsultationStatusEnum.CONFIRMED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("您还未确认,无需取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invited.getInvitedStatus() >= ConsultationStatusEnum.SIGNED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("已签名的确认无法取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 取消该医生的确认
|
||||||
|
invited.setInvitedStatus(ConsultationStatusEnum.SUBMITTED.getCode()); // 改回待确认
|
||||||
|
invited.setConfirmOpinion(null);
|
||||||
|
invited.setConfirmTime(null);
|
||||||
|
consultationInvitedMapper.updateById(invited);
|
||||||
|
|
||||||
|
// 5. 更新会诊申请的确认计数
|
||||||
|
Integer confirmedCount = request.getConfirmedCount() - 1;
|
||||||
|
if (confirmedCount < 0) confirmedCount = 0;
|
||||||
|
request.setConfirmedCount(confirmedCount);
|
||||||
|
|
||||||
|
// 6. 如果不是所有医生都确认了,申请表状态改回"已提交",确认表删除确认记录,邀请表实时更新状态。
|
||||||
|
if (confirmedCount < request.getInvitedCount()) {
|
||||||
|
request.setConsultationStatus(ConsultationStatusEnum.SUBMITTED.getCode());
|
||||||
|
|
||||||
|
// 🎯 物理删除确认记录(因为不是所有医生都确认了,确认记录不应该存在)
|
||||||
|
int deletedCount = consultationConfirmationMapper.physicalDeleteByRequestId(request.getId());
|
||||||
|
if (deletedCount > 0) {
|
||||||
|
log.info("取消确认后,确认人数({}) < 邀请人数({}),物理删除确认记录", confirmedCount, request.getInvitedCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("取消确认后,会诊申请状态改回:已提交");
|
||||||
|
}
|
||||||
|
|
||||||
|
consultationRequestMapper.updateById(request);
|
||||||
|
|
||||||
|
// 更新医嘱状态为已提交
|
||||||
|
updateServiceRequestStatus(request.getOrderId(), RequestStatus.ACTIVE.getValue());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("取消确认会诊失败", e);
|
||||||
|
throw new RuntimeException("取消确认会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean signConsultation(String consultationId) {
|
||||||
|
try {
|
||||||
|
if (!StringUtils.hasText(consultationId)) {
|
||||||
|
throw new IllegalArgumentException("会诊申请单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询会诊申请
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.eq(ConsultationRequest::getConsultationId, consultationId);
|
||||||
|
ConsultationRequest request = consultationRequestMapper.selectOne(requestWrapper);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
throw new IllegalArgumentException("会诊申请不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只要会诊已提交(状态>=10),当前医生已确认就可以签名
|
||||||
|
if (request.getConsultationStatus() == null || request.getConsultationStatus() < ConsultationStatusEnum.SUBMITTED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("会诊申请尚未提交,无法签名");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取当前登录医生信息
|
||||||
|
Long currentPhysicianId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
String currentPhysicianName = SecurityUtils.getLoginUser().getUser().getNickName();
|
||||||
|
|
||||||
|
// 3. 查询当前医生的邀请记录
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
|
||||||
|
invitedWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.eq(ConsultationInvited::getInvitedPhysicianId, currentPhysicianId);
|
||||||
|
ConsultationInvited invited = consultationInvitedMapper.selectOne(invitedWrapper);
|
||||||
|
|
||||||
|
if (invited == null) {
|
||||||
|
throw new IllegalArgumentException("您不在被邀请的医生列表中");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invited.getInvitedStatus() == null || invited.getInvitedStatus() < ConsultationStatusEnum.CONFIRMED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("请先确认会诊后再签名");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invited.getInvitedStatus() >= ConsultationStatusEnum.SIGNED.getCode()) {
|
||||||
|
throw new IllegalArgumentException("您已经签名过了");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 更新邀请记录(只改变个人状态)
|
||||||
|
invited.setInvitedStatus(ConsultationStatusEnum.SIGNED.getCode()); // 已签名
|
||||||
|
invited.setSignatureTime(new Date());
|
||||||
|
consultationInvitedMapper.updateById(invited);
|
||||||
|
|
||||||
|
log.info("医生 {} 签名成功", currentPhysicianName);
|
||||||
|
|
||||||
|
// 5. 更新会诊申请的签名计数
|
||||||
|
Integer signedCount = (request.getSignedCount() == null ? 0 : request.getSignedCount()) + 1;
|
||||||
|
request.setSignedCount(signedCount);
|
||||||
|
|
||||||
|
// 6. 检查是否所有医生都签名了
|
||||||
|
Integer invitedCount = request.getInvitedCount();
|
||||||
|
log.info("会诊签名进度:{}/{}", signedCount, invitedCount);
|
||||||
|
|
||||||
|
if (signedCount >= invitedCount) {
|
||||||
|
// 🎯 所有医生都签名了,整体状态才变为"已签名"
|
||||||
|
request.setConsultationStatus(ConsultationStatusEnum.SIGNED.getCode());
|
||||||
|
request.setSignature(currentPhysicianName);
|
||||||
|
request.setSignaturePhysicianId(currentPhysicianId);
|
||||||
|
request.setSignatureDate(new Date());
|
||||||
|
|
||||||
|
// 更新确认记录(如果不存在则创建)
|
||||||
|
LambdaQueryWrapper<ConsultationConfirmation> confirmWrapper = new LambdaQueryWrapper<>();
|
||||||
|
confirmWrapper.eq(ConsultationConfirmation::getConsultationRequestId, request.getId());
|
||||||
|
ConsultationConfirmation confirmation = consultationConfirmationMapper.selectOne(confirmWrapper);
|
||||||
|
|
||||||
|
if (confirmation == null) {
|
||||||
|
// 如果确认记录不存在,创建一个
|
||||||
|
createConfirmationRecord(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新确认记录
|
||||||
|
updateConfirmationRecord(request);
|
||||||
|
|
||||||
|
// 更新医嘱状态为"已完成"
|
||||||
|
updateServiceRequestStatus(request.getOrderId(), RequestStatus.COMPLETED.getValue());
|
||||||
|
|
||||||
|
log.info("所有医生都已签名,会诊申请状态更新为:已签名(30)");
|
||||||
|
} else {
|
||||||
|
// 🎯 关键修改:部分医生签名,整体状态不变(保持为10或20)
|
||||||
|
// 签名只改变 invited_status,不改变整体状态
|
||||||
|
log.info("部分医生已签名({}/{}), 整体状态保持不变: {}", signedCount, invitedCount, request.getConsultationStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
consultationRequestMapper.updateById(request);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("签名会诊失败", e);
|
||||||
|
throw new RuntimeException("签名会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新确认记录(所有医生都签名后调用)
|
||||||
|
*
|
||||||
|
* 业务逻辑说明:
|
||||||
|
* 1. 只有当所有医生都签名后,才会调用此方法
|
||||||
|
* 2. "签名医生"应该是最后一个签名的医生(触发全局状态变为"已签名"的医生)
|
||||||
|
* 3. 也就是当前正在操作签名的医生(从request中获取)
|
||||||
|
*/
|
||||||
|
private void updateConfirmationRecord(ConsultationRequest request) {
|
||||||
|
try {
|
||||||
|
LambdaQueryWrapper<ConsultationConfirmation> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(ConsultationConfirmation::getConsultationRequestId, request.getId());
|
||||||
|
ConsultationConfirmation confirmation = consultationConfirmationMapper.selectOne(wrapper);
|
||||||
|
|
||||||
|
if (confirmation != null) {
|
||||||
|
confirmation.setAllSignedDate(new Date());
|
||||||
|
confirmation.setConsultationStatus(ConsultationStatusEnum.SIGNED.getCode()); // 使用枚举
|
||||||
|
|
||||||
|
// 🎯 设置签名医生信息(最后一个签名的医生 = 当前操作的医生)
|
||||||
|
confirmation.setSignature(request.getSignature());
|
||||||
|
confirmation.setSignaturePhysicianId(request.getSignaturePhysicianId());
|
||||||
|
confirmation.setSignatureDate(request.getSignatureDate());
|
||||||
|
|
||||||
|
// 更新医生列表JSON(包含签名时间)
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> invitedWrapper = new LambdaQueryWrapper<>();
|
||||||
|
invitedWrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.ge(ConsultationInvited::getInvitedStatus, 1);
|
||||||
|
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(invitedWrapper);
|
||||||
|
|
||||||
|
// 更新汇总的会诊意见
|
||||||
|
String allOpinions = invitedList.stream()
|
||||||
|
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
|
||||||
|
.map(ConsultationInvited::getConfirmOpinion)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
confirmation.setConsultationOpinion(allOpinions);
|
||||||
|
|
||||||
|
List<Map<String, Object>> physicians = invitedList.stream()
|
||||||
|
.map(inv -> {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("physicianId", inv.getInvitedPhysicianId());
|
||||||
|
map.put("physicianName", inv.getInvitedPhysicianName());
|
||||||
|
map.put("deptName", inv.getInvitedDepartmentName());
|
||||||
|
map.put("confirmTime", inv.getConfirmTime());
|
||||||
|
map.put("signatureTime", inv.getSignatureTime());
|
||||||
|
return map;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
confirmation.setConfirmingPhysicians(JSON.toJSONString(physicians));
|
||||||
|
consultationConfirmationMapper.updateById(confirmation);
|
||||||
|
|
||||||
|
log.info("更新会诊确认记录成功,所有医生都已签名");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新确认记录失败", e);
|
||||||
|
throw new RuntimeException("更新确认记录失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConsultationConfirmationDto getConfirmationDetail(String consultationId) {
|
||||||
|
try {
|
||||||
|
if (!StringUtils.hasText(consultationId)) {
|
||||||
|
throw new IllegalArgumentException("会诊申请单号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询会诊申请
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.eq(ConsultationRequest::getConsultationId, consultationId);
|
||||||
|
ConsultationRequest request = consultationRequestMapper.selectOne(requestWrapper);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
throw new IllegalArgumentException("会诊申请不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为DTO
|
||||||
|
return convertToConfirmationDto(request);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取会诊确认详情失败", e);
|
||||||
|
throw new RuntimeException("获取会诊确认详情失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为会诊确认DTO
|
||||||
|
*/
|
||||||
|
private ConsultationConfirmationDto convertToConfirmationDto(ConsultationRequest request) {
|
||||||
|
ConsultationConfirmationDto dto = new ConsultationConfirmationDto();
|
||||||
|
|
||||||
|
// 基本信息
|
||||||
|
dto.setConsultationRequestId(request.getId());
|
||||||
|
dto.setConsultationId(request.getConsultationId());
|
||||||
|
dto.setConsultationStatus(request.getConsultationStatus());
|
||||||
|
|
||||||
|
// 患者信息
|
||||||
|
dto.setPatientName(request.getPatientName());
|
||||||
|
dto.setGenderText(request.getGenderEnum() != null && request.getGenderEnum() == 1 ? "男" : "女");
|
||||||
|
dto.setAge(request.getAge());
|
||||||
|
dto.setPatientIdentifierNo(request.getPatientIdentifierNo());
|
||||||
|
|
||||||
|
// 申请信息
|
||||||
|
dto.setApplyDept(request.getDepartment());
|
||||||
|
dto.setApplyDoctor(request.getRequestingPhysician());
|
||||||
|
dto.setApplyTime(request.getConsultationRequestDate());
|
||||||
|
|
||||||
|
// 会诊信息
|
||||||
|
dto.setConsultationDate(request.getConsultationDate());
|
||||||
|
dto.setUrgent("2".equals(request.getConsultationUrgency()));
|
||||||
|
dto.setInvitedObject(request.getInvitedObject());
|
||||||
|
dto.setConsultationPurpose(request.getConsultationPurpose());
|
||||||
|
dto.setProvisionalDiagnosis(request.getProvisionalDiagnosis());
|
||||||
|
|
||||||
|
// 提交信息
|
||||||
|
dto.setSubmittingPhysician(request.getConfirmingPhysician());
|
||||||
|
dto.setSubmittingTime(request.getConfirmingDate());
|
||||||
|
|
||||||
|
// 查询确认记录
|
||||||
|
LambdaQueryWrapper<ConsultationConfirmation> confirmWrapper = new LambdaQueryWrapper<>();
|
||||||
|
confirmWrapper.eq(ConsultationConfirmation::getConsultationRequestId, request.getId());
|
||||||
|
ConsultationConfirmation confirmation = consultationConfirmationMapper.selectOne(confirmWrapper);
|
||||||
|
|
||||||
|
if (confirmation != null) {
|
||||||
|
dto.setId(confirmation.getId());
|
||||||
|
dto.setConfirmingPhysicianId(confirmation.getConfirmingPhysicianId());
|
||||||
|
dto.setConfirmingPhysicianName(confirmation.getConfirmingPhysicianName());
|
||||||
|
dto.setConfirmingDeptId(confirmation.getConfirmingDeptId());
|
||||||
|
dto.setConfirmingDeptName(confirmation.getConfirmingDeptName());
|
||||||
|
dto.setConfirmingDate(confirmation.getConfirmingDate());
|
||||||
|
dto.setConsultationOpinion(confirmation.getConsultationOpinion());
|
||||||
|
dto.setConfirmingPhysician(confirmation.getConfirmingPhysicians());
|
||||||
|
dto.setSignature(confirmation.getSignature());
|
||||||
|
dto.setSignaturePhysicianId(confirmation.getSignaturePhysicianId());
|
||||||
|
dto.setSignatureDate(confirmation.getSignatureDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ConsultationOpinionDto> getConsultationOpinions(String consultationId) {
|
||||||
|
try {
|
||||||
|
if (!StringUtils.hasText(consultationId)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询会诊申请
|
||||||
|
LambdaQueryWrapper<ConsultationRequest> requestWrapper = new LambdaQueryWrapper<>();
|
||||||
|
requestWrapper.eq(ConsultationRequest::getConsultationId, consultationId);
|
||||||
|
ConsultationRequest request = consultationRequestMapper.selectOne(requestWrapper);
|
||||||
|
|
||||||
|
if (request == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查询所有已确认的医生及其意见
|
||||||
|
LambdaQueryWrapper<ConsultationInvited> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(ConsultationInvited::getConsultationRequestId, request.getId())
|
||||||
|
.ge(ConsultationInvited::getInvitedStatus, ConsultationStatusEnum.CONFIRMED.getCode()) // 已确认或已签名
|
||||||
|
.orderByAsc(ConsultationInvited::getConfirmTime);
|
||||||
|
|
||||||
|
List<ConsultationInvited> invitedList = consultationInvitedMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
// 3. 转换为DTO
|
||||||
|
return invitedList.stream()
|
||||||
|
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
|
||||||
|
.map(inv -> {
|
||||||
|
ConsultationOpinionDto dto = new ConsultationOpinionDto();
|
||||||
|
dto.setPhysicianId(inv.getInvitedPhysicianId());
|
||||||
|
dto.setPhysicianName(inv.getInvitedPhysicianName());
|
||||||
|
dto.setDeptId(inv.getInvitedDepartmentId());
|
||||||
|
dto.setDeptName(inv.getInvitedDepartmentName());
|
||||||
|
dto.setOpinion(inv.getConfirmOpinion()); // 已包含"科室-医生:"前缀
|
||||||
|
dto.setConfirmTime(inv.getConfirmTime());
|
||||||
|
dto.setSignatureTime(inv.getSignatureTime());
|
||||||
|
dto.setStatus(inv.getInvitedStatus());
|
||||||
|
dto.setIsSigned(inv.getInvitedStatus() != null && inv.getInvitedStatus() >= ConsultationStatusEnum.SIGNED.getCode());
|
||||||
|
return dto;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取会诊意见失败", e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.openhis.web.consultation.controller;
|
|||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
import com.openhis.web.consultation.appservice.IConsultationAppService;
|
import com.openhis.web.consultation.appservice.IConsultationAppService;
|
||||||
import com.openhis.web.consultation.dto.ConsultationActivityDto;
|
import com.openhis.web.consultation.dto.ConsultationActivityDto;
|
||||||
|
import com.openhis.web.consultation.dto.ConsultationConfirmationDto;
|
||||||
|
import com.openhis.web.consultation.dto.ConsultationOpinionDto;
|
||||||
import com.openhis.web.consultation.dto.ConsultationRequestDto;
|
import com.openhis.web.consultation.dto.ConsultationRequestDto;
|
||||||
import com.openhis.web.consultation.dto.DepartmentTreeDto;
|
import com.openhis.web.consultation.dto.DepartmentTreeDto;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
@@ -47,6 +49,41 @@ public class ConsultationController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会诊申请列表(支持多条件查询)
|
||||||
|
*/
|
||||||
|
@ApiOperation("查询会诊申请列表")
|
||||||
|
@PostMapping("/query")
|
||||||
|
public R<List<ConsultationRequestDto>> queryConsultationList(@RequestBody ConsultationRequestDto dto) {
|
||||||
|
try {
|
||||||
|
List<ConsultationRequestDto> list = consultationAppService.queryConsultationList(dto);
|
||||||
|
return R.ok(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询会诊申请列表失败", e);
|
||||||
|
return R.fail("查询会诊申请列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询会诊申请列表(支持多条件查询)
|
||||||
|
*/
|
||||||
|
@ApiOperation("分页查询会诊申请列表")
|
||||||
|
@PostMapping("/queryPage")
|
||||||
|
public R<com.baomidou.mybatisplus.extension.plugins.pagination.Page<ConsultationRequestDto>> queryConsultationListPage(
|
||||||
|
@RequestBody ConsultationRequestDto dto,
|
||||||
|
@ApiParam("页码") @RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||||
|
@ApiParam("每页大小") @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
|
||||||
|
try {
|
||||||
|
log.info("分页查询会诊申请列表,pageNum: {}, pageSize: {}", pageNum, pageSize);
|
||||||
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<ConsultationRequestDto> page =
|
||||||
|
consultationAppService.queryConsultationListPage(dto, pageNum, pageSize);
|
||||||
|
return R.ok(page);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分页查询会诊申请列表失败", e);
|
||||||
|
return R.fail("分页查询会诊申请列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存会诊申请
|
* 保存会诊申请
|
||||||
*/
|
*/
|
||||||
@@ -171,5 +208,99 @@ public class ConsultationController {
|
|||||||
return R.fail("获取会诊项目列表失败: " + e.getMessage());
|
return R.fail("获取会诊项目列表失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 会诊确认相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待确认的会诊列表(当前医生被邀请的会诊)
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取待确认的会诊列表")
|
||||||
|
@GetMapping("/confirmation/pending")
|
||||||
|
public R<List<ConsultationConfirmationDto>> getPendingConfirmationList() {
|
||||||
|
try {
|
||||||
|
List<ConsultationConfirmationDto> list = consultationAppService.getPendingConfirmationList();
|
||||||
|
return R.ok(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取待确认会诊列表失败", e);
|
||||||
|
return R.fail("获取待确认会诊列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认会诊
|
||||||
|
*/
|
||||||
|
@ApiOperation("确认会诊")
|
||||||
|
@PostMapping("/confirmation/confirm")
|
||||||
|
public R<String> confirmConsultation(@RequestBody ConsultationConfirmationDto dto) {
|
||||||
|
try {
|
||||||
|
Boolean result = consultationAppService.confirmConsultation(dto);
|
||||||
|
return result ? R.ok("确认成功") : R.fail("确认失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("确认会诊失败", e);
|
||||||
|
return R.fail("确认会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消确认会诊
|
||||||
|
*/
|
||||||
|
@ApiOperation("取消确认会诊")
|
||||||
|
@PostMapping("/confirmation/cancelConfirm")
|
||||||
|
public R<String> cancelConfirmation(@ApiParam("会诊申请单号") @RequestParam String consultationId) {
|
||||||
|
try {
|
||||||
|
Boolean result = consultationAppService.cancelConfirmation(consultationId);
|
||||||
|
return result ? R.ok("取消确认成功") : R.fail("取消确认失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("取消确认会诊失败", e);
|
||||||
|
return R.fail("取消确认会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名会诊
|
||||||
|
*/
|
||||||
|
@ApiOperation("签名会诊")
|
||||||
|
@PostMapping("/confirmation/sign")
|
||||||
|
public R<String> signConsultation(@ApiParam("会诊申请单号") @RequestParam String consultationId) {
|
||||||
|
try {
|
||||||
|
Boolean result = consultationAppService.signConsultation(consultationId);
|
||||||
|
return result ? R.ok("签名成功") : R.fail("签名失败");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("签名会诊失败", e);
|
||||||
|
return R.fail("签名会诊失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会诊确认详情
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取会诊确认详情")
|
||||||
|
@GetMapping("/confirmation/detail")
|
||||||
|
public R<ConsultationConfirmationDto> getConfirmationDetail(
|
||||||
|
@ApiParam("会诊申请单号") @RequestParam String consultationId) {
|
||||||
|
try {
|
||||||
|
ConsultationConfirmationDto detail = consultationAppService.getConfirmationDetail(consultationId);
|
||||||
|
return R.ok(detail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取会诊确认详情失败", e);
|
||||||
|
return R.fail("获取会诊确认详情失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会诊意见列表
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取会诊意见列表")
|
||||||
|
@GetMapping("/confirmation/opinions")
|
||||||
|
public R<List<ConsultationOpinionDto>> getConsultationOpinions(
|
||||||
|
@ApiParam("会诊申请单号") @RequestParam String consultationId) {
|
||||||
|
try {
|
||||||
|
List<ConsultationOpinionDto> opinions = consultationAppService.getConsultationOpinions(consultationId);
|
||||||
|
return R.ok(opinions);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取会诊意见列表失败", e);
|
||||||
|
return R.fail("获取会诊意见列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.openhis.web.consultation.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊确认表实体类
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-02-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("consultation_confirmation")
|
||||||
|
public class ConsultationConfirmation implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊申请ID(外键:consultation_request.id)
|
||||||
|
*/
|
||||||
|
@TableField("consultation_request_id")
|
||||||
|
private Long consultationRequestId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊申请单号
|
||||||
|
*/
|
||||||
|
@TableField("consultation_id")
|
||||||
|
private String consultationId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认会诊的医生ID(操作【确认】按钮的当前医生ID)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_physician_id")
|
||||||
|
private Long confirmingPhysicianId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认会诊的医生姓名(操作【确认】按钮的当前医生姓名)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_physician_name")
|
||||||
|
private String confirmingPhysicianName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代表科室ID(操作【确认】按钮的当前开单科室ID)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_dept_id")
|
||||||
|
private Long confirmingDeptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代表科室名称(操作【确认】按钮的当前开单科室)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_dept_name")
|
||||||
|
private String confirmingDeptName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认会诊的日期(操作【确认】按钮当前系统时间)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_date")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date confirmingDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊状态:0-取消确认(作废),20-已确认(会诊医生已查看/同意,可写初步意见),30-已签名(已电子签名,意见最终生效),40-已完成(会诊报告已回写,流程关闭)
|
||||||
|
*/
|
||||||
|
@TableField("consultation_status")
|
||||||
|
private Integer consultationStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊意见(最多500字)
|
||||||
|
*/
|
||||||
|
@TableField("consultation_opinion")
|
||||||
|
private String consultationOpinion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊确认参加医师(多个医师信息,JSON格式)
|
||||||
|
*/
|
||||||
|
@TableField("confirming_physicians")
|
||||||
|
private String confirmingPhysicians;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有医生都确认的时间
|
||||||
|
*/
|
||||||
|
@TableField("all_confirmed_date")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date allConfirmedDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有医生都签名的时间
|
||||||
|
*/
|
||||||
|
@TableField("all_signed_date")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date allSignedDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名医生姓名
|
||||||
|
*/
|
||||||
|
@TableField("signature")
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名医生ID
|
||||||
|
*/
|
||||||
|
@TableField("signature_physician_id")
|
||||||
|
private Long signaturePhysicianId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名时间
|
||||||
|
*/
|
||||||
|
@TableField("signature_date")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date signatureDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间(由 Java 代码维护)
|
||||||
|
*/
|
||||||
|
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人
|
||||||
|
*/
|
||||||
|
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人
|
||||||
|
*/
|
||||||
|
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private String updateBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户ID
|
||||||
|
*/
|
||||||
|
@TableField("tenant_id")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除标识:0-未删除,1-已删除
|
||||||
|
*/
|
||||||
|
@TableField("is_deleted")
|
||||||
|
@TableLogic
|
||||||
|
private Integer isDeleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
@TableField("remark")
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,13 @@ public class ConsultationInvited implements Serializable {
|
|||||||
private String invitedPhysicianName;
|
private String invitedPhysicianName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邀请状态(0-待确认,1-已确认,2-已拒绝)
|
* 邀请状态(使用ConsultationStatusEnum)
|
||||||
|
* 0-新开(待确认)
|
||||||
|
* 10-已提交(待确认)
|
||||||
|
* 20-已确认
|
||||||
|
* 30-已签名
|
||||||
|
* 40-已完成
|
||||||
|
* 50-已取消(已拒绝)
|
||||||
*/
|
*/
|
||||||
@TableField("invited_status")
|
@TableField("invited_status")
|
||||||
private Integer invitedStatus;
|
private Integer invitedStatus;
|
||||||
@@ -69,11 +75,18 @@ public class ConsultationInvited implements Serializable {
|
|||||||
private Date confirmTime;
|
private Date confirmTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认意见
|
* 确认意见(格式:科室-医生:意见内容)
|
||||||
*/
|
*/
|
||||||
@TableField("confirm_opinion")
|
@TableField("confirm_opinion")
|
||||||
private String confirmOpinion;
|
private String confirmOpinion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名时间
|
||||||
|
*/
|
||||||
|
@TableField("signature_time")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date signatureTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -216,6 +216,24 @@ public class ConsultationRequest implements Serializable {
|
|||||||
@TableField("cancel_reason")
|
@TableField("cancel_reason")
|
||||||
private String cancelReason;
|
private String cancelReason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已确认的医生数量
|
||||||
|
*/
|
||||||
|
@TableField("confirmed_count")
|
||||||
|
private Integer confirmedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已签名的医生数量
|
||||||
|
*/
|
||||||
|
@TableField("signed_count")
|
||||||
|
private Integer signedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被邀请的医生总数
|
||||||
|
*/
|
||||||
|
@TableField("invited_count")
|
||||||
|
private Integer invitedCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.openhis.web.consultation.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊确认DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-02-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("会诊确认DTO")
|
||||||
|
public class ConsultationConfirmationDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("主键ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊申请ID")
|
||||||
|
private Long consultationRequestId;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊申请单号")
|
||||||
|
private String consultationId;
|
||||||
|
|
||||||
|
@ApiModelProperty("确认医生ID")
|
||||||
|
private Long confirmingPhysicianId;
|
||||||
|
|
||||||
|
@ApiModelProperty("确认医生姓名")
|
||||||
|
private String confirmingPhysicianName;
|
||||||
|
|
||||||
|
@ApiModelProperty("代表科室ID")
|
||||||
|
private Long confirmingDeptId;
|
||||||
|
|
||||||
|
@ApiModelProperty("代表科室名称")
|
||||||
|
private String confirmingDeptName;
|
||||||
|
|
||||||
|
@ApiModelProperty("确认日期")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date confirmingDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊状态:0-取消确认,20-已确认,30-已签名,40-已完成")
|
||||||
|
private Integer consultationStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊意见")
|
||||||
|
private String consultationOpinion;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊确认参加医师")
|
||||||
|
private String confirmingPhysician;
|
||||||
|
|
||||||
|
@ApiModelProperty("签名医生姓名")
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
@ApiModelProperty("签名医生ID")
|
||||||
|
private Long signaturePhysicianId;
|
||||||
|
|
||||||
|
@ApiModelProperty("签名时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date signatureDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
// ========== 扩展字段(用于前端显示) ==========
|
||||||
|
|
||||||
|
@ApiModelProperty("患者姓名")
|
||||||
|
private String patientName;
|
||||||
|
|
||||||
|
@ApiModelProperty("性别")
|
||||||
|
private String genderText;
|
||||||
|
|
||||||
|
@ApiModelProperty("年龄")
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
|
@ApiModelProperty("就诊卡号")
|
||||||
|
private String patientIdentifierNo;
|
||||||
|
|
||||||
|
@ApiModelProperty("申请科室")
|
||||||
|
private String applyDept;
|
||||||
|
|
||||||
|
@ApiModelProperty("申请医师")
|
||||||
|
private String applyDoctor;
|
||||||
|
|
||||||
|
@ApiModelProperty("申请时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date applyTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date consultationDate;
|
||||||
|
|
||||||
|
@ApiModelProperty("紧急标志")
|
||||||
|
private Boolean urgent;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊邀请对象")
|
||||||
|
private String invitedObject;
|
||||||
|
|
||||||
|
@ApiModelProperty("病史及目的")
|
||||||
|
private String consultationPurpose;
|
||||||
|
|
||||||
|
@ApiModelProperty("门诊诊断")
|
||||||
|
private String provisionalDiagnosis;
|
||||||
|
|
||||||
|
@ApiModelProperty("提交医生")
|
||||||
|
private String submittingPhysician;
|
||||||
|
|
||||||
|
@ApiModelProperty("提交时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date submittingTime;
|
||||||
|
|
||||||
|
// ========== 当前医生的个人状态 ==========
|
||||||
|
|
||||||
|
@ApiModelProperty("当前医生的邀请状态:0-待确认,1-已确认,2-已拒绝,3-已签名")
|
||||||
|
private Integer myInvitedStatus;
|
||||||
|
|
||||||
|
@ApiModelProperty("当前医生的确认时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date myConfirmTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("当前医生的签名时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date mySignatureTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.openhis.web.consultation.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊意见DTO
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
* @date 2026-02-09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel("会诊意见DTO")
|
||||||
|
public class ConsultationOpinionDto implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ApiModelProperty("医生ID")
|
||||||
|
private Long physicianId;
|
||||||
|
|
||||||
|
@ApiModelProperty("医生姓名")
|
||||||
|
private String physicianName;
|
||||||
|
|
||||||
|
@ApiModelProperty("科室ID")
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
@ApiModelProperty("科室名称")
|
||||||
|
private String deptName;
|
||||||
|
|
||||||
|
@ApiModelProperty("会诊意见(已包含科室-医生前缀)")
|
||||||
|
private String opinion;
|
||||||
|
|
||||||
|
@ApiModelProperty("确认时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date confirmTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("签名时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date signatureTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态:0-待确认,1-已确认,2-已拒绝,3-已签名")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否已签名")
|
||||||
|
private Boolean isSigned;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -185,6 +185,34 @@ public class ConsultationRequestDto implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date myConfirmTime;
|
private Date myConfirmTime;
|
||||||
|
|
||||||
|
// ==================== 会诊记录相关字段(会诊完成后显示) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会诊邀请参加医师(已签名医生列表,格式:科室-姓名、科室-姓名)
|
||||||
|
*/
|
||||||
|
private String invitedPhysiciansText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属医生(第一个签名的医生)
|
||||||
|
*/
|
||||||
|
private String attendingPhysician;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代表科室(第一个签名医生的科室)
|
||||||
|
*/
|
||||||
|
private String representDepartment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名医生(第一个签名的医生)
|
||||||
|
*/
|
||||||
|
private String signPhysician;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名时间(第一个签名医生的签名时间)
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date signTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邀请对象DTO
|
* 邀请对象DTO
|
||||||
@@ -38,6 +39,20 @@ public class InvitedObjectDto implements Serializable {
|
|||||||
* 医生姓名
|
* 医生姓名
|
||||||
*/
|
*/
|
||||||
private String physicianName;
|
private String physicianName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邀请状态:0=待确认,1=已确认,3=已签名
|
||||||
|
*/
|
||||||
|
private Integer invitedStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认时间
|
||||||
|
*/
|
||||||
|
private Date confirmTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名时间
|
||||||
|
*/
|
||||||
|
private Date signatureTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public enum ConsultationStatusEnum {
|
|||||||
* 判断是否可以编辑
|
* 判断是否可以编辑
|
||||||
*/
|
*/
|
||||||
public boolean canEdit() {
|
public boolean canEdit() {
|
||||||
return this == NEW || this == SUBMITTED;
|
return this == NEW;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,3 +83,6 @@ public enum ConsultationStatusEnum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,3 +49,8 @@ public enum ConsultationUrgencyEnum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user