Compare commits
106 Commits
ae96bbd0bb
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 669d669422 | |||
| 98fe9f3301 | |||
| 6f7d723c6b | |||
| 0a08088ada | |||
|
|
48309fcaa4 | ||
|
|
28160e082c | ||
|
|
29ecfd90f2 | ||
| f690b78b18 | |||
| 6f71c678bd | |||
| 1c781c1224 | |||
|
|
638f853af6 | ||
|
|
96a8f75aa1 | ||
| 15a6445e26 | |||
| 0e4b0ad6fd | |||
|
|
7da461a9cb | ||
| b0040bcd48 | |||
| fa5394cc35 | |||
| 81cc8b08a0 | |||
| 0cecf3bcad | |||
|
|
b8d7e3cdf1 | ||
|
|
df2a4c1694 | ||
|
|
a6a4e0ed58 | ||
|
|
ba31371b6f | ||
| 9bd5caaa1b | |||
| 164e4a4b75 | |||
| 4f0cc1a0c4 | |||
|
|
6dedb92b54 | ||
|
|
0f0dc70c7e | ||
|
|
acfce391dc | ||
|
|
b0f2eabf6b | ||
|
|
c5db404290 | ||
|
|
c4c3073be0 | ||
| 41494ebf7c | |||
|
|
4de4d9099e | ||
| 497af01f9b | |||
| ffc1f29b80 | |||
| 86bca03b04 | |||
| 11c2758289 | |||
| 802f845231 | |||
|
|
ea5215a1b0 | ||
| a9fb093d9c | |||
|
|
f4bf064f08 | ||
|
|
4dd824d296 | ||
| 3ab6c2d424 | |||
| 12b2bf255c | |||
|
|
c878dc19d7 | ||
|
|
c1efd84332 | ||
| 1616f66fc4 | |||
|
|
2df1ed645f | ||
|
|
4f7fc1c09a | ||
| bd873f81d2 | |||
| 1975fda73c | |||
| ffce6f81c3 | |||
| ca043de624 | |||
| 054b51c63d | |||
| 5cf2dd165c | |||
| 27b094744c | |||
|
|
55e3533600 | ||
|
|
1522183432 | ||
| 6382741b71 | |||
| 16c854d55f | |||
| 73617e1b0f | |||
| abd5bd9f2f | |||
|
|
9000d66c0c | ||
|
|
61be9ff552 | ||
|
|
9408cf6c2d | ||
|
|
66c70a2b4a | ||
| f6d9321f95 | |||
| ccff9a7246 | |||
| 2884f610f5 | |||
|
|
035738f990 | ||
|
|
d0c6f57f6b | ||
|
|
58c1e02415 | ||
|
|
1e459b8883 | ||
|
|
0d57e984a6 | ||
| 4450e3cc50 | |||
|
|
902ee0587e | ||
| 49550fcc2e | |||
|
|
1dd7ee3428 | ||
|
|
8dff5d466a | ||
|
|
19ada4ace9 | ||
| c92ff38133 | |||
| 1c07108e58 | |||
|
|
34dd969cb4 | ||
| a0b546266d | |||
|
|
fc9ce6241e | ||
|
|
5187ff1ae3 | ||
|
|
73b1d01044 | ||
|
|
b88ad89146 | ||
|
|
de8039c513 | ||
| 3464153d93 | |||
| 6b868e378f | |||
| f6403fa059 | |||
|
|
bc92b9aa62 | ||
|
|
46145ff636 | ||
|
|
3ad32fac9f | ||
| d1223aec07 | |||
| 649f7bcf5b | |||
| a3dce8de60 | |||
| f81dd54f0c | |||
| 803e4d0bb5 | |||
| deebcde41f | |||
| 095c43bbf3 | |||
| aa3beb848b | |||
|
|
f11b7380a4 | ||
| da17b2b89c |
29
.qwen/agents/full-stack-developer.md
Normal file
29
.qwen/agents/full-stack-developer.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: full-stack-developer
|
||||||
|
description: Use this agent when you need comprehensive full-stack development assistance including frontend, backend, database design, API integration, deployment planning, and architectural decisions. This agent excels at analyzing complex technical requirements, designing scalable solutions, implementing clean code across multiple technologies, and providing expert guidance on best practices for modern web applications.
|
||||||
|
color: Blue
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an elite full-stack software engineer with extensive experience across all layers of modern web application development. You possess deep expertise in frontend technologies (React, Vue, Angular, HTML/CSS, JavaScript/TypeScript), backend systems (Node.js, Python, Java, .NET, Ruby), databases (SQL and NoSQL), cloud platforms (AWS, Azure, GCP), and DevOps practices.
|
||||||
|
|
||||||
|
Your primary responsibilities include:
|
||||||
|
- Analyzing complex technical requirements and proposing optimal architectural solutions
|
||||||
|
- Writing clean, efficient, maintainable code across frontend and backend systems
|
||||||
|
- Designing robust APIs and data models
|
||||||
|
- Optimizing performance and ensuring security best practices
|
||||||
|
- Providing guidance on scalability, testing, and deployment strategies
|
||||||
|
- Troubleshooting complex issues spanning multiple technology stacks
|
||||||
|
|
||||||
|
When working on projects, you will:
|
||||||
|
1. First understand the complete scope and requirements before proposing solutions
|
||||||
|
2. Consider scalability, maintainability, and security implications of your designs
|
||||||
|
3. Follow industry best practices for code organization, documentation, and testing
|
||||||
|
4. Suggest appropriate technologies based on project requirements and constraints
|
||||||
|
5. Provide implementation details with proper error handling and edge case considerations
|
||||||
|
6. Recommend optimization strategies for performance and resource utilization
|
||||||
|
|
||||||
|
For frontend development, focus on responsive design, accessibility, state management, and user experience. For backend work, emphasize proper architecture patterns, database design, authentication/authorization, and API design principles. When addressing databases, consider normalization, indexing, query optimization, and data consistency.
|
||||||
|
|
||||||
|
Always prioritize clean code principles, proper separation of concerns, and modular design. When uncertain about requirements, ask clarifying questions to ensure your solution meets the actual needs. Provide code examples that demonstrate best practices and include comments where necessary for understanding.
|
||||||
|
|
||||||
|
In your responses, balance technical depth with practical applicability. Consider trade-offs between different approaches and explain your recommendations. When reviewing existing code, identify potential improvements related to performance, security, maintainability, and adherence to best practices.
|
||||||
32
.qwen/agents/his-architect-developer.md
Normal file
32
.qwen/agents/his-architect-developer.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: his-architect-developer
|
||||||
|
description: Use this agent when designing, developing, reviewing, or troubleshooting Hospital Information System (HIS) applications. This agent specializes in full-stack development for healthcare systems including database design, backend APIs, frontend interfaces, security compliance, and integration with medical devices or third-party systems.
|
||||||
|
color: Blue
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an elite Healthcare Information System (HIS) Development Architect and Full-Stack Engineer with extensive experience in designing and implementing comprehensive hospital management solutions. You possess deep expertise in healthcare software architecture, regulatory compliance (HIPAA, FDA, etc.), medical data standards (HL7, FHIR), and secure system integration.
|
||||||
|
|
||||||
|
Your responsibilities include:
|
||||||
|
- Designing scalable, secure, and compliant HIS architectures
|
||||||
|
- Developing robust backend services and APIs
|
||||||
|
- Creating intuitive frontend interfaces for healthcare professionals
|
||||||
|
- Ensuring patient data security and privacy compliance
|
||||||
|
- Integrating with medical devices and external healthcare systems
|
||||||
|
- Optimizing system performance for high-availability environments
|
||||||
|
- Troubleshooting complex technical issues in healthcare IT infrastructure
|
||||||
|
|
||||||
|
When working on HIS projects, you will:
|
||||||
|
1. Prioritize patient safety and data security above all other considerations
|
||||||
|
2. Follow healthcare industry standards and regulations (HIPAA, HITECH, FDA guidelines)
|
||||||
|
3. Implement proper audit trails and logging for all patient-related operations
|
||||||
|
4. Design fail-safe mechanisms and disaster recovery procedures
|
||||||
|
5. Ensure accessibility compliance for users with varying technical expertise
|
||||||
|
6. Plan for high availability and minimal downtime in critical systems
|
||||||
|
|
||||||
|
For database design, focus on normalized schemas that support medical record integrity, implement proper indexing for fast queries, and ensure backup/recovery procedures meet healthcare requirements. When developing APIs, follow RESTful principles while incorporating OAuth 2.0 or similar authentication methods suitable for healthcare environments.
|
||||||
|
|
||||||
|
For frontend development, prioritize usability for healthcare workers who may be operating under stress, ensuring clear workflows and minimizing cognitive load. Implement responsive designs that work across various devices commonly used in healthcare settings.
|
||||||
|
|
||||||
|
Always consider scalability requirements for growing healthcare institutions and plan for future expansion. When troubleshooting, approach problems systematically considering the potential impact on patient care.
|
||||||
|
|
||||||
|
In your responses, provide detailed explanations of your architectural decisions, code implementations, and recommendations. Include relevant healthcare industry best practices and explain how your solutions address specific regulatory requirements.
|
||||||
33
.qwen/agents/his-developer-architect.md
Normal file
33
.qwen/agents/his-developer-architect.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
name: his-developer-architect
|
||||||
|
description: Use this agent when developing or architecting Hospital Information System (HIS) solutions using Vue3, Spring Boot, and MyBatis technologies. This agent specializes in healthcare system development, understanding medical workflows, patient management systems, and hospital operational processes. Ideal for designing secure, scalable, and compliant healthcare applications.
|
||||||
|
color: Blue
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an elite Healthcare Information System (HIS) developer and architect with deep expertise in Vue3, Spring Boot, and MyBatis technologies. You specialize in building robust, secure, and scalable hospital management systems that handle critical healthcare operations including patient records, medical workflows, billing, pharmacy management, and administrative processes.
|
||||||
|
|
||||||
|
Your responsibilities include:
|
||||||
|
- Designing and implementing full-stack HIS solutions using Vue3 for modern, responsive frontends and Spring Boot with MyBatis for secure, efficient backends
|
||||||
|
- Ensuring compliance with healthcare industry standards such as HIPAA, HL7, FHIR, and local health data protection regulations
|
||||||
|
- Creating secure authentication and authorization systems for healthcare staff with role-based access controls
|
||||||
|
- Optimizing database designs for handling large volumes of sensitive patient data efficiently
|
||||||
|
- Implementing audit trails and logging systems required for healthcare environments
|
||||||
|
- Building integration capabilities between different hospital systems and external healthcare providers
|
||||||
|
|
||||||
|
Technical Guidelines:
|
||||||
|
- Follow Vue3 best practices using Composition API, TypeScript, and state management with Pinia
|
||||||
|
- Implement Spring Boot microservices architecture with proper security configurations (Spring Security)
|
||||||
|
- Use MyBatis effectively with proper transaction management and connection pooling
|
||||||
|
- Apply healthcare-specific design patterns and architectural principles
|
||||||
|
- Prioritize data integrity, security, and system reliability over performance optimizations when there's a conflict
|
||||||
|
- Implement comprehensive error handling and logging for healthcare regulatory compliance
|
||||||
|
|
||||||
|
When designing solutions, consider:
|
||||||
|
- Patient privacy and data security requirements
|
||||||
|
- High availability and disaster recovery needs for critical healthcare systems
|
||||||
|
- Scalability to handle varying loads during peak times
|
||||||
|
- Integration with existing hospital infrastructure and legacy systems
|
||||||
|
- User experience for healthcare professionals who need quick, reliable access to information
|
||||||
|
- Regulatory compliance and audit requirements specific to healthcare systems
|
||||||
|
|
||||||
|
You will provide detailed technical recommendations, code implementations, architectural diagrams, and best practices tailored specifically to healthcare information systems. Always prioritize patient safety and data security in your solutions.
|
||||||
6
.qwen/settings.json
Normal file
6
.qwen/settings.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tools": {
|
||||||
|
"approvalMode": "yolo"
|
||||||
|
},
|
||||||
|
"$version": 2
|
||||||
|
}
|
||||||
376
CODEBUDDY.md
Normal file
376
CODEBUDDY.md
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
# 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/`
|
||||||
223
MybastisColumnsHandler_optimization_guide.md
Normal file
223
MybastisColumnsHandler_optimization_guide.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# 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
Normal file
184
QWEN.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# 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
|
||||||
202
audit_field_best_practices.md
Normal file
202
audit_field_best_practices.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
# 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 系统中的所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误。
|
||||||
113
audit_field_solution.md
Normal file
113
audit_field_solution.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# 关于数据库审计字段(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的自动填充机制来设置审计字段
|
||||||
|
- 确保所有实体类继承基础实体类
|
||||||
|
- 在必要时才考虑移除数据库约束
|
||||||
38
debug_api_return.md
Normal file
38
debug_api_return.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# 检查后端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注解或其他序列化配置问题
|
||||||
290
deep_dive_autofill_solution.md
Normal file
290
deep_dive_autofill_solution.md
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# 深度排查 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
通过这些方案,应该能够解决自动填充不生效的问题。
|
||||||
143
diagnose_autofill_issue.md
Normal file
143
diagnose_autofill_issue.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# 诊断 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);
|
||||||
|
|
||||||
|
// ... 其他代码
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
通过以上步骤,应该能够诊断并解决自动填充不工作的问题。
|
||||||
145
enhanced_MybastisColumnsHandler.java
Normal file
145
enhanced_MybastisColumnsHandler.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
fragment.java
Normal file
13
fragment.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
10
md/test.html
10
md/test.html
@@ -1,10 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>测试合并11111</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
273
md/需求/100-门诊手术中临时医嘱生成界面PRD_2026-1-23.md
Normal file
273
md/需求/100-门诊手术中临时医嘱生成界面PRD_2026-1-23.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
## 门诊手术中临时医嘱生成界面PRD文档
|
||||||
|
|
||||||
|
### 一、页面概述
|
||||||
|
|
||||||
|
**页面名称**:门诊手术中临时医嘱生成界面
|
||||||
|
**页面目标**:帮助麻醉医师在手术过程中快速生成临时医嘱,完成药品计费引用、医嘱预览和电子签名确认的全流程操作
|
||||||
|
**适用场景**:门诊手术过程中需要追加药品医嘱时使用
|
||||||
|
**页面类型**:表单页+数据展示页
|
||||||
|
|
||||||
|
**核心功能**:
|
||||||
|
|
||||||
|
1. 患者手术信息展示
|
||||||
|
2. 已引用计费药品列表展示与汇总
|
||||||
|
3. 临时医嘱预览与编辑功能
|
||||||
|
4. 医师电子签名确认流程
|
||||||
|
5. 数据刷新与退出操作
|
||||||
|
**用户价值**:简化手术中医嘱生成流程,确保医嘱准确性,实现无纸化操作,提高手术室工作效率
|
||||||
|
原型图地址:https://static.pm-ai.cn/prototype/20260122/e1d7f10b85e9efea543bf47bd6831600/index.html
|
||||||
|
**流程图:**
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start(["Start"]) --> Enter["进入门诊手术中临时医嘱生成界面"]
|
||||||
|
Enter --> ShowBase["展示患者基本信息"]
|
||||||
|
ShowBase --> ShowQuoted["显示已引用计费药品列表"]
|
||||||
|
ShowQuoted --> ShowPreview["显示医嘱预览表格"]
|
||||||
|
ShowPreview --> UserOp{用户操作}
|
||||||
|
|
||||||
|
UserOp -- "引用计费" --> GetLatest{"获取最新计费药品数据\n获取成功?"}
|
||||||
|
GetLatest -- "否" --> ErrTip1["显示错误提示"]
|
||||||
|
GetLatest -- "是" --> UpdateTable["更新药品表格和汇总"]
|
||||||
|
|
||||||
|
UserOp -- "编辑" --> PopEdit["弹出医嘱编辑表单"]
|
||||||
|
PopEdit --> EditVal{"验证通过?"}
|
||||||
|
EditVal -- "否" --> ErrTip2["返回错误提示"]
|
||||||
|
EditVal -- "是" --> SaveClick{"点击保存?"}
|
||||||
|
SaveClick -- "是" --> GenTemp["生成临时药品医嘱"]
|
||||||
|
SaveClick -- "否" --> UserOp
|
||||||
|
|
||||||
|
GenTemp --> UpdatePreview["更新医嘱预览表格"]
|
||||||
|
UpdatePreview --> UpdateRecord["更新手术记录"]
|
||||||
|
UpdateRecord --> ShowResult["显示生成结果"]
|
||||||
|
|
||||||
|
UserOp -- "一键签名并生成医嘱" --> PopPwd["弹出账户密码输入框"]
|
||||||
|
PopPwd --> PopConfirm{"弹出确认对话框"}
|
||||||
|
PopConfirm -- "否" --> UserOp
|
||||||
|
PopConfirm -- "是" --> GenTemp
|
||||||
|
|
||||||
|
UserOp -- "刷新" --> Reload["重新加载界面数据"]
|
||||||
|
Reload --> ShowQuoted
|
||||||
|
|
||||||
|
UserOp -- "退出" --> ExitConfirm{"确认退出?"}
|
||||||
|
ExitConfirm -- "否" --> UserOp
|
||||||
|
ExitConfirm -- "是" --> ReturnUp["返回上级页面"]
|
||||||
|
|
||||||
|
ErrTip1 --> UserOp
|
||||||
|
ErrTip2 --> PopEdit
|
||||||
|
ShowResult --> UserOp
|
||||||
|
ReturnUp --> End([结束])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 二、整体布局分析
|
||||||
|
|
||||||
|
**页面宽度**:自适应布局
|
||||||
|
|
||||||
|
**要区域划分**:
|
||||||
|
|
||||||
|
1. 顶部信息区(15%):患者基本信息+操作按钮区
|
||||||
|
2. 计费药品展示区(35%):已引用计费药品表格+金额汇总
|
||||||
|
3. 医嘱预览区(35%):待生成医嘱的预览表格
|
||||||
|
4. 签名确认区(15%):医师签名信息+操作按钮
|
||||||
|
**布局特点**:上下分块布局,采用卡片式设计,主要区域间有明确分隔线
|
||||||
|
**响应式要求**:768px以下时患者信息改为纵向排列,操作按钮换行显示
|
||||||
|
|
||||||
|
### 三、页面区域详细描述
|
||||||
|
|
||||||
|
#### 1. 顶部信息区
|
||||||
|
|
||||||
|
**区域位置**:页面顶部
|
||||||
|
**区域尺寸**:高度180px(包含20px内边距)
|
||||||
|
**区域功能**:展示患者基本信息+提供主要操作入口
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **标题栏**:
|
||||||
|
- 元素类型:标题文本
|
||||||
|
- 显示内容:“门诊术中临时医嘱”
|
||||||
|
- 样式特征:白色文字,1.5rem字号,居中显示,渐变蓝色背景
|
||||||
|
- **患者信息卡**:
|
||||||
|
- 元素类型:信息展示区块
|
||||||
|
- 显示内容:患者姓名、就诊卡号、手术单号、科室、医师、角色
|
||||||
|
- 患者:样例值-张三
|
||||||
|
- 就诊卡号:样例值-202507010122
|
||||||
|
- 手术单号:样例值- S202507010135
|
||||||
|
- 科室: 样例值-手术室(OR101)-取值于手术安排的手术间号字段
|
||||||
|
- 医师:样例值-李麻(3015)
|
||||||
|
- 角色:样例值-麻醉医师
|
||||||
|
- 样式特征:半透明白色背景,圆角8px,内部flex布局
|
||||||
|
- **操作按钮组**:
|
||||||
|
- **[刷新按钮]**:
|
||||||
|
- 元素类型:主要操作按钮
|
||||||
|
- 显示内容:↻ 刷新
|
||||||
|
- 交互行为:点击后重新加载当前界面的数据
|
||||||
|
- 样式特征:蓝色渐变背景,悬停有上浮效果
|
||||||
|
- **[引用计费按钮]**:
|
||||||
|
- 元素类型:次要操作按钮
|
||||||
|
- 显示内容:引用计费
|
||||||
|
- 交互行为:点击后拉取当前患者最新计费药品的数据
|
||||||
|
|
||||||
|
#### 2. 计费药品展示区
|
||||||
|
|
||||||
|
**区域位置**:顶部信息区下方
|
||||||
|
**区域尺寸**:高度约420px(包含标题和表格)
|
||||||
|
**区域功能**:展示待生成医嘱的计费药品清单
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **表格标题**:
|
||||||
|
- 显示内容:“一、已引用计费药品(待生成医嘱)”
|
||||||
|
- 样式特征:1.2rem字号,底部边框线
|
||||||
|
- **药品数据表格**:
|
||||||
|
|
||||||
|
**取值于门诊术中计费界面生成的药品计费数据(adm_charge_item(费用项管理)、med_medication_request(药品请求管理)等),具体与系统实际业务数据为主。**
|
||||||
|
|
||||||
|
**(参考)关联字段:adm_charge_item. encounter_id = med_medication_request. encounter_id and –就诊ID**
|
||||||
|
|
||||||
|
**adm_charge_item. service_table = 'med_medication_request' and --记录药品数据**
|
||||||
|
|
||||||
|
**adm_charge_item. bus_no = med_medication_request. bus_no -- adm_charge_item. bus_no的值之前多加了‘CI’**
|
||||||
|
|
||||||
|
- 展示方式:斑马纹表格
|
||||||
|
- 数据字段:
|
||||||
|
- 序号:数字 - 自动生成
|
||||||
|
- 药品名称:文本 - 如"罗哌卡因注射液"
|
||||||
|
- 规格:文本 - 如"10ml"
|
||||||
|
- 数量:数字 - 可编辑
|
||||||
|
- 批号:文本 - 如"L240715"
|
||||||
|
- 单价:数字 - 如"38"
|
||||||
|
- 小计:数字 - 自动计算
|
||||||
|
- 医保:标签 - “甲/乙/自费”
|
||||||
|
- 样式特征:表头浅灰色背景,医保类型有颜色区分(蓝色=医保,绿色=自费)
|
||||||
|
- **金额汇总栏**:
|
||||||
|
- 显示内容:
|
||||||
|
- 医保内金额(蓝色强调)
|
||||||
|
- 自费金额(绿色强调)
|
||||||
|
- 总计金额(红色强调)
|
||||||
|
- 位置:表格底部右对齐
|
||||||
|
|
||||||
|
#### 3. 医嘱预览区
|
||||||
|
|
||||||
|
**区域位置**:计费药品展示区下方
|
||||||
|
**区域尺寸**:高度约420px(包含标题和表格)
|
||||||
|
**区域功能**:展示即将生成的药品医嘱
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
\*生成门诊药品医嘱表相关的数据,满足**计费药品明细 ↔ 药品医嘱** 一一对应的要求。
|
||||||
|
|
||||||
|
可以对照参考:需结合门诊医生站开立药品医嘱时生成的药品医嘱表
|
||||||
|
|
||||||
|
- **表格标题**:
|
||||||
|
- 显示内容:“二、临时医嘱预览(已生成)”
|
||||||
|
- **医嘱表格**:
|
||||||
|
- 展示方式:斑马纹表格
|
||||||
|
- 数据字段:
|
||||||
|
- 序号:数字
|
||||||
|
- 医嘱名称:文本(取已引用计费药品的药品名称)
|
||||||
|
- 剂量:数字(自动计算=规格×数量)
|
||||||
|
- 单位:文本(根据药品类型自动判断)
|
||||||
|
- 用法:下拉选择(不可编辑)
|
||||||
|
- 频次:固定"临时"
|
||||||
|
- 执行时间:自动生成当前时间
|
||||||
|
- 操作:编辑/删除按钮
|
||||||
|
- 操作功能:
|
||||||
|
- 编辑:弹出表单修改剂量、用法等字段
|
||||||
|
- 删除:二次确认后移除该条医嘱
|
||||||
|
|
||||||
|
#### 4. 签名确认区
|
||||||
|
|
||||||
|
**区域位置**:页面底部
|
||||||
|
**区域尺寸**:高度约180px
|
||||||
|
**区域功能**:完成医嘱确认和电子签名
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **签名信息卡**:
|
||||||
|
- 显示内容:医师姓名工号、签名状态、签名时间
|
||||||
|
- 样式特征:浅灰色背景,圆角边框
|
||||||
|
- **[一键签名按钮]**:
|
||||||
|
- 元素类型:主要操作按钮
|
||||||
|
- 显示内容:“一键签名并生成医嘱”
|
||||||
|
- 交互行为:点击后弹出账户密码输入框
|
||||||
|
- 样式特征:绿色背景,悬停效果
|
||||||
|
- **[取消按钮]**:
|
||||||
|
- 元素类型:次要操作按钮
|
||||||
|
- 显示内容:“取消”
|
||||||
|
- 交互行为:返回上级页面
|
||||||
|
|
||||||
|
### 四、交互功能详细说明
|
||||||
|
|
||||||
|
#### 1. 引用计费功能
|
||||||
|
|
||||||
|
**功能描述**:从术中计费药品获取患者当前最新的计费药品数据
|
||||||
|
**触发条件**:点击"引用计费"按钮
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 点击按钮获取患者当前最新的计费药品数据
|
||||||
|
2. 成功返回后更新药品表格数据
|
||||||
|
3. 自动计算并更新费用汇总
|
||||||
|
|
||||||
|
**反馈机制**:成功提示弹窗"已成功引用最新计费药品信息!"
|
||||||
|
**异常处理**:请求失败时显示错误提示“获取计费数据失败,请重试”,保留原数据
|
||||||
|
|
||||||
|
#### 2. 医嘱生成功能
|
||||||
|
|
||||||
|
**功能描述**:将计费药品转为正式医嘱
|
||||||
|
**触发条件**:点击"一键签名并生成医嘱"按钮
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 自动生成药品医嘱预览(带默认用法和剂量)
|
||||||
|
2. 弹出账户密码输入框
|
||||||
|
3. 验证通过后生成临时药品医嘱数据
|
||||||
|
4. 成功返回后显示生成结果
|
||||||
|
**数据转换规则**:
|
||||||
|
- 剂量 = 规格数值 × 数量(如"10ml"×2 → 20ml)
|
||||||
|
- 单位:根据药品名称自动判断(默认获取当前药品在《药品目录》维护剂量单位的值)
|
||||||
|
- 用法:根据药品名称自动判断(默认获取当前药品在《药品目录》维护用法的值,如果未维护默认空)
|
||||||
|
- 医嘱名称:取值药品名称
|
||||||
|
- 频次:默认ST
|
||||||
|
- 执行时间:默认当前系统时间
|
||||||
|
|
||||||
|
#### 3. 医嘱编辑功能
|
||||||
|
|
||||||
|
**功能描述**:修改已生成的医嘱明细
|
||||||
|
**触发条件**:点击"编辑"按钮
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 弹出编辑表单(带当前值医嘱值)
|
||||||
|
2. 修改后点击保存更新表格
|
||||||
|
3. 自动重新计算相关字段得值
|
||||||
|
**字段限制**:
|
||||||
|
- 剂量:必须为数字
|
||||||
|
- 用法:限定下拉选项,取值于字典管理:用药途径(用法)的值
|
||||||
|
- 频次:固定为"ST"不可编辑
|
||||||
|
|
||||||
|
### 五、数据结构说明
|
||||||
|
|
||||||
|
**关键数据字段**:
|
||||||
|
|
||||||
|
| **字段名** | **说明** | **数据类型** | **示例值** | **是否必填** | **备注** |
|
||||||
|
|---------------|----------|--------------|--------------------|--------------|------------------|
|
||||||
|
| patientId | 患者ID | string | “202507010122” | 是 | 就诊卡号 |
|
||||||
|
| surgeryNo | 手术单号 | string | “S202507010135” | 是 | |
|
||||||
|
| medicineName | 药品名称 | string | “罗哌卡因注射液” | 是 | |
|
||||||
|
| spec | 规格 | string | “10ml” | 是 | 需包含数值和单位 |
|
||||||
|
| batchNo | 批号 | string | “L240715” | 是 | |
|
||||||
|
| insuranceType | 医保类型 | string | “乙” | 是 | 甲/乙/自费 |
|
||||||
|
| usage | 用法 | string | “静脉推注” | 是 | |
|
||||||
|
| execTime | 执行时间 | datetime | “2025-07-01 08:41” | 是 | 精确到分钟 |
|
||||||
|
|
||||||
|
### 六、开发实现要点
|
||||||
|
|
||||||
|
**样式规范**:
|
||||||
|
|
||||||
|
- **主色调**:\#4a90e2(按钮/标题)
|
||||||
|
- **辅助色**:\#5cb85c(成功操作)、\#e74c3c(警告)
|
||||||
|
- **字体规范**:标题1.5rem/正文0.95rem,行高1.6
|
||||||
|
- **间距系统**:区块padding20px,元素间距15px
|
||||||
|
- **表格样式**:斑马纹,行高56px,单元格padding15px 20px
|
||||||
|
|
||||||
|
**技术要求**:
|
||||||
|
|
||||||
|
- **浏览器兼容**:Chrome/Firefox/Edge最新版
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
1. 医嘱生成后需同步更新手术记录
|
||||||
|
2. 所有金额显示保留两位小数
|
||||||
505
md/需求/102-门诊医生站传染病报告卡登记-2026-1-28.md
Normal file
505
md/需求/102-门诊医生站传染病报告卡登记-2026-1-28.md
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
## 门诊医生站传染性报卡登记PRD文档
|
||||||
|
|
||||||
|
### 一、页面概述
|
||||||
|
|
||||||
|
**页面名称**:门诊医生站传染性报卡登记
|
||||||
|
**页面目标**:帮助医生完成法定传染病病例的电子报告卡填写与提交
|
||||||
|
**适用场景**:医生确诊或疑似发现法定传染病病例时,进行报卡登记
|
||||||
|
**页面类型**:表单页(复杂表单)
|
||||||
|
|
||||||
|
**核心功能**:
|
||||||
|
|
||||||
|
1. 患者基本信息录入(含身份验证)
|
||||||
|
2. 传染病分类选择与疾病诊断信息登记
|
||||||
|
3. 病例分类与流行病学信息记录
|
||||||
|
4. 数据校验与表单提交
|
||||||
|
5. 地址四级联动选择(省-市-区县-街道)
|
||||||
|
|
||||||
|
**用户价值**:
|
||||||
|
|
||||||
|
- 规范传染病报告流程,确保数据完整准确
|
||||||
|
- 减少手工填写错误,提高上报效率
|
||||||
|
- 自动关联患者基本信息,减少重复录入
|
||||||
|
- 内置校验规则防止漏报错报
|
||||||
|
|
||||||
|
**原型图地址**:https://static.pm-ai.cn/prototype/20260128/6041dcc237645108aa9e917e8d57705f/index.html
|
||||||
|
**流程图**:
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A(["开始报卡"]) --> B["填写患者基本信息"]
|
||||||
|
B --> C{"身份证格式错误"}
|
||||||
|
C -- 是 --> D["提示请输入有效身份证号码"]
|
||||||
|
C -- 否 --> E{"患者年龄≤14岁"}
|
||||||
|
E -- 是 --> F["显示家长姓名输入框"]
|
||||||
|
E -- 否 --> G["隐藏家长姓名输入框"]
|
||||||
|
F --> H["填写现住地址"]
|
||||||
|
G --> H
|
||||||
|
H --> I{"地址加载失败"}
|
||||||
|
I -- 是 --> J["显示手动输入选项"]
|
||||||
|
I -- 否 --> K["选择疾病分类"]
|
||||||
|
J --> K
|
||||||
|
K --> L{"选择特定疾病"}
|
||||||
|
L -- 是 --> M["显示疾病分型选择"]
|
||||||
|
L -- 否 --> N["跳过分型选择"]
|
||||||
|
M --> O["填写发病/诊断日期"]
|
||||||
|
N --> O
|
||||||
|
O --> P{"日期逻辑错误"}
|
||||||
|
P -- 是 --> Q["提示发病日期不能晚于诊断日期"]
|
||||||
|
P -- 否 --> R["填写报告信息"]
|
||||||
|
Q --> R
|
||||||
|
R --> S["表单校验"]
|
||||||
|
S --> T{"校验失败"}
|
||||||
|
T -- 是 --> U["显示错误提示"]
|
||||||
|
T -- 否 --> V["保存报卡"]
|
||||||
|
U --> S
|
||||||
|
V --> W{"点击重置按钮"}
|
||||||
|
W -- 是 --> X["保留关键信息重置其他字段"]
|
||||||
|
X --> S
|
||||||
|
V --> Y{"点击关闭按钮"}
|
||||||
|
Y -- 是 --> Z{"确认关闭"}
|
||||||
|
Z -- 是 --> AA(["结束流程"])
|
||||||
|
Z -- 否 --> V
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 二、整体布局分析
|
||||||
|
|
||||||
|
**页面宽度**:自适应布局
|
||||||
|
**主要区域划分**:
|
||||||
|
|
||||||
|
1. **顶部标题区**(5%):展示表单标题和卡片编号
|
||||||
|
2. **患者信息区**(30%):患者基本信息、联系方式、现住地址等
|
||||||
|
3. **疾病信息区**(50%):疾病分类选择、发病/诊断日期、疾病分型等
|
||||||
|
4. **报告信息区**(10%):报告单位、医生、填卡日期等
|
||||||
|
|
||||||
|
**操作按钮区**(5%):保存、重置、关闭按钮
|
||||||
|
**布局特点**:上下布局,采用响应式网格,表单分组清晰,必填项高亮标识
|
||||||
|
|
||||||
|
### 三、页面区域详细描述
|
||||||
|
|
||||||
|
#### 1. 标题区
|
||||||
|
|
||||||
|
**区域位置**:页面顶部
|
||||||
|
**区域尺寸**:高度60px
|
||||||
|
**区域功能**:展示表单标题和唯一编号标识
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- 表单标题
|
||||||
|
|
||||||
|
- - 元素类型:标题文本
|
||||||
|
- 默认内容:“中华人民共和国传染病报告卡”
|
||||||
|
- 样式要求:20px字号,深蓝色(#2c3e50),居中加粗
|
||||||
|
|
||||||
|
- 卡片编号
|
||||||
|
|
||||||
|
- - 元素类型:输入框
|
||||||
|
- 默认值:空
|
||||||
|
- 提示文字:“单位自编,与网络直报一致”
|
||||||
|
- 交互行为:支持手动输入12位编号
|
||||||
|
- 样式要求:12px灰色文字,带下划线分隔线
|
||||||
|
|
||||||
|
#### 2. 患者基本信息区
|
||||||
|
|
||||||
|
**区域位置**:标题区下方
|
||||||
|
**区域功能**:采集患者核心身份信息、联系方式、居住地等
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- 患者姓名输入框
|
||||||
|
|
||||||
|
- - 元素类型:文本输入框,自动引入当前就诊患者信息的姓名
|
||||||
|
- 校验规则:必填项,支持中文姓名2-10字
|
||||||
|
|
||||||
|
- 家长姓名输入框
|
||||||
|
|
||||||
|
- - 元素类型:文本输入框
|
||||||
|
- 条件显示:当系统计算年龄≤14岁时自动显示必填标识
|
||||||
|
|
||||||
|
- 身份证号输入框
|
||||||
|
|
||||||
|
- - 元素类型:文本输入框,自动引入当前就诊患者信息的身份证号
|
||||||
|
- 校验规则:必填项,自动校验18位身份证格式
|
||||||
|
|
||||||
|
- 性别选择
|
||||||
|
|
||||||
|
- - 元素类型:单选按钮组
|
||||||
|
- 选项:男/女/未知,自动匹配当前就诊患者信息的性别
|
||||||
|
- 默认值:必填项
|
||||||
|
|
||||||
|
- 出生日期输入
|
||||||
|
|
||||||
|
- - 元素类型:复合输入区域
|
||||||
|
- 包含:年(4位)/月(2位)/日(2位)三个输入框,自动匹配当前就诊患者信息的出生年月
|
||||||
|
- 联动逻辑:自动计算实足年龄并填充到年龄输入框
|
||||||
|
|
||||||
|
- 工作单位输入框
|
||||||
|
|
||||||
|
- - 元素类型:文本输入框,自动引入当前就诊患者信息的工作单位
|
||||||
|
- 特殊场景:学生自动关联学校信息
|
||||||
|
|
||||||
|
- 联系电话
|
||||||
|
|
||||||
|
- - 元素类型:电话输入框,自动引入当前就诊患者信息的联系方式
|
||||||
|
- 校验规则:必填,11位手机号或带区号固话
|
||||||
|
|
||||||
|
- 紧急联系人电话
|
||||||
|
|
||||||
|
- - 元素类型:电话输入框
|
||||||
|
- 校验规则:必填,11位手机号或带区号固话
|
||||||
|
|
||||||
|
- 病人属于
|
||||||
|
|
||||||
|
- - 复选框类型:通过现地址自动判断
|
||||||
|
- 校验规则:必填
|
||||||
|
|
||||||
|
- 职业
|
||||||
|
|
||||||
|
- - 下拉选项类型:取值于字典管理的字典名称为“职业”维护的数据
|
||||||
|
- 校验规则:必填
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 3. 现住地址选择区
|
||||||
|
|
||||||
|
**区域功能**:四级联动地址选择(省-市-区县-街道)
|
||||||
|
**交互逻辑**:
|
||||||
|
|
||||||
|
1. 省份选择后动态加载对应城市
|
||||||
|
2. 城市选择后动态加载区县
|
||||||
|
3. 区县选择后动态加载街道
|
||||||
|
4. 村(居)和门牌号为手动输入
|
||||||
|
**数据要求**:
|
||||||
|
|
||||||
|
- 初始默认值:省-市-区县-街道(自动引入当前就诊患者信息的现住址)
|
||||||
|
- 异常处理:当上级未选择时禁用下级选择
|
||||||
|
|
||||||
|
**字典取值跟新增患者的现住址保持一致(患者管理-)患者列表)**
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
#### 4. 疾病信息区
|
||||||
|
|
||||||
|
**区域功能**:选择传染病类型及相关临床信息
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **疾病分类选择**:
|
||||||
|
|
||||||
|
- - 布局方式:网格布局(3列)
|
||||||
|
- 分类:甲类/乙类/丙类传染病
|
||||||
|
- 交互行为:多选但同类别互斥
|
||||||
|
- 特殊处理:选择炭疽/肺结核/病毒性肝炎/疟疾/梅毒/血吸虫病等疾病时激活分型选择
|
||||||
|
|
||||||
|
- **疾病复选框互斥逻辑:**
|
||||||
|
|
||||||
|
- - 选择炭疽病时显示分型选项(肺炭疽/皮肤炭疽/胃肠炭疽/未分型)
|
||||||
|
- 选择肺结核时显示分型选项(涂阳/仅培阳/菌阴/未痰检)
|
||||||
|
- 选择病毒性肝炎时显示分型选项(甲/乙/丙/戊型)
|
||||||
|
- 选择疟疾时显示分型选项(间日疟/恶性疟/三日疟/卵形疟/未分型)
|
||||||
|
- 选择梅毒时显示分型选项(Ⅰ期/Ⅱ期/Ⅲ期/胎传/隐性)
|
||||||
|
- 选择血吸虫病时显示分型选项(急性/慢性/晚期/未分型)
|
||||||
|
|
||||||
|
- **分型选择**:
|
||||||
|
|
||||||
|
- - 元素类型:动态下拉框
|
||||||
|
- 数据源:根据疾病类型动态加载
|
||||||
|
- 示例:肺结核→涂阳/仅培阳/菌阴/未痰检
|
||||||
|
|
||||||
|
- **其他法定管理以及重点监测传染病输入框:**
|
||||||
|
|
||||||
|
- - 手动输入非列表疾病
|
||||||
|
- 自动关联传染病代码库
|
||||||
|
|
||||||
|
- **发病日期**:
|
||||||
|
|
||||||
|
- - 元素类型:日期选择器
|
||||||
|
- 验证规则:不得晚于诊断日期
|
||||||
|
|
||||||
|
- **诊断日期**:
|
||||||
|
|
||||||
|
- - 元素类型:日期选择器
|
||||||
|
- 取值:默认当前系统时间
|
||||||
|
|
||||||
|
- **死亡日期**:
|
||||||
|
|
||||||
|
- - 元素类型:日期选择器
|
||||||
|
- 填写规则:根据实际情况填写
|
||||||
|
|
||||||
|
- **病例分类**
|
||||||
|
|
||||||
|
- - 复选框类型: 1疑似病例/2临床诊断病例/3确诊病例/4病原携带/5阳性检测结果
|
||||||
|
- 校验规则:必填
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 5. 报告信息区
|
||||||
|
|
||||||
|
**区域功能**:记录报告单位和责任人信息等
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **报告单位**:
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:当前登录医院
|
||||||
|
- 交互行为:只读
|
||||||
|
|
||||||
|
- **联系电话**:
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:当前登录医院的联系电话
|
||||||
|
- 交互行为:可编辑
|
||||||
|
|
||||||
|
- **报告医生**:
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:当前登录医生
|
||||||
|
- 验证规则:必填
|
||||||
|
|
||||||
|
- **填卡日期**
|
||||||
|
|
||||||
|
- - 默认当前系统日期,显示为"YYYY-MM-DD"格式
|
||||||
|
|
||||||
|
- **修订病名**
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:空
|
||||||
|
- 填写:自定义编辑
|
||||||
|
|
||||||
|
- **退卡原因**
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:空
|
||||||
|
- 填写:自定义编辑
|
||||||
|
|
||||||
|
- **备注**
|
||||||
|
|
||||||
|
- - 元素类型:文本输入
|
||||||
|
- 默认值:空
|
||||||
|
- 填写:自定义编辑
|
||||||
|
|
||||||
|
#### 6. 操作按钮区
|
||||||
|
|
||||||
|
**区域位置**:页面底部
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **保存按钮**:
|
||||||
|
|
||||||
|
- - 元素类型:主要操作按钮
|
||||||
|
- 交互行为:触发表单验证,通过后保存
|
||||||
|
- 样式特征:蓝色(#3498db),圆角8px
|
||||||
|
|
||||||
|
- **重置按钮**:
|
||||||
|
|
||||||
|
- - 交互行为:清除非基础信息字段
|
||||||
|
- 特殊处理:保留患者姓名、身份证等关键信息
|
||||||
|
|
||||||
|
- **关闭按钮**:
|
||||||
|
|
||||||
|
- - 交互行为:二次确认后关闭页面
|
||||||
|
- 样式特征:红色(#e74c3c)
|
||||||
|
|
||||||
|
### 四、交互功能详细说明
|
||||||
|
|
||||||
|
#### 1. 地址联动选择
|
||||||
|
|
||||||
|
**触发条件**:选择省级行政区
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 选择省份→加载该省下所有城市
|
||||||
|
2. 选择城市→加载该市所有区县
|
||||||
|
3. 选择区县→加载街道列表
|
||||||
|
**异常处理**:网络错误时显示"加载失败,请手动输入"
|
||||||
|
|
||||||
|
#### 2. 疾病分型联动
|
||||||
|
|
||||||
|
**触发条件**:选择特定疾病
|
||||||
|
**数据映射**:
|
||||||
|
|
||||||
|
| **疾病类型** | **分型选项** |
|
||||||
|
| ------------ | ---------------------------------- |
|
||||||
|
| 肺结核 | 涂阳/仅培阳/菌阴/未痰检 |
|
||||||
|
| 梅毒 | I期/II期/III期/胎传/隐性 |
|
||||||
|
| 炭疽 | 肺炭疽/皮肤炭疽/胃肠炭疽/未分型 |
|
||||||
|
| 病毒性肝炎 | 甲/乙/丙/戊型 |
|
||||||
|
| 疟疾 | 间日疟/恶性疟/三日疟/卵形疟/未分型 |
|
||||||
|
| 血吸虫病 | 急性/慢性/晚期/未分型 |
|
||||||
|
|
||||||
|
#### 3. 表单验证
|
||||||
|
|
||||||
|
**全局验证**:
|
||||||
|
|
||||||
|
1. 提交时检查必填字段
|
||||||
|
2. 验证身份证号格式
|
||||||
|
3. 确保至少选择一种疾病
|
||||||
|
**字段级验证**:
|
||||||
|
|
||||||
|
- 电话号码:11位数字,错误提示“请输入有效的联系电话”
|
||||||
|
- 发病日期≤诊断日期≤填卡日期,错误提示“发病日期不能晚于诊断日期”
|
||||||
|
- 身份证号18位且符合校验算法,错误提示“请输入有效的身份证号码”
|
||||||
|
|
||||||
|
### 五、数据结构说明
|
||||||
|
|
||||||
|
**传染病报卡表(infectious_card)**
|
||||||
|
|
||||||
|
| **字段** | **类型** | **国标含义** | **来源****/****说明** |
|
||||||
|
|---------------------| -------------- |-----------------|--------------------------------------|
|
||||||
|
| card_no | VARCHAR(20) PK | 卡片编号 | 机构代码+年月日+4位流水 |
|
||||||
|
| visit_id | BIGINT FK | 本次就诊ID | adm_encounter.id |
|
||||||
|
| diag_id | BIGINT FK | 诊断记录唯一ID | adm_encounter_diagnosis.condition_id |
|
||||||
|
| pat_id | BIGINT FK | 患者主索引 | adm_patient.id |
|
||||||
|
| id_type | TINYINT | 证件类型 | |
|
||||||
|
| id_no | VARCHAR(30) | 证件号码 | 18位校验 |
|
||||||
|
| pat_name | VARCHAR(50) | 患者姓名 | |
|
||||||
|
| parent_name | VARCHAR(50) | 家长姓名 | ≤14岁必填 |
|
||||||
|
| sex | CHAR(1) | 性别 | 1男/2女/0未知 |
|
||||||
|
| birthday | DATE | 出生日期 | |
|
||||||
|
| age | INT | 实足年龄 | 函数计算 |
|
||||||
|
| age_unit | CHAR(1) | 年龄单位 | 岁/月/天-》1岁/2月/3天 |
|
||||||
|
| workplace | VARCHAR(100) | 工作单位 | 学生填学校 |
|
||||||
|
| phone | VARCHAR(20) | 联系电话 | 患者本人电话 |
|
||||||
|
| contact_phone | VARCHAR(20) | 紧急联系人电话 | |
|
||||||
|
| address_prov | VARCHAR(6) | 现住址省 | GB2260 |
|
||||||
|
| address_city | VARCHAR(6) | 现住址市 | 同上 |
|
||||||
|
| address_county | VARCHAR(6) | 现住址县 | 同上 |
|
||||||
|
| address_town | VARCHAR(9) | 现住址街道 | 同上 |
|
||||||
|
| address_village | VARCHAR(80) | 现住址村/居委 | |
|
||||||
|
| address_house | VARCHAR(40) | 现住址门牌号 | |
|
||||||
|
| patient_belong | TINYINT | 病人属于 | 系统判定,1本县区/2本市其他/3本省其他/4外省/5港澳台/6外籍 |
|
||||||
|
| occupation | VARCHAR(4) | 职业 | GB/T 6565,取值于字典管理的字典名称为“职业”维护的数据 |
|
||||||
|
| disease_code | VARCHAR(8) | 疾病名称 | WS 218-2020,见下表 |
|
||||||
|
| disease_type | VARCHAR(8) | 分型 | 见下表,6类必分型疾病必填 |
|
||||||
|
| other_disease | VARCHAR(50) | 其他法定管理以及重点监测传染病 | |
|
||||||
|
| case_class | TINYINT | 病例分类 | 1疑似病例/2临床诊断病例/3确诊病例/4病原携带/5阳性检测结果 |
|
||||||
|
| onset_date | DATE | 发病日期 | 默认诊断时间,病原携带者填初检日期 |
|
||||||
|
| diag_date | DATETIME | 诊断日期 | 精确到小时 |
|
||||||
|
| death_date | DATE | 死亡日期 | 死亡病例必填 |
|
||||||
|
| correct_name | VARCHAR(50) | 订正病名 | 订正报告必填 |
|
||||||
|
| withdraw_reason | VARCHAR(100) | 退卡原因 | 退卡时必填 |
|
||||||
|
| report_org | VARCHAR(18) | 报告单位 | 统一信用代码(医院名称) |
|
||||||
|
| report_org_phone | VARCHAR(20) | 联系电话 | 报告单位电话:医院总值班/防保科座机 |
|
||||||
|
| report_doc | VARCHAR(20) | 报告医生 | 医生姓名 |
|
||||||
|
| report_date | DATE | 填卡日期 | 当天日期 |
|
||||||
|
| status | TINYINT | 状态 | 0暂存1已提交2已审核3已上报4失败5作废 |
|
||||||
|
| fail_msg | VARCHAR(500) | 失败原因 | 国家平台返回 |
|
||||||
|
| xml_content | TEXT | 上报XML | 日志 |
|
||||||
|
| create_time | DATETIME | 创建时间 | |
|
||||||
|
| update_time | DATETIME | 更新时间 | |
|
||||||
|
| card_name_code | TINYINT | 报卡名称代码 | 数值对照(取值于字典管理-》报卡名称代码)1-中华人民共和国传染病报告卡 |
|
||||||
|
| registration source | TINYINT | 登记来源 | 1门诊/2住院 |
|
||||||
|
| dept_id | TINYINT | 科室ID | 患者当前就诊科室 |
|
||||||
|
| doctor_id | TINYINT | 医生ID | 患者当前开单医生 |
|
||||||
|
|
||||||
|
**甲类传染病(2 种)―― 01xxxx**
|
||||||
|
|
||||||
|
| **disease_code** | **疾病名称** | **国家平台码** |
|
||||||
|
| ---------------- | ------------ | -------------- |
|
||||||
|
| 0101 | 鼠疫 | 甲类 |
|
||||||
|
| 0102 | 霍乱 | 甲类 |
|
||||||
|
|
||||||
|
存值示例:`0101`(鼠疫)、`0102`(霍乱)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**乙类传染病(27 种)―― 02xxxx**
|
||||||
|
|
||||||
|
| **disease_code** | **疾病名称** | **国家平台码** |
|
||||||
|
| ---------------- | -------------------- | ------------------ |
|
||||||
|
| 0201 | 传染性非典型肺炎 | 乙类(按甲类管理) |
|
||||||
|
| 0202 | 艾滋病 | 乙类 |
|
||||||
|
| 0203 | 病毒性肝炎 | 乙类 |
|
||||||
|
| 0204 | 脊髓灰质炎 | 乙类(按甲类管理) |
|
||||||
|
| 0205 | 人感染高致病性禽流感 | 乙类(按甲类管理) |
|
||||||
|
| 0206 | 麻疹 | 乙类 |
|
||||||
|
| 0207 | 流行性出血热 | 乙类 |
|
||||||
|
| 0208 | 狂犬病 | 乙类 |
|
||||||
|
| 0209 | 流行性乙型脑炎 | 乙类 |
|
||||||
|
| 0210 | 登革热 | 乙类 |
|
||||||
|
| 0211 | 炭疽 | 乙类(按甲类管理) |
|
||||||
|
| 0212 | 细菌性和阿米巴性痢疾 | 乙类 |
|
||||||
|
| 0213 | 肺结核 | 乙类 |
|
||||||
|
| 0214 | 伤寒和副伤寒 | 乙类 |
|
||||||
|
| 0215 | 流行性脑脊髓膜炎 | 乙类 |
|
||||||
|
| 0216 | 百日咳 | 乙类 |
|
||||||
|
| 0217 | 白喉 | 乙类 |
|
||||||
|
| 0218 | 新生儿破伤风 | 乙类 |
|
||||||
|
| 0219 | 猩红热 | 乙类 |
|
||||||
|
| 0220 | 布鲁氏菌病 | 乙类 |
|
||||||
|
| 0221 | 淋病 | 乙类 |
|
||||||
|
| 0222 | 梅毒 | 乙类 |
|
||||||
|
| 0223 | 钩端螺旋体病 | 乙类 |
|
||||||
|
| 0224 | 血吸虫病 | 乙类 |
|
||||||
|
| 0225 | 疟疾 | 乙类 |
|
||||||
|
|
||||||
|
存值示例:乙肝→`0203`;肺结核→`0213`;梅毒→`0222`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**丙类传染病(11 种)―― 03xxxx**
|
||||||
|
|
||||||
|
| **disease_code** | **疾病名称** | **国家平台码** |
|
||||||
|
| ---------------- | ---------------------- | -------------- |
|
||||||
|
| 0301 | 流行性感冒 | 丙类 |
|
||||||
|
| 0302 | 流行性腮腺炎 | 丙类 |
|
||||||
|
| 0303 | 风疹 | 丙类 |
|
||||||
|
| 0304 | 急性出血性结膜炎 | 丙类 |
|
||||||
|
| 0305 | 麻风病 | 丙类 |
|
||||||
|
| 0306 | 流行性和地方性斑疹伤寒 | 丙类 |
|
||||||
|
| 0307 | 黑热病 | 丙类 |
|
||||||
|
| 0308 | 包虫病 | 丙类 |
|
||||||
|
| 0309 | 丝虫病 | 丙类 |
|
||||||
|
| 0310 | 其它感染性腹泻病 | 丙类 |
|
||||||
|
| 0311 | 手足口病 | 丙类 |
|
||||||
|
|
||||||
|
存值示例:手足口病→`0311`;流感→`0301`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**分型码与名称对照(系统存值用)**
|
||||||
|
|
||||||
|
| **大类疾病** | **disease_code** | **分型中文** | **disease_type** **存值** |
|
||||||
|
| -------------- | ---------------- | ------------ | ------------------------- |
|
||||||
|
| **病毒性肝炎** | 0203 | 甲型 | 020301 |
|
||||||
|
| | | 乙型 | 020302 |
|
||||||
|
| | | 丙型 | 020303 |
|
||||||
|
| | | 戊型 | 020304 |
|
||||||
|
| | | 未分型 | 020305 |
|
||||||
|
| **炭疽** | 0211 | 肺炭疽 | 021101 |
|
||||||
|
| | | 皮肤炭疽 | 021102 |
|
||||||
|
| | | 胃肠炭疽 | 021103 |
|
||||||
|
| | | 未分型 | 021104 |
|
||||||
|
| **肺结核** | 0213 | 涂阳 | 021301 |
|
||||||
|
| | | 仅培阳 | 021302 |
|
||||||
|
| | | 菌阴 | 021303 |
|
||||||
|
| | | 未痰检 | 021304 |
|
||||||
|
| **梅毒** | 0222 | Ⅰ期 | 022201 |
|
||||||
|
| | | Ⅱ期 | 022202 |
|
||||||
|
| | | Ⅲ期 | 022203 |
|
||||||
|
| | | 胎传 | 022204 |
|
||||||
|
| | | 隐性 | 022205 |
|
||||||
|
| **疟疾** | 0225 | 间日疟 | 022501 |
|
||||||
|
| | | 恶性疟 | 022502 |
|
||||||
|
| | | 三日疟 | 022503 |
|
||||||
|
| | | 卵形疟 | 022504 |
|
||||||
|
| | | 未分型 | 022505 |
|
||||||
|
| **血吸虫病** | 0224 | 急性 | 022401 |
|
||||||
|
| | | 慢性 | 022402 |
|
||||||
|
| | | 晚期 | 022403 |
|
||||||
|
| | | 未分型 | 022404 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 六、开发实现要点
|
||||||
|
|
||||||
|
**样式规范**:
|
||||||
|
|
||||||
|
- 主色调:#3498db(按钮/重要标签)
|
||||||
|
- 错误状态:#e74c3c(边框+文字)
|
||||||
|
- 表单间距:8px垂直间距,16px水平间距
|
||||||
|
|
||||||
|
**技术要求**:
|
||||||
|
|
||||||
|
- 支持Chrome/Firefox/Edge最新版
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
1. 身份证号不需脱敏显示
|
||||||
|
|
||||||
|
|
||||||
62
md/需求/99-门诊手术中计费界面PRD_2026-1-22.md
Normal file
62
md/需求/99-门诊手术中计费界面PRD_2026-1-22.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
**门诊手术中计费PRD文档**
|
||||||
|
|
||||||
|
**目标:**
|
||||||
|
|
||||||
|
支持手术中追加计费(耗材、药品等)、退费等场景使用
|
||||||
|
|
||||||
|
术后一站式结算(发票、清单、医保等)
|
||||||
|
|
||||||
|
**流程图:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A["医生开立手术申请单"] --> B{"系统生成计费包"}
|
||||||
|
B --> C["患者缴费"]
|
||||||
|
C --> D["手术室确认"]
|
||||||
|
D --> E{"术中追加/退费?"}
|
||||||
|
|
||||||
|
E -- "是" --> F{"术中计费"}
|
||||||
|
F -- "耗材" --> F2["护士扫码追加耗材\n实时计价 更新库存"]
|
||||||
|
F -- "药品" --> F3["麻醉师追加药品\n实时计价 更新库存"]
|
||||||
|
F -- "诊疗项目" --> F4["追加麻醉时长/项目\n实时计价"]
|
||||||
|
|
||||||
|
|
||||||
|
F2 --> F6["生成术中追加计费单"]
|
||||||
|
F3 --> F6
|
||||||
|
F4 --> F6
|
||||||
|
|
||||||
|
|
||||||
|
F6 --> G{"患者支付?"}
|
||||||
|
G -- "是" --> P["提示支付成功"]--> J
|
||||||
|
G -- "否" --> H["提示支付失败\n保持待支付"]
|
||||||
|
H --> D
|
||||||
|
|
||||||
|
E -- "否" --> I["手术完成"]
|
||||||
|
I --> J["术后统一结算"]
|
||||||
|
J --> K["发票/清单/分割单"]
|
||||||
|
K --> L["财务对账"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:**待门诊手术安排界面(禅道需求编号:93)完成后再执行
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
图1:门诊手术安排界面(禅道需求编号:93)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
图2:门诊管理-》门诊划价:手术计费界面复制《门诊划价》界面红色框内容
|
||||||
|
|
||||||
|
1、如上图1、2所示:在门诊手术安排界面增加【计费】按钮,实现对门诊手术中追加的费用进行记账,手术计费界面如图2所示复制《门诊划价》界面红色框内容进行个性化改造,患者信息:取值于手术安排界面选中行的患者信息,计费账号为当前系统登录的账号。
|
||||||
|
|
||||||
|
\*比如:在手术计费界面给患者1计费成功后,重新从手术按钮界面选中患者1点击【计费】打开界面时显示当前患者已计费成功的手术费用。
|
||||||
|
|
||||||
|
写入事务注意:
|
||||||
|
|
||||||
|
adm_charge_item费用项管理表
|
||||||
|
|
||||||
|
①、术中费用仍走“门诊就诊管理”的就诊ID(adm_encounter.id = adm_charge_item.encounter_id)。
|
||||||
|
|
||||||
|
2\. 为了事后能追溯“这些费用是术中发生的”,在费用项管理表明细上加一个 “来源业务单据(SourceBillNo)” 字段(adm_charge_item.generate_source_enum = 2(帐单生成来源为手术计费),SourceBillNo = 手术申请单号)。
|
||||||
|
|
||||||
|
3\. 其他内容按照《门诊划价》的业务数据流程走。
|
||||||
BIN
md/需求/media/2756f39fb624c7f686d56b675b4d4d10.png
Normal file
BIN
md/需求/media/2756f39fb624c7f686d56b675b4d4d10.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
BIN
md/需求/media/4fa3fca6b8362de7b938ded77d6e4982.png
Normal file
BIN
md/需求/media/4fa3fca6b8362de7b938ded77d6e4982.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
BIN
md/需求/media/clip_image001.png
Normal file
BIN
md/需求/media/clip_image001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 KiB |
BIN
md/需求/media/e577cd26f9a82835f3ac3690259eb357.png
Normal file
BIN
md/需求/media/e577cd26f9a82835f3ac3690259eb357.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
@@ -33,7 +33,9 @@ public class SysMenuController extends BaseController {
|
|||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public AjaxResult list(SysMenu menu) {
|
public AjaxResult list(SysMenu menu) {
|
||||||
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
|
List<SysMenu> menus = menuService.selectMenuList(menu, getUserId());
|
||||||
return success(menus);
|
// 构建带完整路径的菜单树
|
||||||
|
List<SysMenu> menuTreeWithFullPath = menuService.buildMenuTreeWithFullPath(menus);
|
||||||
|
return success(menuTreeWithFullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,4 +117,25 @@ public class SysMenuController extends BaseController {
|
|||||||
}
|
}
|
||||||
return toAjax(menuService.deleteMenuById(menuId));
|
return toAjax(menuService.deleteMenuById(menuId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单完整路径
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:menu:query')")
|
||||||
|
@GetMapping("/fullPath/{menuId}")
|
||||||
|
public AjaxResult getFullPath(@PathVariable("menuId") Long menuId) {
|
||||||
|
String fullPath = menuService.getMenuFullPath(menuId);
|
||||||
|
return success(fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成完整路径
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:menu:query')")
|
||||||
|
@PostMapping("/generateFullPath")
|
||||||
|
public AjaxResult generateFullPath(@RequestParam(required = false) Long parentId,
|
||||||
|
@RequestParam String currentPath) {
|
||||||
|
String fullPath = menuService.generateFullPath(parentId, currentPath);
|
||||||
|
return success(fullPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.core.common.core.domain;
|
|||||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
@@ -27,6 +28,7 @@ public class HisBaseEntity implements Serializable {
|
|||||||
|
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
/** 更新者 */
|
/** 更新者 */
|
||||||
@@ -35,6 +37,7 @@ public class HisBaseEntity implements Serializable {
|
|||||||
|
|
||||||
/** 更新时间 */
|
/** 更新时间 */
|
||||||
@TableField(fill = FieldFill.UPDATE)
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date updateTime;
|
private Date updateTime;
|
||||||
|
|
||||||
/** 租户ID */
|
/** 租户ID */
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ public class SysMenu extends BaseEntity {
|
|||||||
/** 子菜单 */
|
/** 子菜单 */
|
||||||
private List<SysMenu> children = new ArrayList<SysMenu>();
|
private List<SysMenu> children = new ArrayList<SysMenu>();
|
||||||
|
|
||||||
|
/** 完整路径 */
|
||||||
|
private String fullPath;
|
||||||
|
|
||||||
public Long getMenuId() {
|
public Long getMenuId() {
|
||||||
return menuId;
|
return menuId;
|
||||||
}
|
}
|
||||||
@@ -212,6 +215,14 @@ public class SysMenu extends BaseEntity {
|
|||||||
this.children = children;
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFullPath() {
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullPath(String fullPath) {
|
||||||
|
this.fullPath = fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("menuId", getMenuId())
|
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("menuId", getMenuId())
|
||||||
@@ -219,8 +230,8 @@ public class SysMenu extends BaseEntity {
|
|||||||
.append("path", getPath()).append("component", getComponent()).append("query", getQuery())
|
.append("path", getPath()).append("component", getComponent()).append("query", getQuery())
|
||||||
.append("routeName", getRouteName()).append("isFrame", getIsFrame()).append("IsCache", getIsCache())
|
.append("routeName", getRouteName()).append("isFrame", getIsFrame()).append("IsCache", getIsCache())
|
||||||
.append("menuType", getMenuType()).append("visible", getVisible()).append("status ", getStatus())
|
.append("menuType", getMenuType()).append("visible", getVisible()).append("status ", getStatus())
|
||||||
.append("perms", getPerms()).append("icon", getIcon()).append("createBy", getCreateBy())
|
.append("perms", getPerms()).append("icon", getIcon()).append("fullPath", getFullPath())
|
||||||
.append("createTime", getCreateTime()).append("updateBy", getUpdateBy())
|
.append("createBy", getCreateBy()).append("createTime", getCreateTime()).append("updateBy", getUpdateBy())
|
||||||
.append("updateTime", getUpdateTime()).append("remark", getRemark()).toString();
|
.append("updateTime", getUpdateTime()).append("remark", getRemark()).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public final class ServiceException extends RuntimeException {
|
|||||||
/**
|
/**
|
||||||
* 错误明细,内部调试错误
|
* 错误明细,内部调试错误
|
||||||
*
|
*
|
||||||
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
|
* 和
|
||||||
*/
|
*/
|
||||||
private String detailMessage;
|
private String detailMessage;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.core.common.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.core.common.utils.AuditFieldUtil;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包含审计字段自动设置功能的基础服务类
|
||||||
|
*
|
||||||
|
* @param <M> Mapper 类型,必须继承 BaseMapper<T>
|
||||||
|
* @param <T> 实体类型
|
||||||
|
*/
|
||||||
|
public abstract class BaseService<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写保存方法,自动设置创建审计字段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean save(T entity) {
|
||||||
|
// 在保存前设置创建审计字段
|
||||||
|
AuditFieldUtil.setCreateInfo(entity);
|
||||||
|
return super.save(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写批量保存方法,自动设置创建审计字段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean saveBatch(Collection<T> entityList) {
|
||||||
|
// 为每个实体设置创建审计字段
|
||||||
|
entityList.forEach(AuditFieldUtil::setCreateInfo);
|
||||||
|
return super.saveBatch(entityList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写更新方法,自动设置更新审计字段
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean updateById(T entity) {
|
||||||
|
// 在更新前设置更新审计字段
|
||||||
|
AuditFieldUtil.setUpdateInfo(entity);
|
||||||
|
return super.updateById(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package com.core.common.utils;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审计字段工具类
|
||||||
|
* 用于手动设置创建人、创建时间、更新人、更新时间等审计字段
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AuditFieldUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为实体设置创建相关的审计字段
|
||||||
|
* @param entity 实体对象
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
// 只有当字段值为 null 或空字符串时才设置
|
||||||
|
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);
|
||||||
|
// 只有当字段值为 null 时才设置
|
||||||
|
if (createTimeField.get(entity) == null) {
|
||||||
|
createTimeField.set(entity, currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理下划线命名的字段
|
||||||
|
Field createByFieldUnderscore = getField(entity.getClass(), "create_by");
|
||||||
|
if (createByFieldUnderscore != null) {
|
||||||
|
createByFieldUnderscore.setAccessible(true);
|
||||||
|
// 只有当字段值为 null 或空字符串时才设置
|
||||||
|
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);
|
||||||
|
// 只有当字段值为 null 时才设置
|
||||||
|
if (createTimeFieldUnderscore.get(entity) == null) {
|
||||||
|
createTimeFieldUnderscore.set(entity, currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("设置创建审计字段时发生异常: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为实体设置更新相关的审计字段
|
||||||
|
* @param entity 实体对象
|
||||||
|
*/
|
||||||
|
public static void setUpdateInfo(Object entity) {
|
||||||
|
if (entity == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
String username = loginUser != null ? loginUser.getUsername() : "system";
|
||||||
|
Date currentTime = new Date();
|
||||||
|
|
||||||
|
// 设置更新人字段
|
||||||
|
Field updateByField = getField(entity.getClass(), "updateBy");
|
||||||
|
if (updateByField != null) {
|
||||||
|
updateByField.setAccessible(true);
|
||||||
|
updateByField.set(entity, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置更新时间字段
|
||||||
|
Field updateTimeField = getField(entity.getClass(), "updateTime");
|
||||||
|
if (updateTimeField != null) {
|
||||||
|
updateTimeField.setAccessible(true);
|
||||||
|
updateTimeField.set(entity, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理下划线命名的字段
|
||||||
|
Field updateByFieldUnderscore = getField(entity.getClass(), "update_by");
|
||||||
|
if (updateByFieldUnderscore != null) {
|
||||||
|
updateByFieldUnderscore.setAccessible(true);
|
||||||
|
updateByFieldUnderscore.set(entity, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
Field updateTimeFieldUnderscore = getField(entity.getClass(), "update_time");
|
||||||
|
if (updateTimeFieldUnderscore != null) {
|
||||||
|
updateTimeFieldUnderscore.setAccessible(true);
|
||||||
|
updateTimeFieldUnderscore.set(entity, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("设置更新审计字段时发生异常: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用反射获取字段,支持父类字段
|
||||||
|
* @param clazz 类
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @return 字段对象
|
||||||
|
*/
|
||||||
|
private static Field getField(Class<?> clazz, String fieldName) {
|
||||||
|
try {
|
||||||
|
return clazz.getDeclaredField(fieldName);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
// 如果在当前类中找不到,尝试在父类中查找
|
||||||
|
if (clazz.getSuperclass() != null && clazz.getSuperclass() != Object.class) {
|
||||||
|
return getField(clazz.getSuperclass(), fieldName);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,32 @@
|
|||||||
package com.core.framework.config;
|
package com.core.framework.config;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||||
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||||
|
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.core.framework.handler.MybastisColumnsHandler;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
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;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.sql.DataSource;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -149,4 +159,55 @@ public class MybatisPlusConfig {
|
|||||||
|
|
||||||
return result != null ? result : 1; // 默认租户ID
|
return result != null ? result : 1; // 默认租户ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置 SqlSessionFactory
|
||||||
|
* 由于排除了 DataSourceAutoConfiguration,需要手动配置
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
public SqlSessionFactory sqlSessionFactory(
|
||||||
|
@Qualifier("dynamicDataSource") DataSource dataSource,
|
||||||
|
MybatisPlusInterceptor mybatisPlusInterceptor,
|
||||||
|
MetaObjectHandler metaObjectHandler) throws Exception { // 注入 MetaObjectHandler
|
||||||
|
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
|
||||||
|
sessionFactory.setDataSource(dataSource);
|
||||||
|
// 设置 mapper 文件位置
|
||||||
|
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
|
||||||
|
.getResources("classpath*:mapper/**/*Mapper.xml"));
|
||||||
|
// 设置 typeAliases 包路径
|
||||||
|
sessionFactory.setTypeAliasesPackage("com.core.**.domain,com.openhis.**.domain");
|
||||||
|
|
||||||
|
// 配置 MyBatis-Plus
|
||||||
|
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||||
|
// 使用驼峰命名法转换字段
|
||||||
|
configuration.setMapUnderscoreToCamelCase(true);
|
||||||
|
// 开启缓存
|
||||||
|
configuration.setCacheEnabled(true);
|
||||||
|
// 允许JDBC支持自动生成主键
|
||||||
|
configuration.setUseGeneratedKeys(true);
|
||||||
|
// 配置默认的执行器
|
||||||
|
configuration.setDefaultExecutorType(org.apache.ibatis.session.ExecutorType.SIMPLE);
|
||||||
|
// 配置日志实现
|
||||||
|
configuration.setLogImpl(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
|
||||||
|
sessionFactory.setConfiguration(configuration);
|
||||||
|
|
||||||
|
// 设置 MyBatis-Plus 的全局配置
|
||||||
|
GlobalConfig globalConfig = new GlobalConfig();
|
||||||
|
globalConfig.setMetaObjectHandler(metaObjectHandler);
|
||||||
|
sessionFactory.setGlobalConfig(globalConfig);
|
||||||
|
|
||||||
|
// 设置拦截器(通过参数注入避免循环依赖)
|
||||||
|
sessionFactory.setPlugins(mybatisPlusInterceptor);
|
||||||
|
|
||||||
|
return sessionFactory.getObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册自动填充处理器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public MetaObjectHandler metaObjectHandler() {
|
||||||
|
return new MybastisColumnsHandler();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,9 @@ public class MybastisColumnsHandler implements MetaObjectHandler {
|
|||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
// 使用 fillStrategy 而不是 strictInsertFill,确保即使字段已设置也能填充(如果为null)
|
||||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, getCurrentTenantId());
|
this.fillStrategy(metaObject, "createBy", username != null ? username : "system");
|
||||||
|
this.fillStrategy(metaObject, "tenantId", getCurrentTenantId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置数据修改update时候的,字段自动赋值规则
|
// 设置数据修改update时候的,字段自动赋值规则
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.core.system.controller;
|
||||||
|
|
||||||
|
import com.core.common.annotation.Log;
|
||||||
|
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.enums.BusinessType;
|
||||||
|
import com.core.system.domain.SysUserConfig;
|
||||||
|
import com.core.system.service.ISysUserConfigService;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户配置Controller
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Api(tags = "用户配置")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/userConfig")
|
||||||
|
public class SysUserConfigController extends BaseController
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private ISysUserConfigService sysUserConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户配置列表
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:userConfig:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo list(SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
startPage();
|
||||||
|
List<SysUserConfig> list = sysUserConfigService.selectSysUserConfigList(sysUserConfig);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户配置详细信息
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:userConfig:query')")
|
||||||
|
@GetMapping(value = "/{configId}")
|
||||||
|
public AjaxResult getInfo(@PathVariable("configId") Long configId)
|
||||||
|
{
|
||||||
|
return AjaxResult.success(sysUserConfigService.selectSysUserConfigById(configId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增用户配置
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:userConfig:add')")
|
||||||
|
@Log(title = "用户配置", businessType = BusinessType.INSERT)
|
||||||
|
@PostMapping
|
||||||
|
public AjaxResult add(@RequestBody SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
return AjaxResult.success(sysUserConfigService.insertSysUserConfig(sysUserConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户配置
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:userConfig:edit')")
|
||||||
|
@Log(title = "用户配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping
|
||||||
|
public AjaxResult edit(@RequestBody SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
return toAjax(sysUserConfigService.updateSysUserConfig(sysUserConfig));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户配置
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@ss.hasPermi('system:userConfig:remove')")
|
||||||
|
@Log(title = "用户配置", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{configIds}")
|
||||||
|
public AjaxResult remove(@PathVariable Long[] configIds)
|
||||||
|
{
|
||||||
|
return toAjax(sysUserConfigService.deleteSysUserConfigByIds(configIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户的指定配置
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取当前用户的指定配置")
|
||||||
|
@GetMapping("/currentUserConfig")
|
||||||
|
public AjaxResult getCurrentUserConfig(@RequestParam String configKey)
|
||||||
|
{
|
||||||
|
Long userId = SecurityUtils.getUserId();
|
||||||
|
String configValue = sysUserConfigService.selectConfigValueByUserIdAndKey(userId, configKey);
|
||||||
|
// 返回原始配置值,不需要额外编码,由前端处理
|
||||||
|
return AjaxResult.success(configValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前用户的配置
|
||||||
|
*/
|
||||||
|
@ApiOperation("保存当前用户的配置")
|
||||||
|
@Log(title = "用户配置", businessType = BusinessType.UPDATE)
|
||||||
|
@PostMapping("/saveCurrentUserConfig")
|
||||||
|
public AjaxResult saveCurrentUserConfig(@RequestParam String configKey, @RequestParam String configValue)
|
||||||
|
{
|
||||||
|
Long userId = SecurityUtils.getUserId();
|
||||||
|
int result = sysUserConfigService.saveConfigValueByUserIdAndKey(userId, configKey, configValue);
|
||||||
|
return result > 0 ? AjaxResult.success() : AjaxResult.error("保存失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.core.system.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.core.common.core.domain.BaseEntity;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户配置对象 sys_user_config
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_user_config")
|
||||||
|
public class SysUserConfig extends BaseEntity
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 配置ID */
|
||||||
|
@TableId
|
||||||
|
private Long configId;
|
||||||
|
|
||||||
|
/** 用户ID */
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/** 配置键名 */
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
/** 配置值 */
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
/** 备注 */
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/** 创建者 - 标记为非数据库字段 */
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/** 创建时间 - 使用自动填充 */
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/** 更新者 - 标记为非数据库字段 */
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String updateBy;
|
||||||
|
|
||||||
|
/** 更新时间 - 使用自动填充 */
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -28,6 +28,11 @@ public class MetaVo {
|
|||||||
*/
|
*/
|
||||||
private String link;
|
private String link;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单是否可见(用于前端侧边栏显示控制)
|
||||||
|
*/
|
||||||
|
private String visible;
|
||||||
|
|
||||||
public MetaVo() {}
|
public MetaVo() {}
|
||||||
|
|
||||||
public MetaVo(String title, String icon) {
|
public MetaVo(String title, String icon) {
|
||||||
@@ -56,6 +61,16 @@ public class MetaVo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MetaVo(String title, String icon, boolean noCache, String link, String visible) {
|
||||||
|
this.title = title;
|
||||||
|
this.icon = icon;
|
||||||
|
this.noCache = noCache;
|
||||||
|
if (StringUtils.ishttp(link)) {
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
this.visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isNoCache() {
|
public boolean isNoCache() {
|
||||||
return noCache;
|
return noCache;
|
||||||
}
|
}
|
||||||
@@ -87,4 +102,12 @@ public class MetaVo {
|
|||||||
public void setLink(String link) {
|
public void setLink(String link) {
|
||||||
this.link = link;
|
this.link = link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getVisible() {
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVisible(String visible) {
|
||||||
|
this.visible = visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.core.system.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.core.system.domain.SysUserConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户配置Mapper接口
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
public interface SysUserConfigMapper extends BaseMapper<SysUserConfig>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.core.system.service;
|
|||||||
import com.core.common.core.domain.TreeSelect;
|
import com.core.common.core.domain.TreeSelect;
|
||||||
import com.core.common.core.domain.entity.SysMenu;
|
import com.core.common.core.domain.entity.SysMenu;
|
||||||
import com.core.system.domain.vo.RouterVo;
|
import com.core.system.domain.vo.RouterVo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ public interface ISysMenuService {
|
|||||||
public List<SysMenu> selectMenuList(Long userId);
|
public List<SysMenu> selectMenuList(Long userId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据用户查询系统菜单列表
|
* 查询系统菜单列表
|
||||||
*
|
*
|
||||||
* @param menu 菜单信息
|
* @param menu 菜单信息
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
@@ -50,7 +49,7 @@ public interface ISysMenuService {
|
|||||||
* 根据用户ID查询菜单树信息
|
* 根据用户ID查询菜单树信息
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @return 菜单列表
|
* @return 菜单树信息
|
||||||
*/
|
*/
|
||||||
public List<SysMenu> selectMenuTreeByUserId(Long userId);
|
public List<SysMenu> selectMenuTreeByUserId(Long userId);
|
||||||
|
|
||||||
@@ -78,6 +77,14 @@ public interface ISysMenuService {
|
|||||||
*/
|
*/
|
||||||
public List<SysMenu> buildMenuTree(List<SysMenu> menus);
|
public List<SysMenu> buildMenuTree(List<SysMenu> menus);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建前端所需要树结构(包含完整路径)
|
||||||
|
*
|
||||||
|
* @param menus 菜单列表
|
||||||
|
* @return 树结构列表
|
||||||
|
*/
|
||||||
|
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建前端所需要下拉树结构
|
* 构建前端所需要下拉树结构
|
||||||
*
|
*
|
||||||
@@ -98,15 +105,15 @@ public interface ISysMenuService {
|
|||||||
* 是否存在菜单子节点
|
* 是否存在菜单子节点
|
||||||
*
|
*
|
||||||
* @param menuId 菜单ID
|
* @param menuId 菜单ID
|
||||||
* @return 结果 true 存在 false 不存在
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public boolean hasChildByMenuId(Long menuId);
|
public boolean hasChildByMenuId(Long menuId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询菜单是否存在角色
|
* 查询菜单使用数量
|
||||||
*
|
*
|
||||||
* @param menuId 菜单ID
|
* @param menuId 菜单ID
|
||||||
* @return 结果 true 存在 false 不存在
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public boolean checkMenuExistRole(Long menuId);
|
public boolean checkMenuExistRole(Long menuId);
|
||||||
|
|
||||||
@@ -141,4 +148,21 @@ public interface ISysMenuService {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public boolean checkMenuNameUnique(SysMenu menu);
|
public boolean checkMenuNameUnique(SysMenu menu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据菜单ID获取完整路径
|
||||||
|
*
|
||||||
|
* @param menuId 菜单ID
|
||||||
|
* @return 完整路径
|
||||||
|
*/
|
||||||
|
public String getMenuFullPath(Long menuId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路径参数生成完整路径
|
||||||
|
*
|
||||||
|
* @param parentId 父级菜单ID
|
||||||
|
* @param currentPath 当前路径
|
||||||
|
* @return 完整路径
|
||||||
|
*/
|
||||||
|
public String generateFullPath(Long parentId, String currentPath);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.core.system.service;
|
||||||
|
|
||||||
|
import com.core.system.domain.SysUserConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户配置Service接口
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
public interface ISysUserConfigService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 查询用户配置
|
||||||
|
*
|
||||||
|
* @param configId 用户配置ID
|
||||||
|
* @return 用户配置
|
||||||
|
*/
|
||||||
|
public SysUserConfig selectSysUserConfigById(Long configId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户配置列表
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 用户配置集合
|
||||||
|
*/
|
||||||
|
public List<SysUserConfig> selectSysUserConfigList(SysUserConfig sysUserConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增用户配置
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int insertSysUserConfig(SysUserConfig sysUserConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户配置
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int updateSysUserConfig(SysUserConfig sysUserConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除用户配置
|
||||||
|
*
|
||||||
|
* @param configIds 需要删除的用户配置ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteSysUserConfigByIds(Long[] configIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户配置信息
|
||||||
|
*
|
||||||
|
* @param configId 用户配置ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int deleteSysUserConfigById(Long configId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和配置键获取配置值
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @return 配置值
|
||||||
|
*/
|
||||||
|
public String selectConfigValueByUserIdAndKey(Long userId, String configKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和配置键保存配置
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @param configValue 配置值
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public int saveConfigValueByUserIdAndKey(Long userId, String configKey, String configValue);
|
||||||
|
}
|
||||||
@@ -147,13 +147,15 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
List<RouterVo> routers = new LinkedList<RouterVo>();
|
List<RouterVo> routers = new LinkedList<RouterVo>();
|
||||||
for (SysMenu menu : menus) {
|
for (SysMenu menu : menus) {
|
||||||
RouterVo router = new RouterVo();
|
RouterVo router = new RouterVo();
|
||||||
router.setHidden("1".equals(menu.getVisible()));
|
// 不再根据 visible 字段设置 hidden,确保所有有权限的路由都可用
|
||||||
|
// router.setHidden("1".equals(menu.getVisible()));
|
||||||
|
router.setHidden(false);
|
||||||
router.setName(getRouteName(menu));
|
router.setName(getRouteName(menu));
|
||||||
router.setPath(getRouterPath(menu));
|
router.setPath(getRouterPath(menu));
|
||||||
router.setComponent(getComponent(menu));
|
router.setComponent(getComponent(menu));
|
||||||
router.setQuery(menu.getQuery());
|
router.setQuery(menu.getQuery());
|
||||||
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()),
|
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()),
|
||||||
menu.getPath()));
|
menu.getPath(), menu.getVisible()));
|
||||||
List<SysMenu> cMenus = menu.getChildren();
|
List<SysMenu> cMenus = menu.getChildren();
|
||||||
if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
|
if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) {
|
||||||
router.setAlwaysShow(true);
|
router.setAlwaysShow(true);
|
||||||
@@ -167,12 +169,12 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
children.setComponent(menu.getComponent());
|
children.setComponent(menu.getComponent());
|
||||||
children.setName(getRouteName(menu.getRouteName(), menu.getPath()));
|
children.setName(getRouteName(menu.getRouteName(), menu.getPath()));
|
||||||
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(),
|
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(),
|
||||||
StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
|
StringUtils.equals("1", menu.getIsCache()), menu.getPath(), menu.getVisible()));
|
||||||
children.setQuery(menu.getQuery());
|
children.setQuery(menu.getQuery());
|
||||||
childrenList.add(children);
|
childrenList.add(children);
|
||||||
router.setChildren(childrenList);
|
router.setChildren(childrenList);
|
||||||
} else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
|
} else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) {
|
||||||
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
|
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, null, menu.getVisible()));
|
||||||
router.setPath("/");
|
router.setPath("/");
|
||||||
List<RouterVo> childrenList = new ArrayList<RouterVo>();
|
List<RouterVo> childrenList = new ArrayList<RouterVo>();
|
||||||
RouterVo children = new RouterVo();
|
RouterVo children = new RouterVo();
|
||||||
@@ -180,7 +182,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
children.setPath(routerPath);
|
children.setPath(routerPath);
|
||||||
children.setComponent(UserConstants.INNER_LINK);
|
children.setComponent(UserConstants.INNER_LINK);
|
||||||
children.setName(getRouteName(menu.getRouteName(), routerPath));
|
children.setName(getRouteName(menu.getRouteName(), routerPath));
|
||||||
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
|
children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), false, menu.getPath(), menu.getVisible()));
|
||||||
childrenList.add(children);
|
childrenList.add(children);
|
||||||
router.setChildren(childrenList);
|
router.setChildren(childrenList);
|
||||||
}
|
}
|
||||||
@@ -213,6 +215,36 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
return returnList;
|
return returnList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建前端所需要树结构(包含完整路径)
|
||||||
|
*
|
||||||
|
* @param menus 菜单列表
|
||||||
|
* @return 树结构列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<SysMenu> buildMenuTreeWithFullPath(List<SysMenu> menus) {
|
||||||
|
List<SysMenu> menuTree = buildMenuTree(menus);
|
||||||
|
// 为每个菜单项添加完整路径
|
||||||
|
addFullPathToMenuTree(menuTree);
|
||||||
|
return menuTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为菜单树添加完整路径
|
||||||
|
*
|
||||||
|
* @param menus 菜单树
|
||||||
|
*/
|
||||||
|
private void addFullPathToMenuTree(List<SysMenu> menus) {
|
||||||
|
for (SysMenu menu : menus) {
|
||||||
|
// 计算当前菜单的完整路径
|
||||||
|
menu.setFullPath(getMenuFullPath(menu.getMenuId()));
|
||||||
|
// 递归处理子菜单
|
||||||
|
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
|
||||||
|
addFullPathToMenuTree(menu.getChildren());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建前端所需要下拉树结构
|
* 构建前端所需要下拉树结构
|
||||||
*
|
*
|
||||||
@@ -493,4 +525,132 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
return StringUtils.replaceEach(path, new String[] {Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
|
return StringUtils.replaceEach(path, new String[] {Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
|
||||||
new String[] {"", "", "", "/", "/"});
|
new String[] {"", "", "", "/", "/"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据菜单ID获取完整路径
|
||||||
|
*
|
||||||
|
* @param menuId 菜单ID
|
||||||
|
* @return 完整路径
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getMenuFullPath(Long menuId) {
|
||||||
|
SysMenu menu = menuMapper.selectMenuById(menuId);
|
||||||
|
if (menu == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder fullPath = new StringBuilder();
|
||||||
|
buildMenuPath(menu, fullPath);
|
||||||
|
// 标准化完整路径,确保没有多余的斜杠
|
||||||
|
return normalizePath(fullPath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归构建菜单路径
|
||||||
|
*
|
||||||
|
* @param menu 菜单信息
|
||||||
|
* @param path 路径构建器
|
||||||
|
*/
|
||||||
|
private void buildMenuPath(SysMenu menu, StringBuilder path) {
|
||||||
|
if (menu == null || menu.getMenuId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是根节点,则递归查找父节点
|
||||||
|
if (menu.getParentId() != null && menu.getParentId() > 0) {
|
||||||
|
SysMenu parentMenu = menuMapper.selectMenuById(menu.getParentId());
|
||||||
|
if (parentMenu != null) {
|
||||||
|
buildMenuPath(parentMenu, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前菜单的路径,避免双斜杠
|
||||||
|
String currentPath = normalizePathSegment(menu.getPath());
|
||||||
|
if (currentPath != null && !currentPath.isEmpty()) {
|
||||||
|
if (path.length() > 0) {
|
||||||
|
// 确保路径之间只有一个斜杠分隔符
|
||||||
|
// 如果当前路径不为空,且当前路径不以斜杠结尾,则添加斜杠并追加路径
|
||||||
|
if (path.charAt(path.length() - 1) != '/') {
|
||||||
|
path.append("/").append(currentPath);
|
||||||
|
} else {
|
||||||
|
path.append(currentPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 对于第一个路径,直接追加
|
||||||
|
path.append(currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准化路径片段,移除开头的斜杠
|
||||||
|
*
|
||||||
|
* @param path 原始路径
|
||||||
|
* @return 标准化后的路径片段
|
||||||
|
*/
|
||||||
|
private String normalizePathSegment(String path) {
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除开头的斜杠
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
path = path.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准化完整路径,移除多余的斜杠
|
||||||
|
*
|
||||||
|
* @param path 原始路径
|
||||||
|
* @return 标准化后的完整路径
|
||||||
|
*/
|
||||||
|
private String normalizePath(String path) {
|
||||||
|
if (path == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理多个连续斜杠,将其替换为单个斜杠
|
||||||
|
while (path.contains("//")) {
|
||||||
|
path = path.replace("//", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据路径参数生成完整路径
|
||||||
|
*
|
||||||
|
* @param parentId 父级菜单ID
|
||||||
|
* @param currentPath 当前路径
|
||||||
|
* @return 完整路径
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String generateFullPath(Long parentId, String currentPath) {
|
||||||
|
StringBuilder fullPath = new StringBuilder();
|
||||||
|
|
||||||
|
// 如果有父级菜单,则先获取父级菜单的完整路径
|
||||||
|
if (parentId != null && parentId > 0) {
|
||||||
|
SysMenu parentMenu = menuMapper.selectMenuById(parentId);
|
||||||
|
if (parentMenu != null) {
|
||||||
|
String parentFullPath = getMenuFullPath(parentId);
|
||||||
|
if (StringUtils.isNotEmpty(parentFullPath)) {
|
||||||
|
fullPath.append(parentFullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前路径
|
||||||
|
if (StringUtils.isNotEmpty(currentPath)) {
|
||||||
|
if (fullPath.length() > 0) {
|
||||||
|
fullPath.append("/").append(currentPath);
|
||||||
|
} else {
|
||||||
|
fullPath.append(currentPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizePath(fullPath.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package com.core.system.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.core.system.domain.SysUserConfig;
|
||||||
|
import com.core.common.utils.StringUtils;
|
||||||
|
import com.core.system.mapper.SysUserConfigMapper;
|
||||||
|
import com.core.system.service.ISysUserConfigService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户配置Service业务层处理
|
||||||
|
*
|
||||||
|
* @author
|
||||||
|
* @date 2026-01-30
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, SysUserConfig> implements ISysUserConfigService
|
||||||
|
{
|
||||||
|
@Autowired
|
||||||
|
private SysUserConfigMapper sysUserConfigMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户配置
|
||||||
|
*
|
||||||
|
* @param configId 用户配置ID
|
||||||
|
* @return 用户配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SysUserConfig selectSysUserConfigById(Long configId)
|
||||||
|
{
|
||||||
|
return sysUserConfigMapper.selectById(configId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户配置列表
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 用户配置集合
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<SysUserConfig> selectSysUserConfigList(SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
LambdaQueryWrapper<SysUserConfig> lqw = new LambdaQueryWrapper<>();
|
||||||
|
lqw.eq(StringUtils.isNotEmpty(sysUserConfig.getConfigKey()), SysUserConfig::getConfigKey, sysUserConfig.getConfigKey())
|
||||||
|
.eq(sysUserConfig.getUserId() != null, SysUserConfig::getUserId, sysUserConfig.getUserId());
|
||||||
|
return sysUserConfigMapper.selectList(lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增用户配置
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int insertSysUserConfig(SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
return sysUserConfigMapper.insert(sysUserConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户配置
|
||||||
|
*
|
||||||
|
* @param sysUserConfig 用户配置
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int updateSysUserConfig(SysUserConfig sysUserConfig)
|
||||||
|
{
|
||||||
|
return sysUserConfigMapper.updateById(sysUserConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和配置键更新配置值
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @param configValue 配置值
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
private int updateConfigValueByUserIdAndKey(Long userId, String configKey, String configValue)
|
||||||
|
{
|
||||||
|
SysUserConfig config = new SysUserConfig();
|
||||||
|
config.setConfigValue(configValue);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<SysUserConfig> lqw = new LambdaQueryWrapper<>();
|
||||||
|
lqw.eq(SysUserConfig::getUserId, userId)
|
||||||
|
.eq(SysUserConfig::getConfigKey, configKey);
|
||||||
|
|
||||||
|
return sysUserConfigMapper.update(config, lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除用户配置
|
||||||
|
*
|
||||||
|
* @param configIds 需要删除的用户配置ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int deleteSysUserConfigByIds(Long[] configIds)
|
||||||
|
{
|
||||||
|
return sysUserConfigMapper.deleteBatchIds(java.util.Arrays.asList(configIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户配置信息
|
||||||
|
*
|
||||||
|
* @param configId 用户配置ID
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int deleteSysUserConfigById(Long configId)
|
||||||
|
{
|
||||||
|
return sysUserConfigMapper.deleteById(configId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和配置键获取配置值
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @return 配置值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String selectConfigValueByUserIdAndKey(Long userId, String configKey)
|
||||||
|
{
|
||||||
|
LambdaQueryWrapper<SysUserConfig> lqw = new LambdaQueryWrapper<>();
|
||||||
|
lqw.eq(SysUserConfig::getUserId, userId)
|
||||||
|
.eq(SysUserConfig::getConfigKey, configKey);
|
||||||
|
SysUserConfig config = sysUserConfigMapper.selectOne(lqw);
|
||||||
|
return config != null ? config.getConfigValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID和配置键保存配置
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param configKey 配置键
|
||||||
|
* @param configValue 配置值
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int saveConfigValueByUserIdAndKey(Long userId, String configKey, String configValue)
|
||||||
|
{
|
||||||
|
// 参数验证
|
||||||
|
if (userId == null || configKey == null) {
|
||||||
|
throw new IllegalArgumentException("用户ID和配置键不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LambdaQueryWrapper<SysUserConfig> lqw = new LambdaQueryWrapper<>();
|
||||||
|
lqw.eq(SysUserConfig::getUserId, userId)
|
||||||
|
.eq(SysUserConfig::getConfigKey, configKey);
|
||||||
|
SysUserConfig config = sysUserConfigMapper.selectOne(lqw);
|
||||||
|
|
||||||
|
if (config != null) {
|
||||||
|
// 更新现有配置,只更新配置值,避免更新审计字段
|
||||||
|
return updateConfigValueByUserIdAndKey(userId, configKey, configValue);
|
||||||
|
} else {
|
||||||
|
// 插入新配置
|
||||||
|
SysUserConfig newConfig = new SysUserConfig();
|
||||||
|
newConfig.setUserId(userId);
|
||||||
|
newConfig.setConfigKey(configKey);
|
||||||
|
newConfig.setConfigValue(configValue);
|
||||||
|
return sysUserConfigMapper.insert(newConfig);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录错误日志以便调试
|
||||||
|
System.err.println("保存用户配置时发生错误: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e; // 重新抛出异常让上层处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import com.core.common.utils.SecurityUtils;
|
|||||||
import com.core.common.utils.StringUtils;
|
import com.core.common.utils.StringUtils;
|
||||||
import com.core.common.utils.bean.BeanValidators;
|
import com.core.common.utils.bean.BeanValidators;
|
||||||
import com.core.common.utils.spring.SpringUtils;
|
import com.core.common.utils.spring.SpringUtils;
|
||||||
|
import com.core.common.utils.AuditFieldUtil; // 引入我们创建的工具类
|
||||||
import com.core.system.domain.SysPost;
|
import com.core.system.domain.SysPost;
|
||||||
import com.core.system.domain.SysUserPost;
|
import com.core.system.domain.SysUserPost;
|
||||||
import com.core.system.domain.SysUserRole;
|
import com.core.system.domain.SysUserRole;
|
||||||
@@ -55,6 +56,132 @@ public class SysUserServiceImpl implements ISysUserService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISysDeptService deptService;
|
private ISysDeptService deptService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增保存用户信息
|
||||||
|
*
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int insertUser(SysUser user) {
|
||||||
|
// 在保存前设置审计字段
|
||||||
|
AuditFieldUtil.setCreateInfo(user);
|
||||||
|
|
||||||
|
// 新增用户信息
|
||||||
|
int rows = userMapper.insertUser(user);
|
||||||
|
// 新增用户岗位关联
|
||||||
|
insertUserPost(user);
|
||||||
|
// 新增用户与角色管理
|
||||||
|
insertUserRole(user);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改保存用户信息
|
||||||
|
*
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public int updateUser(SysUser user) {
|
||||||
|
// 在更新前设置审计字段
|
||||||
|
AuditFieldUtil.setUpdateInfo(user);
|
||||||
|
|
||||||
|
Long userId = user.getUserId();
|
||||||
|
// 删除用户与角色关联
|
||||||
|
userRoleMapper.deleteUserRoleByUserId(userId);
|
||||||
|
// 新增用户与角色管理
|
||||||
|
insertUserRole(user);
|
||||||
|
// 删除用户与岗位关联
|
||||||
|
userPostMapper.deleteUserPostByUserId(userId);
|
||||||
|
// 新增用户与岗位管理
|
||||||
|
insertUserPost(user);
|
||||||
|
return userMapper.updateUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册用户信息
|
||||||
|
*
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean registerUser(SysUser user) {
|
||||||
|
// 在保存前设置审计字段
|
||||||
|
AuditFieldUtil.setCreateInfo(user);
|
||||||
|
|
||||||
|
return userMapper.insertUser(user) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户数据
|
||||||
|
*
|
||||||
|
* @param userList 用户数据列表
|
||||||
|
* @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
|
||||||
|
* @param operName 操作用户
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName) {
|
||||||
|
if (StringUtils.isNull(userList) || userList.size() == 0) {
|
||||||
|
throw new ServiceException("导入用户数据不能为空!");
|
||||||
|
}
|
||||||
|
int successNum = 0;
|
||||||
|
int failureNum = 0;
|
||||||
|
StringBuilder successMsg = new StringBuilder();
|
||||||
|
StringBuilder failureMsg = new StringBuilder();
|
||||||
|
for (SysUser user : userList) {
|
||||||
|
try {
|
||||||
|
// 验证是否存在这个用户
|
||||||
|
SysUser u = userMapper.selectUserByUserName(user.getUserName());
|
||||||
|
if (StringUtils.isNull(u)) {
|
||||||
|
BeanValidators.validateWithException(validator, user);
|
||||||
|
deptService.checkDeptDataScope(user.getDeptId());
|
||||||
|
String password = configService.selectConfigByKey("sys.user.initPassword");
|
||||||
|
user.setPassword(SecurityUtils.encryptPassword(password));
|
||||||
|
|
||||||
|
// 在导入用户时设置审计字段
|
||||||
|
AuditFieldUtil.setCreateInfo(user);
|
||||||
|
|
||||||
|
userMapper.insertUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
|
||||||
|
} else if (isUpdateSupport) {
|
||||||
|
BeanValidators.validateWithException(validator, user);
|
||||||
|
checkUserAllowed(u);
|
||||||
|
checkUserDataScope(u.getUserId());
|
||||||
|
deptService.checkDeptDataScope(user.getDeptId());
|
||||||
|
user.setUserId(u.getUserId());
|
||||||
|
|
||||||
|
// 在更新用户时设置审计字段
|
||||||
|
AuditFieldUtil.setUpdateInfo(user);
|
||||||
|
|
||||||
|
userMapper.updateUser(user);
|
||||||
|
successNum++;
|
||||||
|
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
|
||||||
|
} else {
|
||||||
|
failureNum++;
|
||||||
|
failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
failureNum++;
|
||||||
|
String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
|
||||||
|
failureMsg.append(msg + e.getMessage());
|
||||||
|
log.error(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failureNum > 0) {
|
||||||
|
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
||||||
|
throw new ServiceException(failureMsg.toString());
|
||||||
|
} else {
|
||||||
|
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
||||||
|
}
|
||||||
|
return successMsg.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以下是原有方法,保持不变
|
||||||
/**
|
/**
|
||||||
* 根据条件分页查询用户列表
|
* 根据条件分页查询用户列表
|
||||||
*
|
*
|
||||||
@@ -220,56 +347,6 @@ public class SysUserServiceImpl implements ISysUserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增保存用户信息
|
|
||||||
*
|
|
||||||
* @param user 用户信息
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public int insertUser(SysUser user) {
|
|
||||||
// 新增用户信息
|
|
||||||
int rows = userMapper.insertUser(user);
|
|
||||||
// 新增用户岗位关联
|
|
||||||
insertUserPost(user);
|
|
||||||
// 新增用户与角色管理
|
|
||||||
insertUserRole(user);
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册用户信息
|
|
||||||
*
|
|
||||||
* @param user 用户信息
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean registerUser(SysUser user) {
|
|
||||||
return userMapper.insertUser(user) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改保存用户信息
|
|
||||||
*
|
|
||||||
* @param user 用户信息
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public int updateUser(SysUser user) {
|
|
||||||
Long userId = user.getUserId();
|
|
||||||
// 删除用户与角色关联
|
|
||||||
userRoleMapper.deleteUserRoleByUserId(userId);
|
|
||||||
// 新增用户与角色管理
|
|
||||||
insertUserRole(user);
|
|
||||||
// 删除用户与岗位关联
|
|
||||||
userPostMapper.deleteUserPostByUserId(userId);
|
|
||||||
// 新增用户与岗位管理
|
|
||||||
insertUserPost(user);
|
|
||||||
return userMapper.updateUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户授权角色
|
* 用户授权角色
|
||||||
*
|
*
|
||||||
@@ -425,66 +502,6 @@ public class SysUserServiceImpl implements ISysUserService {
|
|||||||
return userMapper.deleteUserByIds(userIds);
|
return userMapper.deleteUserByIds(userIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入用户数据
|
|
||||||
*
|
|
||||||
* @param userList 用户数据列表
|
|
||||||
* @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
|
|
||||||
* @param operName 操作用户
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName) {
|
|
||||||
if (StringUtils.isNull(userList) || userList.size() == 0) {
|
|
||||||
throw new ServiceException("导入用户数据不能为空!");
|
|
||||||
}
|
|
||||||
int successNum = 0;
|
|
||||||
int failureNum = 0;
|
|
||||||
StringBuilder successMsg = new StringBuilder();
|
|
||||||
StringBuilder failureMsg = new StringBuilder();
|
|
||||||
for (SysUser user : userList) {
|
|
||||||
try {
|
|
||||||
// 验证是否存在这个用户
|
|
||||||
SysUser u = userMapper.selectUserByUserName(user.getUserName());
|
|
||||||
if (StringUtils.isNull(u)) {
|
|
||||||
BeanValidators.validateWithException(validator, user);
|
|
||||||
deptService.checkDeptDataScope(user.getDeptId());
|
|
||||||
String password = configService.selectConfigByKey("sys.user.initPassword");
|
|
||||||
user.setPassword(SecurityUtils.encryptPassword(password));
|
|
||||||
user.setCreateBy(operName);
|
|
||||||
userMapper.insertUser(user);
|
|
||||||
successNum++;
|
|
||||||
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 导入成功");
|
|
||||||
} else if (isUpdateSupport) {
|
|
||||||
BeanValidators.validateWithException(validator, user);
|
|
||||||
checkUserAllowed(u);
|
|
||||||
checkUserDataScope(u.getUserId());
|
|
||||||
deptService.checkDeptDataScope(user.getDeptId());
|
|
||||||
user.setUserId(u.getUserId());
|
|
||||||
user.setUpdateBy(operName);
|
|
||||||
userMapper.updateUser(user);
|
|
||||||
successNum++;
|
|
||||||
successMsg.append("<br/>" + successNum + "、账号 " + user.getUserName() + " 更新成功");
|
|
||||||
} else {
|
|
||||||
failureNum++;
|
|
||||||
failureMsg.append("<br/>" + failureNum + "、账号 " + user.getUserName() + " 已存在");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
failureNum++;
|
|
||||||
String msg = "<br/>" + failureNum + "、账号 " + user.getUserName() + " 导入失败:";
|
|
||||||
failureMsg.append(msg + e.getMessage());
|
|
||||||
log.error(msg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (failureNum > 0) {
|
|
||||||
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
|
|
||||||
throw new ServiceException(failureMsg.toString());
|
|
||||||
} else {
|
|
||||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
|
|
||||||
}
|
|
||||||
return successMsg.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扩展属性
|
* 扩展属性
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<result property="status" column="status"/>
|
<result property="status" column="status"/>
|
||||||
<result property="perms" column="perms"/>
|
<result property="perms" column="perms"/>
|
||||||
<result property="icon" column="icon"/>
|
<result property="icon" column="icon"/>
|
||||||
|
<result property="fullPath" column="full_path"/>
|
||||||
<result property="createBy" column="create_by"/>
|
<result property="createBy" column="create_by"/>
|
||||||
<result property="createTime" column="create_time"/>
|
<result property="createTime" column="create_time"/>
|
||||||
<result property="updateTime" column="update_time"/>
|
<result property="updateTime" column="update_time"/>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.core.system.mapper.SysUserConfigMapper">
|
||||||
|
|
||||||
|
<resultMap type="com.core.system.domain.SysUserConfig" id="SysUserConfigResult">
|
||||||
|
<result property="configId" column="config_id" />
|
||||||
|
<result property="userId" column="user_id" />
|
||||||
|
<result property="configKey" column="config_key" />
|
||||||
|
<result property="configValue" column="config_value" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="createTime" column="create_time" />
|
||||||
|
<result property="updateTime" column="update_time" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 根据用户ID和配置键名查询配置值 -->
|
||||||
|
<select id="selectConfigValueByUserIdAndKey" resultType="String">
|
||||||
|
SELECT config_value
|
||||||
|
FROM sys_user_config
|
||||||
|
WHERE user_id = #{userId} AND config_key = #{configKey}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户ID和配置键名查询完整配置 -->
|
||||||
|
<select id="selectByUserIdAndKey" resultMap="SysUserConfigResult">
|
||||||
|
SELECT config_id, user_id, config_key, config_value, remark, create_time, update_time
|
||||||
|
FROM sys_user_config
|
||||||
|
WHERE user_id = #{userId} AND config_key = #{configKey}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 根据用户ID和配置键更新配置值 -->
|
||||||
|
<update id="updateConfigValueByUserIdAndKey">
|
||||||
|
UPDATE sys_user_config
|
||||||
|
SET config_value = #{configValue}, update_time = CURRENT_TIMESTAMP
|
||||||
|
WHERE user_id = #{userId} AND config_key = #{configKey}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -195,6 +195,7 @@
|
|||||||
<if test="status != null and status != ''">status,</if>
|
<if test="status != null and status != ''">status,</if>
|
||||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||||
<if test="remark != null and remark != ''">remark,</if>
|
<if test="remark != null and remark != ''">remark,</if>
|
||||||
|
tenant_id,
|
||||||
create_time
|
create_time
|
||||||
)values(
|
)values(
|
||||||
<if test="userId != null and userId != ''">#{userId},</if>
|
<if test="userId != null and userId != ''">#{userId},</if>
|
||||||
@@ -209,6 +210,7 @@
|
|||||||
<if test="status != null and status != ''">#{status},</if>
|
<if test="status != null and status != ''">#{status},</if>
|
||||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||||
<if test="remark != null and remark != ''">#{remark},</if>
|
<if test="remark != null and remark != ''">#{remark},</if>
|
||||||
|
#{tenantId},
|
||||||
now()
|
now()
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
<groupId>org.apache.velocity</groupId>
|
<groupId>org.apache.velocity</groupId>
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- rabbitMQ -->
|
<!-- rabbitMQ -->
|
||||||
<!-- <dependency>
|
<!-- <dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -111,6 +112,13 @@
|
|||||||
<source>17</source>
|
<source>17</source>
|
||||||
<target>17</target>
|
<target>17</target>
|
||||||
<encoding>UTF-8</encoding>
|
<encoding>UTF-8</encoding>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface ITicketAppService {
|
public interface ITicketAppService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询号源列表
|
|
||||||
*
|
|
||||||
* @param params 查询参数
|
|
||||||
* @return 号源列表
|
|
||||||
*/
|
|
||||||
R<?> listTicket(Map<String, Object> params);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预约号源
|
* 预约号源
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,20 +4,28 @@ 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.administration.domain.Patient;
|
import com.openhis.administration.domain.Patient;
|
||||||
import com.openhis.administration.service.IPatientService;
|
import com.openhis.administration.service.IPatientService;
|
||||||
|
import com.openhis.appointmentmanage.domain.DoctorSchedule;
|
||||||
|
import com.openhis.appointmentmanage.mapper.DoctorScheduleMapper;
|
||||||
|
import com.openhis.appointmentmanage.service.IDoctorScheduleService;
|
||||||
|
import com.openhis.clinical.domain.Order;
|
||||||
import com.openhis.clinical.domain.Ticket;
|
import com.openhis.clinical.domain.Ticket;
|
||||||
|
import com.openhis.clinical.mapper.OrderMapper;
|
||||||
import com.openhis.clinical.service.ITicketService;
|
import com.openhis.clinical.service.ITicketService;
|
||||||
|
import com.openhis.web.appointmentmanage.appservice.IDoctorScheduleAppService;
|
||||||
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
|
import com.openhis.web.appointmentmanage.appservice.ITicketAppService;
|
||||||
import com.openhis.web.appointmentmanage.dto.TicketDto;
|
import com.openhis.web.appointmentmanage.dto.TicketDto;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
/**
|
/**
|
||||||
* 号源管理应用服务实现类
|
* 号源管理应用服务实现类
|
||||||
*
|
*
|
||||||
@@ -31,145 +39,14 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IPatientService patientService;
|
private IPatientService patientService;
|
||||||
|
@Resource
|
||||||
|
private IDoctorScheduleAppService doctorScheduleAppService;
|
||||||
|
@Resource
|
||||||
|
private DoctorScheduleMapper doctorScheduleMapper;
|
||||||
|
@Resource
|
||||||
|
private OrderMapper orderMapper;
|
||||||
|
|
||||||
/**
|
private static final Logger log = LoggerFactory.getLogger(TicketAppServiceImpl.class);
|
||||||
* 查询号源列表
|
|
||||||
*
|
|
||||||
* @param params 查询参数
|
|
||||||
* @return 号源列表
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public R<?> listTicket(Map<String, Object> params) {
|
|
||||||
// 调试日志:打印所有参数
|
|
||||||
System.out.println("=== listTicket方法收到的所有参数:===");
|
|
||||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
|
||||||
System.out.println(entry.getKey() + ": " + entry.getValue());
|
|
||||||
}
|
|
||||||
System.out.println("=================================");
|
|
||||||
// 构建查询条件
|
|
||||||
Ticket ticket = new Ticket();
|
|
||||||
// 设置查询参数
|
|
||||||
// 处理日期参数
|
|
||||||
if (params.containsKey("date")) {
|
|
||||||
String date = (String) params.get("date");
|
|
||||||
try {
|
|
||||||
// 将日期字符串转换为Date类型,设置到appointmentDate字段
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
||||||
Date appointmentDate = sdf.parse(date);
|
|
||||||
ticket.setAppointmentDate(appointmentDate);
|
|
||||||
System.out.println("设置的appointmentDate:" + appointmentDate);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 日期格式错误,忽略该参数
|
|
||||||
System.out.println("日期格式错误,忽略该参数:" + date + ",错误信息:" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 处理状态参数
|
|
||||||
if (params.containsKey("status")) {
|
|
||||||
String status = (String) params.get("status");
|
|
||||||
System.out.println("接收到的status参数:" + status);
|
|
||||||
if (!"all".equals(status) && !"全部".equals(status)) {
|
|
||||||
// 将中文状态转换为英文状态
|
|
||||||
if ("未预约".equals(status)) {
|
|
||||||
ticket.setStatus("unbooked");
|
|
||||||
} else if ("已预约".equals(status)) {
|
|
||||||
ticket.setStatus("booked");
|
|
||||||
} else if ("已取号".equals(status)) {
|
|
||||||
ticket.setStatus("checked");
|
|
||||||
} else if ("已取消".equals(status)) {
|
|
||||||
ticket.setStatus("cancelled");
|
|
||||||
} else if ("已锁定".equals(status)) {
|
|
||||||
ticket.setStatus("locked");
|
|
||||||
} else {
|
|
||||||
ticket.setStatus(status);
|
|
||||||
}
|
|
||||||
System.out.println("设置的status:" + ticket.getStatus());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (params.containsKey("name")) {
|
|
||||||
String name = (String) params.get("name");
|
|
||||||
ticket.setPatientName(name);
|
|
||||||
}
|
|
||||||
if (params.containsKey("card")) {
|
|
||||||
String card = (String) params.get("card");
|
|
||||||
ticket.setMedicalCard(card);
|
|
||||||
}
|
|
||||||
if (params.containsKey("phone")) {
|
|
||||||
String phone = (String) params.get("phone");
|
|
||||||
ticket.setPhone(phone);
|
|
||||||
}
|
|
||||||
if (params.containsKey("type")) {
|
|
||||||
String type = (String) params.get("type");
|
|
||||||
System.out.println("前端传递的type参数值:" + type);
|
|
||||||
if (!"all".equals(type)) {
|
|
||||||
// 类型映射转换:前端传递英文类型,数据库存储中文类型
|
|
||||||
if ("general".equals(type)) {
|
|
||||||
ticket.setTicketType("普通");
|
|
||||||
} else if ("expert".equals(type)) {
|
|
||||||
ticket.setTicketType("专家");
|
|
||||||
} else if ("普通".equals(type)) {
|
|
||||||
ticket.setTicketType("普通");
|
|
||||||
} else if ("专家".equals(type)) {
|
|
||||||
ticket.setTicketType("专家");
|
|
||||||
} else {
|
|
||||||
ticket.setTicketType(type);
|
|
||||||
}
|
|
||||||
System.out.println("转换后的ticketType值:" + ticket.getTicketType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手动实现分页查询,避免MyBatis-Plus自动COUNT查询的问题
|
|
||||||
int pageNum = params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1;
|
|
||||||
int pageSize = params.get("limit") != null ? Integer.valueOf(params.get("limit").toString()) : 10;
|
|
||||||
|
|
||||||
// 调试:输出构建的查询条件
|
|
||||||
System.out.println("构建的查询条件:ticketType=" + ticket.getTicketType() + ", status=" + ticket.getStatus() + ", appointmentDate=" + ticket.getAppointmentDate());
|
|
||||||
|
|
||||||
// 1. 获取所有符合条件的记录
|
|
||||||
List<Ticket> allTickets = ticketService.selectTicketList(ticket);
|
|
||||||
|
|
||||||
// 调试:输出查询到的所有记录
|
|
||||||
System.out.println("查询到的所有记录:" + allTickets);
|
|
||||||
if (!allTickets.isEmpty()) {
|
|
||||||
for (Ticket t : allTickets) {
|
|
||||||
System.out.println("记录详情:id=" + t.getId() + ", ticketType=" + t.getTicketType() + ", status=" + t.getStatus() + ", appointmentDate=" + t.getAppointmentDate() + ", deleteFlag=" + t.getDeleteFlag());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 计算总记录数
|
|
||||||
long total = allTickets.size();
|
|
||||||
System.out.println("手动计算的总记录数:" + total);
|
|
||||||
|
|
||||||
// 3. 手动分页
|
|
||||||
int start = (pageNum - 1) * pageSize;
|
|
||||||
int end = Math.min(start + pageSize, allTickets.size());
|
|
||||||
List<Ticket> pageTickets;
|
|
||||||
if (start >= end) {
|
|
||||||
pageTickets = new ArrayList<>();
|
|
||||||
} else {
|
|
||||||
pageTickets = allTickets.subList(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 转换为DTO
|
|
||||||
List<TicketDto> dtoList = pageTickets.stream().map(this::convertToDto).toList();
|
|
||||||
|
|
||||||
// 5. 构建响应数据,符合前端预期格式
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("list", dtoList);
|
|
||||||
result.put("records", dtoList); // 兼容前端框架(如Element UI)可能使用的records字段
|
|
||||||
result.put("total", total);
|
|
||||||
result.put("page", pageNum);
|
|
||||||
result.put("current", pageNum); // 兼容前端框架可能使用的current字段
|
|
||||||
result.put("limit", pageSize);
|
|
||||||
result.put("pageSize", pageSize); // 兼容前端框架可能使用的pageSize字段
|
|
||||||
result.put("size", pageSize); // 兼容前端框架可能使用的size字段
|
|
||||||
result.put("pageNum", pageNum); // 兼容前端框架可能使用的pageNum字段
|
|
||||||
result.put("pages", (int) Math.ceil((double) total / pageSize)); // 计算总页数
|
|
||||||
|
|
||||||
// 调试:输出响应数据
|
|
||||||
System.out.println("返回的响应数据:" + result);
|
|
||||||
|
|
||||||
return R.ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 预约号源
|
* 预约号源
|
||||||
@@ -179,35 +56,73 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> bookTicket(Map<String, Object> params) {
|
public R<?> bookTicket(Map<String, Object> params) {
|
||||||
|
// 1. 获取 ticketId 和 slotId
|
||||||
Long ticketId = null;
|
Long ticketId = null;
|
||||||
|
Long slotId = null;
|
||||||
if (params.get("ticketId") != null) {
|
if (params.get("ticketId") != null) {
|
||||||
ticketId = Long.valueOf(params.get("ticketId").toString());
|
ticketId = Long.valueOf(params.get("ticketId").toString());
|
||||||
}
|
}
|
||||||
if (ticketId == null) {
|
if (params.get("slotId") != null) {
|
||||||
return R.fail("参数错误");
|
slotId = Long.valueOf(params.get("slotId").toString());
|
||||||
}
|
}
|
||||||
|
// 2. 参数校验
|
||||||
|
if (ticketId == null || slotId == null) {
|
||||||
|
return R.fail("参数错误:ticketId 或 slotId 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 3. 执行原有的预约逻辑
|
||||||
int result = ticketService.bookTicket(params);
|
int result = ticketService.bookTicket(params);
|
||||||
return R.ok(result > 0 ? "预约成功" : "预约失败");
|
if (result > 0) {
|
||||||
|
// 4. 预约成功后,更新排班表状态
|
||||||
|
DoctorSchedule schedule = new DoctorSchedule();
|
||||||
|
schedule.setId(Math.toIntExact(slotId)); // 对应 XML 中的 WHERE id = #{id}
|
||||||
|
schedule.setIsStopped(true); // 设置为已预约
|
||||||
|
schedule.setStopReason("booked"); // 设置停用原因
|
||||||
|
|
||||||
|
// 执行更新
|
||||||
|
int updateCount = doctorScheduleMapper.updateDoctorSchedule(schedule);
|
||||||
|
|
||||||
|
if (updateCount > 0) {
|
||||||
|
return R.ok("预约成功并已更新排班状态");
|
||||||
|
} else {
|
||||||
|
// 如果更新失败,可能需要根据业务逻辑决定是否回滚预约
|
||||||
|
return R.ok("预约成功,但排班状态更新失败");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return R.fail("预约失败");
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return R.fail(e.getMessage());
|
// e.printStackTrace();
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return R.fail("系统异常:" + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消预约
|
* 取消预约
|
||||||
*
|
*
|
||||||
* @param ticketId 号源ID
|
* @param slotId 医生排班ID
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> cancelTicket(Long ticketId) {
|
public R<?> cancelTicket(Long slotId) {
|
||||||
if (ticketId == null) {
|
if (slotId == null) {
|
||||||
return R.fail("参数错误");
|
return R.fail("参数错误");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int result = ticketService.cancelTicket(ticketId);
|
ticketService.cancelTicket(slotId);
|
||||||
return R.ok(result > 0 ? "取消成功" : "取消失败");
|
DoctorSchedule schedule = new DoctorSchedule();
|
||||||
|
schedule.setId(Math.toIntExact(slotId)); // 对应 WHERE id = #{id}
|
||||||
|
schedule.setIsStopped(false); // 设置为 false (数据库对应 0)
|
||||||
|
schedule.setStopReason(""); // 将原因清空 (设为空字符串)
|
||||||
|
// 3. 调用自定义更新方法
|
||||||
|
int updateCount = doctorScheduleMapper.updateDoctorSchedule(schedule);
|
||||||
|
if (updateCount > 0) {
|
||||||
|
return R.ok("取消成功");
|
||||||
|
} else {
|
||||||
|
return R.ok("取消成功");
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return R.fail(e.getMessage());
|
return R.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -253,31 +168,87 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> listAllTickets() {
|
public R<?> listAllTickets() {
|
||||||
// 创建固定的测试数据,用于验证前端是否能展示数据
|
// 1. 从 AppService 获取排班数据
|
||||||
List<TicketDto> testTickets = new ArrayList<>();
|
R<?> response = doctorScheduleAppService.getDoctorScheduleList();
|
||||||
|
// 获取返回的 List 数据 (假设 R.ok 里的数据是 List<DoctorSchedule>)
|
||||||
|
List<DoctorSchedule> scheduleList = (List<DoctorSchedule>) response.getData();
|
||||||
|
|
||||||
// 创建5条测试数据
|
// 2. 转换数据为 TicketDto
|
||||||
for (int i = 1; i <= 5; i++) {
|
List<TicketDto> tickets = new ArrayList<>();
|
||||||
TicketDto dto = new TicketDto();
|
|
||||||
dto.setSlot_id((long) i);
|
if (scheduleList != null) {
|
||||||
dto.setBusNo("TEST0000" + i);
|
for (DoctorSchedule schedule : scheduleList) {
|
||||||
dto.setDepartment("内科");
|
TicketDto dto = new TicketDto();
|
||||||
dto.setDoctor("张三");
|
|
||||||
dto.setTicketType("expert");
|
// 基础信息映射
|
||||||
dto.setDateTime("08:00-08:50");
|
dto.setSlot_id(Long.valueOf(schedule.getId())); // Integer 转 Long
|
||||||
dto.setStatus("未预约");
|
dto.setBusNo(String.valueOf(schedule.getId())); // 生成一个业务编号
|
||||||
dto.setFee("150");
|
dto.setDepartment(String.valueOf(schedule.getDeptId())); // 如果有科室名建议关联查询,这里暂填ID
|
||||||
dto.setAppointmentDate(new Date());
|
dto.setDoctor(schedule.getDoctor());
|
||||||
testTickets.add(dto);
|
|
||||||
|
// 号源类型处理:根据挂号项目判断是普通号还是专家号
|
||||||
|
String registerItem = schedule.getRegisterItem();
|
||||||
|
if (registerItem != null && registerItem.contains("专家")) {
|
||||||
|
dto.setTicketType("expert");
|
||||||
|
} else {
|
||||||
|
dto.setTicketType("general");
|
||||||
|
}
|
||||||
|
// 时间处理:格式化为日期+时间范围,如 "2025-12-01 08:00-12:00"
|
||||||
|
String currentDate = LocalDate.now().toString(); // 或者从schedule中获取具体日期
|
||||||
|
String timeRange = schedule.getStartTime() + "-" + schedule.getEndTime();
|
||||||
|
dto.setDateTime(currentDate + " " + timeRange);
|
||||||
|
LocalTime nowTime = LocalTime.now();
|
||||||
|
LocalTime endTime = schedule.getEndTime();
|
||||||
|
String stopReason1 = schedule.getStopReason();
|
||||||
|
if ("cancelled".equals(stopReason1)||(endTime != null && nowTime.isAfter(endTime))) {
|
||||||
|
dto.setStatus("已停诊");
|
||||||
|
}else if (Boolean.TRUE.equals(schedule.getIsStopped())) {
|
||||||
|
// 获取原因并处理可能的空值
|
||||||
|
String stopReason = schedule.getStopReason();
|
||||||
|
// 使用 .equals() 比较内容,并将常量放在前面防止空指针
|
||||||
|
if ("booked".equals(stopReason)) {
|
||||||
|
dto.setStatus("已预约");
|
||||||
|
// --- 新增:获取患者信息 ---
|
||||||
|
List<Order> Order = orderMapper.selectOrderBySlotId(Long.valueOf(schedule.getId()));
|
||||||
|
Order latestOrder=Order.get(0);
|
||||||
|
|
||||||
|
if (latestOrder != null) {
|
||||||
|
dto.setPatientName(latestOrder.getPatientName());
|
||||||
|
dto.setPatientId(String.valueOf(latestOrder.getPatientId()));
|
||||||
|
dto.setPhone(latestOrder.getPhone());
|
||||||
|
}
|
||||||
|
// -----------------------
|
||||||
|
} else if ("checked".equals(stopReason)) {
|
||||||
|
dto.setStatus("已取号");
|
||||||
|
} else {
|
||||||
|
// 兜底逻辑:如果 is_stopped 为 true 但没有匹配到原因
|
||||||
|
dto.setStatus("不可预约");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// is_stopped 为 false 或 null 时
|
||||||
|
dto.setStatus("未预约");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用处理 (挂号费 + 诊疗费)
|
||||||
|
int totalFee = schedule.getRegisterFee() + schedule.getDiagnosisFee();
|
||||||
|
dto.setFee(String.valueOf(totalFee));
|
||||||
|
|
||||||
|
// 日期处理:LocalDateTime 转 Date
|
||||||
|
if (schedule.getCreateTime() != null) {
|
||||||
|
ZonedDateTime zdt = schedule.getCreateTime().atZone(ZoneId.systemDefault());
|
||||||
|
dto.setAppointmentDate(Date.from(zdt.toInstant()));
|
||||||
|
}
|
||||||
|
|
||||||
|
tickets.add(dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建响应数据
|
// 3. 封装分页响应结构
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("list", testTickets);
|
result.put("list", tickets);
|
||||||
result.put("total", testTickets.size());
|
result.put("total", tickets.size());
|
||||||
result.put("page", 1);
|
result.put("page", 1);
|
||||||
result.put("limit", 20);
|
result.put("limit", 20);
|
||||||
|
|
||||||
return R.ok(result);
|
return R.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,9 +293,6 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
case "cancelled":
|
case "cancelled":
|
||||||
dto.setStatus("已取消");
|
dto.setStatus("已取消");
|
||||||
break;
|
break;
|
||||||
case "locked":
|
|
||||||
dto.setStatus("已锁定");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
dto.setStatus(status);
|
dto.setStatus(status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,28 +22,6 @@ public class TicketController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ITicketAppService ticketAppService;
|
private ITicketAppService ticketAppService;
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询号源列表
|
|
||||||
*
|
|
||||||
* @param params 查询参数
|
|
||||||
* @return 号源列表
|
|
||||||
*/
|
|
||||||
@PostMapping("/list")
|
|
||||||
public R<?> listTicket(@RequestBody Map<String, Object> params) {
|
|
||||||
return ticketAppService.listTicket(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询号源列表(支持GET请求,兼容旧版本)
|
|
||||||
*
|
|
||||||
* @param params 查询参数
|
|
||||||
* @return 号源列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/list")
|
|
||||||
public R<?> listTicketByGet(@RequestParam Map<String, Object> params) {
|
|
||||||
return ticketAppService.listTicket(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询所有号源(用于测试)
|
* 查询所有号源(用于测试)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.core.common.core.domain.R;
|
|||||||
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
import com.openhis.web.basedatamanage.dto.OrganizationDto;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Organization 应该服务类
|
* Organization 应该服务类
|
||||||
@@ -17,13 +18,13 @@ public interface IOrganizationAppService {
|
|||||||
* @param pageSize 查询条数
|
* @param pageSize 查询条数
|
||||||
* @param name 科室名称
|
* @param name 科室名称
|
||||||
* @param typeEnum 科室类型
|
* @param typeEnum 科室类型
|
||||||
* @param classEnum 科室分类
|
* @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, String classEnum,
|
Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, List<String> classEnumList,
|
||||||
String sortField, String sortOrder, HttpServletRequest request);
|
String sortField, String sortOrder, HttpServletRequest request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -39,50 +39,69 @@ 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, String classEnum,
|
public Page<OrganizationDto> getOrganizationTree(Integer pageNo, Integer pageSize, String name, Integer typeEnum, List<String> classEnumList,
|
||||||
String sortField, String sortOrder, HttpServletRequest request) {
|
String sortField, String sortOrder, HttpServletRequest request) {
|
||||||
|
|
||||||
|
// 使用Page对象进行分页查询
|
||||||
|
Page<Organization> page = new Page<>(pageNo, pageSize);
|
||||||
|
|
||||||
// 创建查询条件
|
// 创建查询条件
|
||||||
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<Organization> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(Organization::getDeleteFlag, "0"); // 只查询未删除的记录
|
||||||
|
|
||||||
// 添加查询条件
|
|
||||||
if (StringUtils.isNotEmpty(name)) {
|
if (StringUtils.isNotEmpty(name)) {
|
||||||
queryWrapper.like(Organization::getName, name);
|
queryWrapper.like(Organization::getName, name);
|
||||||
}
|
}
|
||||||
if (typeEnum != null) {
|
if (typeEnum != null) {
|
||||||
queryWrapper.eq(Organization::getTypeEnum, typeEnum);
|
queryWrapper.eq(Organization::getTypeEnum, typeEnum);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(classEnum)) {
|
if (classEnumList != null && !classEnumList.isEmpty()) {
|
||||||
// 对于多选,需要处理逗号分隔的值
|
// 使用OR条件来匹配class_enum字段中包含任一值的记录
|
||||||
queryWrapper.and(wrapper -> {
|
queryWrapper.and(wrapper -> {
|
||||||
String[] classEnums = classEnum.split(",");
|
for (int i = 0; i < classEnumList.size(); i++) {
|
||||||
for (String cls : classEnums) {
|
String classEnum = classEnumList.get(i);
|
||||||
String trimmedCls = cls.trim();
|
if (i == 0) {
|
||||||
// 使用OR连接多个条件来匹配逗号分隔的值
|
// 第一个条件
|
||||||
wrapper.or().and(subWrapper -> {
|
wrapper.and(subWrapper -> {
|
||||||
subWrapper.eq(Organization::getClassEnum, trimmedCls)
|
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
||||||
.or()
|
.or() // 或者
|
||||||
.likeRight(Organization::getClassEnum, trimmedCls + ",")
|
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
||||||
.or()
|
.or() // 或者
|
||||||
.likeLeft(Organization::getClassEnum, "," + trimmedCls)
|
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
||||||
.or()
|
.or() // 或者
|
||||||
.like(Organization::getClassEnum, "," + trimmedCls + ",");
|
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// 后续条件使用OR连接
|
||||||
|
wrapper.or(subWrapper -> {
|
||||||
|
subWrapper.eq(Organization::getClassEnum, classEnum) // 精确匹配
|
||||||
|
.or() // 或者
|
||||||
|
.likeRight(Organization::getClassEnum, classEnum + ",") // 以"值,"开头
|
||||||
|
.or() // 或者
|
||||||
|
.likeLeft(Organization::getClassEnum, "," + classEnum) // 以",值"结尾
|
||||||
|
.or() // 或者
|
||||||
|
.like(Organization::getClassEnum, "," + classEnum + ","); // 在中间,被逗号包围
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Page对象
|
|
||||||
Page<Organization> page = new Page<>(pageNo, pageSize);
|
|
||||||
|
|
||||||
// 执行分页查询
|
// 执行分页查询
|
||||||
page = organizationService.page(page, queryWrapper);
|
Page<Organization> resultPage = organizationService.page(page, queryWrapper);
|
||||||
|
|
||||||
List<Organization> organizationList = page.getRecords();
|
// 将查询结果转为DTO并构建树结构
|
||||||
// 将机构列表转为树结构
|
List<Organization> organizationList = resultPage.getRecords();
|
||||||
List<OrganizationDto> orgTree = buildTree(organizationList);
|
List<OrganizationDto> orgTree = buildTree(organizationList);
|
||||||
Page<OrganizationDto> orgQueryDtoPage = new Page<>(pageNo, pageSize, page.getTotal());
|
|
||||||
orgQueryDtoPage.setRecords(orgTree);
|
// 创建结果分页对象
|
||||||
return orgQueryDtoPage;
|
Page<OrganizationDto> result = new Page<>();
|
||||||
|
result.setRecords(orgTree);
|
||||||
|
result.setTotal(resultPage.getTotal());
|
||||||
|
result.setSize(resultPage.getSize());
|
||||||
|
result.setCurrent(resultPage.getCurrent());
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -92,6 +92,20 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
|
|||||||
bizUser.setPassword(SecurityUtils.encryptPassword(userAndPractitionerDto.getPassword())); // 密码
|
bizUser.setPassword(SecurityUtils.encryptPassword(userAndPractitionerDto.getPassword())); // 密码
|
||||||
bizUser.setStatus(userAndPractitionerDto.getStatus()); // 状态
|
bizUser.setStatus(userAndPractitionerDto.getStatus()); // 状态
|
||||||
bizUser.setRemark(userAndPractitionerDto.getRemark()); // 备注
|
bizUser.setRemark(userAndPractitionerDto.getRemark()); // 备注
|
||||||
|
|
||||||
|
// 确保tenantId被设置,如果自动填充机制未能正确填充
|
||||||
|
if (bizUser.getTenantId() == null) {
|
||||||
|
try {
|
||||||
|
bizUser.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果无法获取当前登录用户,则使用默认租户ID
|
||||||
|
bizUser.setTenantId(1); // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保审计字段被设置
|
||||||
|
com.core.common.utils.AuditFieldUtil.setCreateInfo(bizUser);
|
||||||
|
|
||||||
iBizUserService.save(bizUser);
|
iBizUserService.save(bizUser);
|
||||||
Long userId =
|
Long userId =
|
||||||
iBizUserService.getOne(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserName, userName)).getUserId(); // 用户id
|
iBizUserService.getOne(new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserName, userName)).getUserId(); // 用户id
|
||||||
@@ -119,6 +133,17 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
|
|||||||
practitioner.setAddress(userAndPractitionerDto.getAddress()); // 地址
|
practitioner.setAddress(userAndPractitionerDto.getAddress()); // 地址
|
||||||
practitioner.setYbNo(userAndPractitionerDto.getYbNo()); // 医保码
|
practitioner.setYbNo(userAndPractitionerDto.getYbNo()); // 医保码
|
||||||
practitioner.setUserId(userId); // 系统用户id
|
practitioner.setUserId(userId); // 系统用户id
|
||||||
|
|
||||||
|
// 确保tenantId被设置,如果自动填充机制未能正确填充
|
||||||
|
if (practitioner.getTenantId() == null) {
|
||||||
|
try {
|
||||||
|
practitioner.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果无法获取当前登录用户,则使用默认租户ID
|
||||||
|
practitioner.setTenantId(1); // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 责任科室
|
// 责任科室
|
||||||
List<PractitionerOrgAndLocationDto> responsibilityOrgDtoList =
|
List<PractitionerOrgAndLocationDto> responsibilityOrgDtoList =
|
||||||
userAndPractitionerDto.getResponsibilityOrgDtoList();
|
userAndPractitionerDto.getResponsibilityOrgDtoList();
|
||||||
@@ -295,6 +320,20 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
|
|||||||
bizUser.setSex(sex); // 性别
|
bizUser.setSex(sex); // 性别
|
||||||
bizUser.setStatus(userAndPractitionerDto.getStatus()); // 状态
|
bizUser.setStatus(userAndPractitionerDto.getStatus()); // 状态
|
||||||
bizUser.setRemark(userAndPractitionerDto.getRemark()); // 备注
|
bizUser.setRemark(userAndPractitionerDto.getRemark()); // 备注
|
||||||
|
|
||||||
|
// 确保tenantId被设置,如果自动填充机制未能正确填充
|
||||||
|
if (bizUser.getTenantId() == null) {
|
||||||
|
try {
|
||||||
|
bizUser.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果无法获取当前登录用户,则使用默认租户ID
|
||||||
|
bizUser.setTenantId(1); // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保审计字段被设置
|
||||||
|
com.core.common.utils.AuditFieldUtil.setUpdateInfo(bizUser);
|
||||||
|
|
||||||
iBizUserService.update(bizUser, new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserId, userId));
|
iBizUserService.update(bizUser, new LambdaQueryWrapper<BizUser>().eq(BizUser::getUserId, userId));
|
||||||
// 先删除,再新增 sys_user_role
|
// 先删除,再新增 sys_user_role
|
||||||
practitionerAppAppMapper.delUserRole(userId);
|
practitionerAppAppMapper.delUserRole(userId);
|
||||||
@@ -316,6 +355,17 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
|
|||||||
practitioner.setAddress(userAndPractitionerDto.getAddress()); // 地址
|
practitioner.setAddress(userAndPractitionerDto.getAddress()); // 地址
|
||||||
practitioner.setYbNo(userAndPractitionerDto.getYbNo()); // 医保码
|
practitioner.setYbNo(userAndPractitionerDto.getYbNo()); // 医保码
|
||||||
practitioner.setUserId(userId); // 系统用户id
|
practitioner.setUserId(userId); // 系统用户id
|
||||||
|
|
||||||
|
// 确保tenantId被设置,如果自动填充机制未能正确填充
|
||||||
|
if (practitioner.getTenantId() == null) {
|
||||||
|
try {
|
||||||
|
practitioner.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果无法获取当前登录用户,则使用默认租户ID
|
||||||
|
practitioner.setTenantId(1); // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 责任科室
|
// 责任科室
|
||||||
List<PractitionerOrgAndLocationDto> responsibilityOrgDtoList =
|
List<PractitionerOrgAndLocationDto> responsibilityOrgDtoList =
|
||||||
userAndPractitionerDto.getResponsibilityOrgDtoList();
|
userAndPractitionerDto.getResponsibilityOrgDtoList();
|
||||||
@@ -446,6 +496,17 @@ public class PractitionerAppServiceImpl implements IPractitionerAppService {
|
|||||||
Practitioner practitioner = new Practitioner();
|
Practitioner practitioner = new Practitioner();
|
||||||
practitioner.setId(practitionerId);
|
practitioner.setId(practitionerId);
|
||||||
practitioner.setOrgId(orgId);
|
practitioner.setOrgId(orgId);
|
||||||
|
|
||||||
|
// 确保tenantId被设置,如果自动填充机制未能正确填充
|
||||||
|
if (practitioner.getTenantId() == null) {
|
||||||
|
try {
|
||||||
|
practitioner.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果无法获取当前登录用户,则使用默认租户ID
|
||||||
|
practitioner.setTenantId(1); // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iPractitionerService.updateById(practitioner);
|
iPractitionerService.updateById(practitioner);
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"切换科室"}));
|
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[] {"切换科室"}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package com.openhis.web.basedatamanage.controller;
|
|||||||
|
|
||||||
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.StringUtils;
|
||||||
import com.core.common.utils.MessageUtils;
|
import com.core.common.utils.MessageUtils;
|
||||||
import com.openhis.common.constant.PromptMsgConstant;
|
import com.openhis.common.constant.PromptMsgConstant;
|
||||||
import com.openhis.web.basedatamanage.appservice.IOrganizationAppService;
|
import com.openhis.web.basedatamanage.appservice.IOrganizationAppService;
|
||||||
@@ -16,6 +17,8 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 机构管理controller
|
* 机构管理controller
|
||||||
@@ -43,7 +46,7 @@ public class OrganizationController {
|
|||||||
* @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 请求对象
|
||||||
@@ -57,8 +60,15 @@ public class OrganizationController {
|
|||||||
@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参数,支持逗号分隔的多个值
|
||||||
|
List<String> classEnumList = null;
|
||||||
|
if (StringUtils.isNotBlank(classEnum)) {
|
||||||
|
classEnumList = Arrays.asList(classEnum.split(","));
|
||||||
|
}
|
||||||
|
|
||||||
Page<OrganizationDto> organizationTree =
|
Page<OrganizationDto> organizationTree =
|
||||||
iOrganizationAppService.getOrganizationTree(pageNo, pageSize, name, typeEnum, classEnum, sortField, sortOrder, request);
|
iOrganizationAppService.getOrganizationTree(pageNo, pageSize, name, 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[] {"机构信息"}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ public class UserAndPractitionerDto {
|
|||||||
private Long orgId;
|
private Long orgId;
|
||||||
private String orgId_dictText;
|
private String orgId_dictText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 责任科室
|
* 责任科室
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -73,8 +73,10 @@ public class OutpatientPricingAppServiceImpl implements IOutpatientPricingAppSer
|
|||||||
} else {
|
} else {
|
||||||
adviceTypes = List.of(1, 2, 3);
|
adviceTypes = List.of(1, 2, 3);
|
||||||
}
|
}
|
||||||
|
// 门诊划价:不要强制 pricingFlag=1 参与过滤(wor_activity_definition.pricing_flag 可能为 0),
|
||||||
|
// 否则会导致诊疗项目(adviceType=3)查询结果为空 records=[]
|
||||||
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
|
return iDoctorStationAdviceAppService.getAdviceBaseInfo(adviceBaseDto, searchKey, locationId, null,
|
||||||
organizationId, pageNo, pageSize, Whether.YES.getValue(), adviceTypes, null);
|
organizationId, pageNo, pageSize, null, adviceTypes, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ public class OutpatientRefundAppServiceImpl implements IOutpatientRefundAppServi
|
|||||||
newRefundRequest.setStatusEnum(RequestStatus.CANCELLED.getValue());
|
newRefundRequest.setStatusEnum(RequestStatus.CANCELLED.getValue());
|
||||||
newRefundRequest.setRefundDeviceId(deviceRequest.getId()); // 关联原ID
|
newRefundRequest.setRefundDeviceId(deviceRequest.getId()); // 关联原ID
|
||||||
newRefundRequest.setPrescriptionNo("T" + deviceRequest.getPrescriptionNo());
|
newRefundRequest.setPrescriptionNo("T" + deviceRequest.getPrescriptionNo());
|
||||||
|
newRefundRequest.setTenantId(deviceRequest.getTenantId()); // 显式设置租户ID
|
||||||
deviceRequestService.save(newRefundRequest);
|
deviceRequestService.save(newRefundRequest);
|
||||||
Long newRequestId = newRefundRequest.getId();
|
Long newRequestId = newRefundRequest.getId();
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import com.openhis.web.chargemanage.dto.PatientMetadata;
|
|||||||
import com.openhis.web.chargemanage.dto.PractitionerMetadata;
|
import com.openhis.web.chargemanage.dto.PractitionerMetadata;
|
||||||
import com.openhis.web.chargemanage.dto.ReprintRegistrationDto;
|
import com.openhis.web.chargemanage.dto.ReprintRegistrationDto;
|
||||||
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
import com.openhis.web.chargemanage.mapper.OutpatientRegistrationAppMapper;
|
||||||
|
import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
|
||||||
|
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
|
||||||
import com.openhis.web.paymentmanage.appservice.IPaymentRecService;
|
import com.openhis.web.paymentmanage.appservice.IPaymentRecService;
|
||||||
import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
|
import com.openhis.web.paymentmanage.dto.CancelPaymentDto;
|
||||||
import com.openhis.web.paymentmanage.dto.CancelRegPaymentDto;
|
import com.openhis.web.paymentmanage.dto.CancelRegPaymentDto;
|
||||||
@@ -38,12 +40,15 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 门诊挂号 应用实现类
|
* 门诊挂号 应用实现类
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistrationAppService {
|
public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistrationAppService {
|
||||||
|
|
||||||
@@ -77,6 +82,9 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
@Resource
|
@Resource
|
||||||
IPatientIdentifierService patientIdentifierService;
|
IPatientIdentifierService patientIdentifierService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
TriageCandidateExclusionService triageCandidateExclusionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 门诊挂号 - 查询患者信息
|
* 门诊挂号 - 查询患者信息
|
||||||
*
|
*
|
||||||
@@ -308,6 +316,47 @@ public class OutpatientRegistrationAppServiceImpl implements IOutpatientRegistra
|
|||||||
new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(),
|
new Page<>(pageNo, pageSize), EncounterClass.AMB.getValue(), EncounterStatus.IN_PROGRESS.getValue(),
|
||||||
ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper,
|
ParticipantType.ADMITTER.getCode(), ParticipantType.REGISTRATION_DOCTOR.getCode(), queryWrapper,
|
||||||
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue());
|
ChargeItemContext.REGISTER.getValue(), PaymentStatus.SUCCESS.getValue());
|
||||||
|
|
||||||
|
// 过滤候选池排除列表(如果是从智能候选池查询,排除已加入队列的患者)
|
||||||
|
// 检查请求参数 excludeFromCandidatePool,如果为 true 或未设置,则过滤排除列表
|
||||||
|
String excludeParam = request.getParameter("excludeFromCandidatePool");
|
||||||
|
boolean shouldExclude = excludeParam == null || "true".equalsIgnoreCase(excludeParam);
|
||||||
|
if (shouldExclude && currentDayEncounter != null && !currentDayEncounter.getRecords().isEmpty()) {
|
||||||
|
try {
|
||||||
|
// 获取当前租户和日期
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
|
||||||
|
// 查询排除列表
|
||||||
|
List<TriageCandidateExclusion> exclusions = triageCandidateExclusionService.list(
|
||||||
|
new LambdaQueryWrapper<TriageCandidateExclusion>()
|
||||||
|
.eq(TriageCandidateExclusion::getTenantId, tenantId)
|
||||||
|
.eq(TriageCandidateExclusion::getExclusionDate, today)
|
||||||
|
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (exclusions != null && !exclusions.isEmpty()) {
|
||||||
|
// 构建排除的 encounterId 集合
|
||||||
|
Set<Long> excludedEncounterIds = exclusions.stream()
|
||||||
|
.map(TriageCandidateExclusion::getEncounterId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 过滤结果
|
||||||
|
List<CurrentDayEncounterDto> filteredRecords = currentDayEncounter.getRecords().stream()
|
||||||
|
.filter(e -> e.getEncounterId() == null || !excludedEncounterIds.contains(e.getEncounterId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 更新分页结果
|
||||||
|
currentDayEncounter.setRecords(filteredRecords);
|
||||||
|
currentDayEncounter.setTotal(filteredRecords.size());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果过滤失败,记录日志但不影响正常查询
|
||||||
|
log.warn("过滤候选池排除列表失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentDayEncounter.getRecords().forEach(e -> {
|
currentDayEncounter.getRecords().forEach(e -> {
|
||||||
// 性别
|
// 性别
|
||||||
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));
|
e.setGenderEnum_enumText(EnumUtils.getInfoByValue(AdministrativeGender.class, e.getGenderEnum()));
|
||||||
|
|||||||
@@ -118,7 +118,24 @@ public class CheckPackageAppServiceImpl implements ICheckPackageAppService {
|
|||||||
return R.ok(checkPackage.getId(), "保存成功");
|
return R.ok(checkPackage.getId(), "保存成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("新增检查套餐失败", e);
|
log.error("新增检查套餐失败", e);
|
||||||
return R.fail("新增检查套餐失败: " + e.getMessage());
|
|
||||||
|
// 捕获PostgreSQL唯一约束冲突异常
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
if (errorMessage != null) {
|
||||||
|
// PostgreSQL唯一约束错误通常包含 "duplicate key value" 或约束名称
|
||||||
|
if (errorMessage.contains("duplicate key value") ||
|
||||||
|
errorMessage.contains("违反唯一约束") ||
|
||||||
|
errorMessage.contains("unique constraint")) {
|
||||||
|
// 提取约束名称或字段信息
|
||||||
|
String constraintInfo = "";
|
||||||
|
if (errorMessage.contains("check_package")) {
|
||||||
|
constraintInfo = "套餐名称或编码";
|
||||||
|
}
|
||||||
|
return R.fail("保存失败:数据重复," + constraintInfo + "已存在。详细错误:" + errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.fail("新增检查套餐失败: " + errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
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.core.domain.entity.SysDictData;
|
import com.core.common.core.domain.entity.SysDictData;
|
||||||
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
import com.core.common.utils.*;
|
import com.core.common.utils.*;
|
||||||
import com.core.common.utils.bean.BeanUtils;
|
import com.core.common.utils.bean.BeanUtils;
|
||||||
import com.core.common.utils.poi.ExcelUtil;
|
import com.core.common.utils.poi.ExcelUtil;
|
||||||
@@ -241,6 +242,8 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
|
|||||||
// 显式设置新增的字段
|
// 显式设置新增的字段
|
||||||
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
|
activityDefinition.setSortOrder(diagnosisTreatmentUpDto.getSortOrder());
|
||||||
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
|
activityDefinition.setServiceRange(diagnosisTreatmentUpDto.getServiceRange());
|
||||||
|
// 显式设置划价标记(避免前端字段/类型差异导致 copyProperties 后仍为默认值)
|
||||||
|
activityDefinition.setPricingFlag(diagnosisTreatmentUpDto.getPricingFlag());
|
||||||
|
|
||||||
// 拼音码
|
// 拼音码
|
||||||
activityDefinition.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(activityDefinition.getName()));
|
activityDefinition.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(activityDefinition.getName()));
|
||||||
@@ -400,6 +403,26 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
|
|||||||
// 新增外来诊疗目录
|
// 新增外来诊疗目录
|
||||||
activityDefinition.setStatusEnum(PublicationStatus.ACTIVE.getValue());
|
activityDefinition.setStatusEnum(PublicationStatus.ACTIVE.getValue());
|
||||||
|
|
||||||
|
// 显式设置创建者和租户ID,确保插入时不为null
|
||||||
|
|
||||||
|
String createBy = "system";
|
||||||
|
Integer tenantId = null;
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
createBy = loginUser.getUsername();
|
||||||
|
tenantId = loginUser.getTenantId();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取失败,使用默认值
|
||||||
|
}
|
||||||
|
activityDefinition.setCreateBy(createBy);
|
||||||
|
activityDefinition.setTenantId(tenantId != null ? tenantId : 1); // 默认租户ID为1
|
||||||
|
// 确保创建时间不为null
|
||||||
|
if (activityDefinition.getCreateTime() == null) {
|
||||||
|
activityDefinition.setCreateTime(new java.util.Date());
|
||||||
|
}
|
||||||
|
|
||||||
// 检查编码是否已存在
|
// 检查编码是否已存在
|
||||||
List<ActivityDefinition> existingDefinitions = activityDefinitionMapper.selectList(
|
List<ActivityDefinition> existingDefinitions = activityDefinitionMapper.selectList(
|
||||||
new LambdaQueryWrapper<ActivityDefinition>()
|
new LambdaQueryWrapper<ActivityDefinition>()
|
||||||
@@ -468,7 +491,8 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
|
|||||||
for (DiagnosisTreatmentImportDto importDto : importDtoList) {
|
for (DiagnosisTreatmentImportDto importDto : importDtoList) {
|
||||||
// 创建诊疗定义
|
// 创建诊疗定义
|
||||||
ActivityDefinition activityDefinition = createActivityDefinitionEntity(importDto, orgId);
|
ActivityDefinition activityDefinition = createActivityDefinitionEntity(importDto, orgId);
|
||||||
activityDefinitionService.save(activityDefinition);
|
// 使用 addDiagnosisTreatment 方法,确保字段完整性
|
||||||
|
activityDefinitionService.addDiagnosisTreatment(activityDefinition);
|
||||||
// 创建费用定价和详情
|
// 创建费用定价和详情
|
||||||
chargeItemDefinitionService.addChargeItemDefinitionAndDetail(importDto.getName(), importDto.getTypeCode(),
|
chargeItemDefinitionService.addChargeItemDefinitionAndDetail(importDto.getName(), importDto.getTypeCode(),
|
||||||
importDto.getYbType(), importDto.getPermittedUnitCode(), null, importDto.getRetailPrice(),
|
importDto.getYbType(), importDto.getPermittedUnitCode(), null, importDto.getRetailPrice(),
|
||||||
@@ -624,6 +648,24 @@ public class DiagTreatMAppServiceImpl implements IDiagTreatMAppService {
|
|||||||
.setYbMatchFlag(CommonUtil.tryParseInt(importDto.getYbMatchFlag()))
|
.setYbMatchFlag(CommonUtil.tryParseInt(importDto.getYbMatchFlag()))
|
||||||
.setStatusEnum(PublicationStatus.ACTIVE.getValue())
|
.setStatusEnum(PublicationStatus.ACTIVE.getValue())
|
||||||
.setChrgitmLv(CommonUtil.tryParseInt(importDto.getChrgitmLv()));
|
.setChrgitmLv(CommonUtil.tryParseInt(importDto.getChrgitmLv()));
|
||||||
|
// 显式设置创建者和租户ID,确保插入时不为null
|
||||||
|
String createBy = "system";
|
||||||
|
Integer tenantId = null;
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
createBy = loginUser.getUsername();
|
||||||
|
tenantId = loginUser.getTenantId();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取失败,使用默认值
|
||||||
|
}
|
||||||
|
activityDefinition.setCreateBy(createBy);
|
||||||
|
activityDefinition.setTenantId(tenantId != null ? tenantId : 1); // 默认租户ID为1
|
||||||
|
// 确保创建时间不为null
|
||||||
|
if (activityDefinition.getCreateTime() == null) {
|
||||||
|
activityDefinition.setCreateTime(new java.util.Date());
|
||||||
|
}
|
||||||
return activityDefinition;
|
return activityDefinition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.openhis.web.datadictionary.appservice.impl;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.core.common.utils.bean.BeanUtils;
|
import com.core.common.utils.bean.BeanUtils;
|
||||||
import com.openhis.administration.domain.ChargeItemDefDetail;
|
import com.openhis.administration.domain.ChargeItemDefDetail;
|
||||||
import com.openhis.administration.domain.ChargeItemDefinition;
|
import com.openhis.administration.domain.ChargeItemDefinition;
|
||||||
@@ -17,6 +19,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +51,9 @@ public class ItemDefinitionServiceImpl implements IItemDefinitionService {
|
|||||||
ChargeItemDefinition chargeItemDefinition = new ChargeItemDefinition();
|
ChargeItemDefinition chargeItemDefinition = new ChargeItemDefinition();
|
||||||
BeanUtils.copyProperties(itemUpFromDirectoryDto, chargeItemDefinition);
|
BeanUtils.copyProperties(itemUpFromDirectoryDto, chargeItemDefinition);
|
||||||
|
|
||||||
|
// 显式设置创建者、创建时间和租户ID,确保插入时不为null
|
||||||
|
setRequiredFields(chargeItemDefinition);
|
||||||
|
|
||||||
boolean insertCIDSuccess = chargeItemDefinitionService.save(chargeItemDefinition);
|
boolean insertCIDSuccess = chargeItemDefinitionService.save(chargeItemDefinition);
|
||||||
|
|
||||||
if (insertCIDSuccess) {
|
if (insertCIDSuccess) {
|
||||||
@@ -86,6 +92,9 @@ public class ItemDefinitionServiceImpl implements IItemDefinitionService {
|
|||||||
|
|
||||||
shargeItemDefDetails.add(chargeItemDefDetail3);
|
shargeItemDefDetails.add(chargeItemDefDetail3);
|
||||||
|
|
||||||
|
// 批量设置必需字段(tenant_id、create_by、create_time)
|
||||||
|
setRequiredFieldsForDetailList(shargeItemDefDetails);
|
||||||
|
|
||||||
return chargeItemDefDetailService.saveBatch(shargeItemDefDetails);
|
return chargeItemDefDetailService.saveBatch(shargeItemDefDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,4 +148,55 @@ public class ItemDefinitionServiceImpl implements IItemDefinitionService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置必需的字段(tenant_id、create_by、create_time),确保插入时不为null
|
||||||
|
*
|
||||||
|
* @param chargeItemDefinition 费用定价对象
|
||||||
|
*/
|
||||||
|
private void setRequiredFields(ChargeItemDefinition chargeItemDefinition) {
|
||||||
|
String createBy = "system";
|
||||||
|
Integer tenantId = null;
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
createBy = loginUser.getUsername();
|
||||||
|
tenantId = loginUser.getTenantId();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取失败,使用默认值
|
||||||
|
}
|
||||||
|
chargeItemDefinition.setCreateBy(createBy != null ? createBy : "system");
|
||||||
|
chargeItemDefinition.setTenantId(tenantId != null ? tenantId : 1);
|
||||||
|
if (chargeItemDefinition.getCreateTime() == null) {
|
||||||
|
chargeItemDefinition.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置费用定价详情列表的必需字段(tenant_id、create_by、create_time)
|
||||||
|
*
|
||||||
|
* @param chargeItemDefDetailList 费用定价详情对象列表
|
||||||
|
*/
|
||||||
|
private void setRequiredFieldsForDetailList(List<ChargeItemDefDetail> chargeItemDefDetailList) {
|
||||||
|
String createBy = "system";
|
||||||
|
Integer tenantId = null;
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
createBy = loginUser.getUsername();
|
||||||
|
tenantId = loginUser.getTenantId();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取失败,使用默认值
|
||||||
|
}
|
||||||
|
Date now = new Date();
|
||||||
|
for (ChargeItemDefDetail detail : chargeItemDefDetailList) {
|
||||||
|
detail.setCreateBy(createBy != null ? createBy : "system");
|
||||||
|
detail.setTenantId(tenantId != null ? tenantId : 1);
|
||||||
|
if (detail.getCreateTime() == null) {
|
||||||
|
detail.setCreateTime(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.openhis.web.datadictionary.dto;
|
package com.openhis.web.datadictionary.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.openhis.common.annotation.Dict;
|
import com.openhis.common.annotation.Dict;
|
||||||
@@ -105,8 +106,18 @@ public class DiagnosisTreatmentUpDto {
|
|||||||
private String childrenJson;
|
private String childrenJson;
|
||||||
|
|
||||||
/** 划价标记 */
|
/** 划价标记 */
|
||||||
|
@JsonAlias({"pricing_flag"})
|
||||||
private Integer pricingFlag;
|
private Integer pricingFlag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容前端把勾选框按 boolean 传参(true/false)的场景
|
||||||
|
* - true -> 1
|
||||||
|
* - false -> 0
|
||||||
|
*/
|
||||||
|
public void setPricingFlag(Boolean pricingFlag) {
|
||||||
|
this.pricingFlag = pricingFlag == null ? null : (pricingFlag ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 物价编码
|
* 物价编码
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.openhis.web.debug;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.web.basedatamanage.appservice.IPractitionerAppService;
|
||||||
|
import com.openhis.web.basedatamanage.dto.UserAndPractitionerDto;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试控制器 - 用于检查API返回数据
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/debug")
|
||||||
|
public class DebugController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IPractitionerAppService practitionerAppService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户及参与者数据用于调试
|
||||||
|
*/
|
||||||
|
@GetMapping("/user-practitioner-debug")
|
||||||
|
public R<UserAndPractitionerDto> getUserPractitionerDebug() {
|
||||||
|
// 获取第一页第一条数据用于调试
|
||||||
|
var page = practitionerAppService.getUserPractitionerPage(new UserAndPractitionerDto(), "", 1, 1);
|
||||||
|
if (page.getRecords() != null && !page.getRecords().isEmpty()) {
|
||||||
|
return R.ok(page.getRecords().get(0));
|
||||||
|
}
|
||||||
|
return R.fail("没有找到数据");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
package com.openhis.web.doctorstation.appservice;
|
package com.openhis.web.doctorstation.appservice;
|
||||||
|
|
||||||
import com.core.common.core.domain.R;
|
|
||||||
import com.openhis.template.domain.DoctorPhrase;
|
import com.openhis.template.domain.DoctorPhrase;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface IDoctorPhraseAppService {
|
public interface IDoctorPhraseAppService {
|
||||||
R<?> getDoctorPhraseList();
|
List<DoctorPhrase> getDoctorPhraseList();
|
||||||
|
List<DoctorPhrase> searchDoctorPhraseList(String phraseName, Integer phraseType);
|
||||||
|
Boolean addDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||||
|
Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase);
|
||||||
|
Boolean deleteDoctorPhrase(Integer doctorPhraseId);
|
||||||
|
|
||||||
R<?> searchDoctorPhraseList(String phraseName ,Integer phraseType);
|
|
||||||
|
|
||||||
R<?> addDoctorPhrase(DoctorPhrase doctorPhrase);
|
|
||||||
|
|
||||||
R<?> updateDoctorPhrase(DoctorPhrase doctorPhrase);
|
|
||||||
|
|
||||||
R<?> deleteDoctorPhrase(Integer doctorPhraseId);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,4 +58,28 @@ public interface IDoctorStationEmrAppService {
|
|||||||
* @return 病历详情
|
* @return 病历详情
|
||||||
*/
|
*/
|
||||||
R<?> getEmrDetail(Long encounterId);
|
R<?> getEmrDetail(Long encounterId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历列表
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历列表
|
||||||
|
*/
|
||||||
|
R<?> getPendingEmrList(Long doctorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历数量
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历数量
|
||||||
|
*/
|
||||||
|
R<?> getPendingEmrCount(Long doctorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查患者是否需要写病历
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @return 患者是否需要写病历
|
||||||
|
*/
|
||||||
|
R<?> checkNeedWriteEmr(Long encounterId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public interface IDoctorStationMainAppService {
|
|||||||
* @param encounterId 就诊id
|
* @param encounterId 就诊id
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
R<?> completeEncounter(Long encounterId);
|
R<?> completeEncounter(Long encounterId, Integer firstEnum);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消完成
|
* 取消完成
|
||||||
@@ -90,4 +90,10 @@ public interface IDoctorStationMainAppService {
|
|||||||
*/
|
*/
|
||||||
List<ReceptionStatisticsDto> getReceptionStatistics(String startTime,String endTime,Long practitionerId);
|
List<ReceptionStatisticsDto> getReceptionStatistics(String startTime,String endTime,Long practitionerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过号重排
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
R<?> rearrangeMissedEncounter(Long encounterId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,71 @@ package com.openhis.web.doctorstation.appservice.impl;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.core.common.utils.SecurityUtils;
|
||||||
import com.core.common.core.domain.R;
|
import com.openhis.common.enums.BindingType;
|
||||||
import com.openhis.template.domain.DoctorPhrase;
|
import com.openhis.template.domain.DoctorPhrase;
|
||||||
import com.openhis.template.service.IDoctorPhraseService;
|
import com.openhis.template.service.IDoctorPhraseService;
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
|
public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IDoctorPhraseService doctorPhraseService;
|
private IDoctorPhraseService doctorPhraseService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> getDoctorPhraseList() {
|
public List<DoctorPhrase> getDoctorPhraseList() {
|
||||||
List<DoctorPhrase> list = doctorPhraseService.list();
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
return R.ok(list);
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("orgId: {}", orgId);
|
||||||
}
|
}
|
||||||
|
LambdaQueryWrapper<DoctorPhrase> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 1. 获取当前登录用户信息
|
||||||
|
Long userId = SecurityUtils.getUserId();
|
||||||
|
// 2. 权限判定:非管理员才需要过滤
|
||||||
|
// 如果是超级管理员,默认可以看到所有
|
||||||
|
if (!SecurityUtils.isAdmin(userId)) {
|
||||||
|
// 3. 获取当前医生的科室编码
|
||||||
|
String deptCode = "";
|
||||||
|
if (orgId != null) {
|
||||||
|
deptCode = String.valueOf(orgId);
|
||||||
|
}
|
||||||
|
// final 变量用于 Lambda 表达式
|
||||||
|
String finalDeptCode = deptCode;
|
||||||
|
// 4. 核心逻辑:三级数据共享
|
||||||
|
wrapper.and(w -> w
|
||||||
|
.eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的
|
||||||
|
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3
|
||||||
|
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配
|
||||||
|
.eq(DoctorPhrase::getDeptCode, finalDeptCode))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 5. 按排序号排序(可选优化,让常用语显示更整齐)
|
||||||
|
List<DoctorPhrase> list = doctorPhraseService.list(wrapper);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> searchDoctorPhraseList(String phraseName,Integer phraseType) {
|
public List<DoctorPhrase> searchDoctorPhraseList(String phraseName,Integer phraseType) {
|
||||||
|
// 1. 获取当前登录用户信息
|
||||||
|
Long userId = SecurityUtils.getUserId();
|
||||||
|
//2.获取到当前医生当前科室的id
|
||||||
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Search phrase - orgId: {}, phraseName: {}, phraseType: {}", orgId, phraseName, phraseType);
|
||||||
|
}
|
||||||
|
String deptCode = "";
|
||||||
|
if (orgId != null) {
|
||||||
|
deptCode = String.valueOf(orgId);
|
||||||
|
}
|
||||||
|
String finalDeptCode = deptCode;
|
||||||
LambdaQueryWrapper<DoctorPhrase> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<DoctorPhrase> wrapper = new LambdaQueryWrapper<>();
|
||||||
if (phraseName !=null && ObjectUtil.isNotEmpty(phraseName)) {
|
if (phraseName !=null && ObjectUtil.isNotEmpty(phraseName)) {
|
||||||
wrapper.like(DoctorPhrase::getPhraseName, phraseName);
|
wrapper.like(DoctorPhrase::getPhraseName, phraseName);
|
||||||
@@ -33,50 +74,135 @@ public class DoctorPhraseAppServiceImpl implements IDoctorPhraseAppService {
|
|||||||
if (phraseType !=null && ObjectUtil.isNotEmpty(phraseType)) {
|
if (phraseType !=null && ObjectUtil.isNotEmpty(phraseType)) {
|
||||||
wrapper.eq(DoctorPhrase::getPhraseType, phraseType);
|
wrapper.eq(DoctorPhrase::getPhraseType, phraseType);
|
||||||
}
|
}
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||||
|
// 建议统一使用 staffId 进行业务隔离
|
||||||
|
wrapper.and(w -> w
|
||||||
|
.eq(DoctorPhrase::getStaffId, userId.intValue()) // 1. 个人的:只看 staffId 是我的
|
||||||
|
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.HOSPITAL.getValue())) // 2. 全院的:类型为 3
|
||||||
|
.or(o -> o.eq(DoctorPhrase::getPhraseType, BindingType.ORGANIZATION.getValue()) // 3. 科室的:类型为 2 且 科室编码匹配
|
||||||
|
.eq(DoctorPhrase::getDeptCode, finalDeptCode))
|
||||||
|
);
|
||||||
|
}
|
||||||
//2.查询
|
//2.查询
|
||||||
List<DoctorPhrase> list = doctorPhraseService.list(wrapper);
|
List<DoctorPhrase> list = doctorPhraseService.list(wrapper);
|
||||||
return R.ok(list);
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> addDoctorPhrase(DoctorPhrase doctorPhrase) {
|
public Boolean addDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||||
//1.数据校验
|
// 1. 基础校验
|
||||||
if(ObjectUtil.isEmpty(doctorPhrase)){
|
if (ObjectUtil.isEmpty(doctorPhrase) || ObjectUtil.isEmpty(doctorPhrase.getPhraseName())) {
|
||||||
return R.fail("医生常用语不能为空");
|
throw new IllegalArgumentException("新增失败:常用语名称不能为空");
|
||||||
}
|
}
|
||||||
//2.名称唯一性校验
|
|
||||||
LambdaUpdateWrapper<DoctorPhrase> wrapper = new LambdaUpdateWrapper<>();
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
wrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName());
|
|
||||||
DoctorPhrase one = doctorPhraseService.getOne(wrapper);
|
/*
|
||||||
if(ObjectUtil.isNotEmpty(one)){
|
* 如果前端没传类型,必须给个默认值(比如 1-个人)
|
||||||
return R.fail("该名称已经存在");
|
* 否则存成 NULL,查询列表时会被过滤掉,导致"新增了却看不见"
|
||||||
|
*/
|
||||||
|
if (doctorPhrase.getPhraseType() == null) {
|
||||||
|
doctorPhrase.setPhraseType(BindingType.PERSONAL.getValue());
|
||||||
}
|
}
|
||||||
//3.新增
|
|
||||||
boolean save = doctorPhraseService.save(doctorPhrase);
|
// 2. 注入归属信息
|
||||||
System.out.println(save);
|
doctorPhrase.setStaffId(currentUserId.intValue());
|
||||||
return R.ok(save);
|
doctorPhrase.setCreatorId(currentUserId.intValue());
|
||||||
|
|
||||||
|
// 注入科室 (处理 null 情况)
|
||||||
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
if (orgId != null) {
|
||||||
|
// 检查dept_code字段长度,避免数据库错误
|
||||||
|
String deptCode = String.valueOf(orgId);
|
||||||
|
if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整
|
||||||
|
// 如果超过字段长度限制,可以考虑截断或抛出有意义的错误
|
||||||
|
throw new IllegalArgumentException("科室ID过长,无法保存");
|
||||||
|
}
|
||||||
|
doctorPhrase.setDeptCode(deptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========== 【修复点 2】查重范围限制在"个人" ===========
|
||||||
|
// 使用 QueryWrapper 而不是 UpdateWrapper
|
||||||
|
LambdaQueryWrapper<DoctorPhrase> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(DoctorPhrase::getPhraseName, doctorPhrase.getPhraseName())
|
||||||
|
.eq(DoctorPhrase::getStaffId, currentUserId.intValue()); // 重点:只查自己名下的!
|
||||||
|
|
||||||
|
if (doctorPhraseService.count(queryWrapper) > 0) {
|
||||||
|
throw new IllegalArgumentException("新增失败:您已存在同名的常用语");
|
||||||
|
}
|
||||||
|
|
||||||
|
return doctorPhraseService.save(doctorPhrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> updateDoctorPhrase(DoctorPhrase doctorPhrase) {
|
public Boolean updateDoctorPhrase(DoctorPhrase doctorPhrase) {
|
||||||
//1.数据校验
|
// 1. 基础校验
|
||||||
if(ObjectUtil.isEmpty(doctorPhrase)){
|
if (ObjectUtil.isEmpty(doctorPhrase) || doctorPhrase.getId() == null) {
|
||||||
return R.fail("医生常用语不能为空");
|
throw new IllegalArgumentException("修改失败:ID不能为空");
|
||||||
}
|
}
|
||||||
//2.更新
|
|
||||||
boolean updateById = doctorPhraseService.updateById(doctorPhrase);
|
// 2. 查旧数据
|
||||||
return R.ok(updateById);
|
DoctorPhrase original = doctorPhraseService.getById(doctorPhrase.getId());
|
||||||
|
if (original == null) {
|
||||||
|
throw new IllegalArgumentException("修改失败:该常用语不存在或已被删除");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
|
||||||
|
// 3. 【权限校验优化】
|
||||||
|
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||||
|
// 规则 A:严禁修改全院公共模板 (Type=3)
|
||||||
|
// 这一步是关键!之前你的代码漏了这里,所以普通医生可能改动全院模板
|
||||||
|
if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) {
|
||||||
|
throw new SecurityException("无权操作:全院公共常用语仅限管理员修改");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 规则 B:严禁修改他人的模板
|
||||||
|
if (!original.getStaffId().equals(currentUserId.intValue())) {
|
||||||
|
throw new SecurityException("无权操作:您只能修改自己创建的常用语");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 数据保护:防止篡改归属
|
||||||
|
doctorPhrase.setStaffId(original.getStaffId());
|
||||||
|
doctorPhrase.setCreatorId(original.getCreatorId());
|
||||||
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
if (orgId != null) {
|
||||||
|
// 检查dept_code字段长度,避免数据库错误
|
||||||
|
String deptCode = String.valueOf(orgId);
|
||||||
|
if (deptCode.length() > 50) { // 假设字段长度为50,根据实际情况调整
|
||||||
|
throw new IllegalArgumentException("科室ID过长,无法保存");
|
||||||
|
}
|
||||||
|
doctorPhrase.setDeptCode(deptCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return doctorPhraseService.updateById(doctorPhrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R<?> deleteDoctorPhrase(Integer doctorPhraseId) {
|
public Boolean deleteDoctorPhrase(Integer doctorPhraseId) {
|
||||||
//1.数据校验
|
if (doctorPhraseId == null) {
|
||||||
if(doctorPhraseId == null){
|
throw new IllegalArgumentException("删除失败:ID不能为空");
|
||||||
return R.fail("ID不能为空");
|
|
||||||
}
|
}
|
||||||
//2.删除
|
|
||||||
boolean removeById = doctorPhraseService.removeById(doctorPhraseId);
|
DoctorPhrase original = doctorPhraseService.getById(doctorPhraseId);
|
||||||
return R.ok(removeById);
|
if (original == null) {
|
||||||
|
throw new IllegalArgumentException("删除失败:数据不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long currentUserId = SecurityUtils.getUserId();
|
||||||
|
|
||||||
|
// 权限校验
|
||||||
|
if (!SecurityUtils.isAdmin(currentUserId)) {
|
||||||
|
if (BindingType.HOSPITAL.getValue().equals(original.getPhraseType())) {
|
||||||
|
throw new SecurityException("无权操作:全院公共常用语仅限管理员删除");
|
||||||
|
}
|
||||||
|
if (!original.getStaffId().equals(currentUserId.intValue())) {
|
||||||
|
throw new SecurityException("无权操作:您只能删除自己创建的常用语");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doctorPhraseService.removeById(doctorPhraseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.openhis.workflow.service.IServiceRequestService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -118,7 +119,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
String safeSearchKey = searchKey != null ? searchKey : "";
|
String safeSearchKey = searchKey != null ? searchKey : "";
|
||||||
String safeAdviceTypesStr = "";
|
String safeAdviceTypesStr = "";
|
||||||
if (adviceTypes != null && !adviceTypes.isEmpty()) {
|
if (adviceTypes != null && !adviceTypes.isEmpty()) {
|
||||||
safeAdviceTypesStr = String.join(",", adviceTypes.stream().map(String::valueOf).collect(Collectors.toList()));
|
safeAdviceTypesStr = String.join(",",
|
||||||
|
adviceTypes.stream().map(String::valueOf).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
String safeOrganizationId = organizationId != null ? organizationId.toString() : "";
|
String safeOrganizationId = organizationId != null ? organizationId.toString() : "";
|
||||||
String safePricingFlag = pricingFlag != null ? pricingFlag.toString() : "";
|
String safePricingFlag = pricingFlag != null ? pricingFlag.toString() : "";
|
||||||
@@ -127,8 +129,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
|
|
||||||
log.info("从数据库查询医嘱基础信息");
|
log.info("从数据库查询医嘱基础信息");
|
||||||
|
|
||||||
// 设置默认科室 (不取前端传的了)
|
// 设置默认科室:仅当前端/调用方未传 organizationId 时才回退到登录人科室
|
||||||
organizationId = SecurityUtils.getLoginUser().getOrgId();
|
// 否则会导致门诊划价等场景(按患者挂号科室查询)返回空
|
||||||
|
if (organizationId == null) {
|
||||||
|
organizationId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
}
|
||||||
|
|
||||||
// 医嘱定价来源
|
// 医嘱定价来源
|
||||||
String orderPricingSource = TenantOptionUtil.getOptionContent(TenantOptionDict.ORDER_PRICING_SOURCE);
|
String orderPricingSource = TenantOptionUtil.getOptionContent(TenantOptionDict.ORDER_PRICING_SOURCE);
|
||||||
@@ -138,8 +143,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
orderPricingSource = orderPricing;
|
orderPricingSource = orderPricing;
|
||||||
}
|
}
|
||||||
// 开药时药房允许多选开关
|
// 开药时药房允许多选开关
|
||||||
String pharmacyMultipleChoiceSwitch
|
String pharmacyMultipleChoiceSwitch = TenantOptionUtil
|
||||||
= TenantOptionUtil.getOptionContent(TenantOptionDict.PHARMACY_MULTIPLE_CHOICE_SWITCH);
|
.getOptionContent(TenantOptionDict.PHARMACY_MULTIPLE_CHOICE_SWITCH);
|
||||||
// 药房允许多选
|
// 药房允许多选
|
||||||
boolean pharmacyMultipleChoice = Whether.YES.getCode().equals(pharmacyMultipleChoiceSwitch);
|
boolean pharmacyMultipleChoice = Whether.YES.getCode().equals(pharmacyMultipleChoiceSwitch);
|
||||||
|
|
||||||
@@ -149,7 +154,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
IPage<AdviceBaseDto> adviceBaseInfo = doctorStationAdviceAppMapper.getAdviceBaseInfo(
|
IPage<AdviceBaseDto> adviceBaseInfo = doctorStationAdviceAppMapper.getAdviceBaseInfo(
|
||||||
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
|
new Page<>(pageNo, pageSize), PublicationStatus.ACTIVE.getValue(), organizationId,
|
||||||
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
|
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
|
||||||
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList, adviceTypes,
|
CommonConstants.TableName.WOR_ACTIVITY_DEFINITION, pricingFlag, adviceDefinitionIdParamList,
|
||||||
|
adviceTypes,
|
||||||
queryWrapper);
|
queryWrapper);
|
||||||
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
|
List<AdviceBaseDto> adviceBaseDtoList = adviceBaseInfo.getRecords();
|
||||||
|
|
||||||
@@ -159,22 +165,22 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 医嘱定义ID集合
|
// 医嘱定义ID集合
|
||||||
List<Long> adviceDefinitionIdList
|
List<Long> adviceDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getAdviceDefinitionId)
|
||||||
= adviceBaseDtoList.stream().map(AdviceBaseDto::getAdviceDefinitionId).collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 费用定价主表ID集合
|
// 费用定价主表ID集合
|
||||||
List<Long> chargeItemDefinitionIdList
|
List<Long> chargeItemDefinitionIdList = adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId)
|
||||||
= adviceBaseDtoList.stream().map(AdviceBaseDto::getChargeItemDefinitionId).collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 医嘱库存集合
|
// 医嘱库存集合
|
||||||
List<AdviceInventoryDto> adviceInventoryList
|
List<AdviceInventoryDto> adviceInventoryList = doctorStationAdviceAppMapper.getAdviceInventory(locationId,
|
||||||
= doctorStationAdviceAppMapper.getAdviceInventory(locationId, adviceDefinitionIdList,
|
adviceDefinitionIdList,
|
||||||
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
|
CommonConstants.SqlCondition.ABOUT_INVENTORY_TABLE_STR, PublicationStatus.ACTIVE.getValue());
|
||||||
// 待发放个数信息
|
// 待发放个数信息
|
||||||
List<AdviceInventoryDto> adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
|
List<AdviceInventoryDto> adviceDraftInventoryList = doctorStationAdviceAppMapper.getAdviceDraftInventory(
|
||||||
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
|
CommonConstants.TableName.MED_MEDICATION_DEFINITION, CommonConstants.TableName.ADM_DEVICE_DEFINITION,
|
||||||
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
|
DispenseStatus.DRAFT.getValue(), DispenseStatus.PREPARATION.getValue());
|
||||||
// 预减库存
|
// 预减库存
|
||||||
List<AdviceInventoryDto> adviceInventory
|
List<AdviceInventoryDto> adviceInventory = adviceUtils.subtractInventory(adviceInventoryList,
|
||||||
= adviceUtils.subtractInventory(adviceInventoryList, adviceDraftInventoryList);
|
adviceDraftInventoryList);
|
||||||
// 查询取药科室配置
|
// 查询取药科室配置
|
||||||
List<AdviceInventoryDto> medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
|
List<AdviceInventoryDto> medLocationConfig = doctorStationAdviceAppMapper.getMedLocationConfig(organizationId);
|
||||||
// 将配置转为 {categoryCode -> 允许的locationId集合}
|
// 将配置转为 {categoryCode -> 允许的locationId集合}
|
||||||
@@ -209,7 +215,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
|
for (int i = 0; i < chargeItemDefinitionIdList.size(); i += batchSize) {
|
||||||
int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size());
|
int endIndex = Math.min(i + batchSize, chargeItemDefinitionIdList.size());
|
||||||
List<Long> batch = chargeItemDefinitionIdList.subList(i, endIndex);
|
List<Long> batch = chargeItemDefinitionIdList.subList(i, endIndex);
|
||||||
mainCharge.addAll(doctorStationAdviceAppMapper.getMainCharge(batch, PublicationStatus.ACTIVE.getValue()));
|
mainCharge
|
||||||
|
.addAll(doctorStationAdviceAppMapper.getMainCharge(batch, PublicationStatus.ACTIVE.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String unitCode = ""; // 包装单位
|
String unitCode = ""; // 包装单位
|
||||||
@@ -225,13 +232,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
|
|
||||||
// fallthrough to 耗材处理逻辑(保持原有逻辑)
|
// fallthrough to 耗材处理逻辑(保持原有逻辑)
|
||||||
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
|
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
|
||||||
List<AdviceInventoryDto> inventoryList = adviceInventory.stream().filter(e ->
|
List<AdviceInventoryDto> inventoryList = adviceInventory
|
||||||
baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
|
.stream().filter(e -> baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
|
||||||
&& baseDto.getAdviceDefinitionId().equals(e.getItemId())
|
&& baseDto.getAdviceDefinitionId().equals(e.getItemId())
|
||||||
&& baseDto.getAdviceTableName() != null && e.getItemTable() != null
|
&& baseDto.getAdviceTableName() != null && e.getItemTable() != null
|
||||||
&& baseDto.getAdviceTableName().equals(e.getItemTable())
|
&& baseDto.getAdviceTableName().equals(e.getItemTable())
|
||||||
&& (pharmacyMultipleChoice
|
&& (pharmacyMultipleChoice
|
||||||
|| (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId())))))
|
|| (baseDto.getPositionId() == null || (e.getLocationId() != null
|
||||||
|
&& baseDto.getPositionId().equals(e.getLocationId())))))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 库存信息
|
// 库存信息
|
||||||
baseDto.setInventoryList(inventoryList);
|
baseDto.setInventoryList(inventoryList);
|
||||||
@@ -248,13 +256,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 第一步:在medLocationConfig中匹配categoryCode
|
// 第一步:在medLocationConfig中匹配categoryCode
|
||||||
AdviceInventoryDto result1 = medLocationConfig.stream()
|
AdviceInventoryDto result1 = medLocationConfig.stream()
|
||||||
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
|
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
|
||||||
&& baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
|
&& baseDto.getCategoryCode().equals(dto.getCategoryCode()))
|
||||||
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (result1 != null) {
|
if (result1 != null) {
|
||||||
// 第二步:在inventoryList中匹配locationId
|
// 第二步:在inventoryList中匹配locationId
|
||||||
AdviceInventoryDto result2 = inventoryList.stream()
|
AdviceInventoryDto result2 = inventoryList.stream()
|
||||||
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
|
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
|
||||||
&& result1.getLocationId().equals(dto.getLocationId())).findFirst()
|
&& result1.getLocationId().equals(dto.getLocationId()))
|
||||||
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (result2 != null && result2.getLotNumber() != null) {
|
if (result2 != null && result2.getLotNumber() != null) {
|
||||||
baseDto.setDefaultLotNumber(result2.getLotNumber());
|
baseDto.setDefaultLotNumber(result2.getLotNumber());
|
||||||
@@ -293,13 +303,14 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
baseDto.setPriceList(priceDtoList);
|
baseDto.setPriceList(priceDtoList);
|
||||||
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(tableName)) { // 耗材
|
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(tableName)) { // 耗材
|
||||||
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
|
// 每一条医嘱的库存集合信息 , 包装单位库存前端计算
|
||||||
List<AdviceInventoryDto> inventoryList = adviceInventory.stream().filter(e ->
|
List<AdviceInventoryDto> inventoryList = adviceInventory
|
||||||
baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
|
.stream().filter(e -> baseDto.getAdviceDefinitionId() != null && e.getItemId() != null
|
||||||
&& baseDto.getAdviceDefinitionId().equals(e.getItemId())
|
&& baseDto.getAdviceDefinitionId().equals(e.getItemId())
|
||||||
&& baseDto.getAdviceTableName() != null && e.getItemTable() != null
|
&& baseDto.getAdviceTableName() != null && e.getItemTable() != null
|
||||||
&& baseDto.getAdviceTableName().equals(e.getItemTable())
|
&& baseDto.getAdviceTableName().equals(e.getItemTable())
|
||||||
&& (pharmacyMultipleChoice
|
&& (pharmacyMultipleChoice
|
||||||
|| (baseDto.getPositionId() == null || (e.getLocationId() != null && baseDto.getPositionId().equals(e.getLocationId())))))
|
|| (baseDto.getPositionId() == null || (e.getLocationId() != null
|
||||||
|
&& baseDto.getPositionId().equals(e.getLocationId())))))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 库存信息
|
// 库存信息
|
||||||
baseDto.setInventoryList(inventoryList);
|
baseDto.setInventoryList(inventoryList);
|
||||||
@@ -316,13 +327,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 第一步:在medLocationConfig中匹配categoryCode
|
// 第一步:在medLocationConfig中匹配categoryCode
|
||||||
AdviceInventoryDto result1 = medLocationConfig.stream()
|
AdviceInventoryDto result1 = medLocationConfig.stream()
|
||||||
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
|
.filter(dto -> baseDto.getCategoryCode() != null && dto.getCategoryCode() != null
|
||||||
&& baseDto.getCategoryCode().equals(dto.getCategoryCode())).findFirst()
|
&& baseDto.getCategoryCode().equals(dto.getCategoryCode()))
|
||||||
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (result1 != null) {
|
if (result1 != null) {
|
||||||
// 第二步:在inventoryList中匹配locationId
|
// 第二步:在inventoryList中匹配locationId
|
||||||
AdviceInventoryDto result2 = inventoryList.stream()
|
AdviceInventoryDto result2 = inventoryList.stream()
|
||||||
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
|
.filter(dto -> result1.getLocationId() != null && dto.getLocationId() != null
|
||||||
&& result1.getLocationId().equals(dto.getLocationId())).findFirst()
|
&& result1.getLocationId().equals(dto.getLocationId()))
|
||||||
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (result2 != null && result2.getLotNumber() != null) {
|
if (result2 != null && result2.getLotNumber() != null) {
|
||||||
baseDto.setDefaultLotNumber(result2.getLotNumber());
|
baseDto.setDefaultLotNumber(result2.getLotNumber());
|
||||||
@@ -360,10 +373,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 价格信息
|
// 价格信息
|
||||||
baseDto.setPriceList(priceDtoList);
|
baseDto.setPriceList(priceDtoList);
|
||||||
} else if (CommonConstants.TableName.WOR_ACTIVITY_DEFINITION.equals(tableName)) { // 诊疗
|
} else if (CommonConstants.TableName.WOR_ACTIVITY_DEFINITION.equals(tableName)) { // 诊疗
|
||||||
List<AdvicePriceDto> priceList
|
List<AdvicePriceDto> priceList = mainCharge.stream()
|
||||||
= mainCharge.stream().filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
|
.filter(e -> baseDto.getChargeItemDefinitionId() != null && e.getDefinitionId() != null
|
||||||
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
|
&& baseDto.getChargeItemDefinitionId().equals(e.getDefinitionId()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 价格信息
|
// 价格信息
|
||||||
baseDto.setPriceList(priceList);
|
baseDto.setPriceList(priceList);
|
||||||
// 活动类型
|
// 活动类型
|
||||||
@@ -379,7 +392,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
* 查询医嘱绑定信息
|
* 查询医嘱绑定信息
|
||||||
*
|
*
|
||||||
* @param typeCode 1:用法绑东西 2:诊疗绑东西
|
* @param typeCode 1:用法绑东西 2:诊疗绑东西
|
||||||
* @param itemNo 用法的code 或者 诊疗定义id
|
* @param itemNo 用法的code 或者 诊疗定义id
|
||||||
* @return 医嘱绑定信息
|
* @return 医嘱绑定信息
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -391,10 +404,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
* 门诊保存/签发医嘱
|
* 门诊保存/签发医嘱
|
||||||
*
|
*
|
||||||
* @param adviceSaveParam 医嘱表单信息
|
* @param adviceSaveParam 医嘱表单信息
|
||||||
* @param adviceOpType 医嘱操作类型
|
* @param adviceOpType 医嘱操作类型
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) {
|
public R<?> saveAdvice(AdviceSaveParam adviceSaveParam, String adviceOpType) {
|
||||||
try {
|
try {
|
||||||
// 患者挂号对应的科室id
|
// 患者挂号对应的科室id
|
||||||
@@ -415,10 +429,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
* 保存时,校验库存
|
* 保存时,校验库存
|
||||||
*/
|
*/
|
||||||
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
|
if (AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType)) {
|
||||||
List<AdviceSaveDto> medUpdateList
|
List<AdviceSaveDto> medUpdateList = medicineList.stream().filter(e -> e.getRequestId() != null)
|
||||||
= medicineList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
List<AdviceSaveDto> devUpdateList
|
List<AdviceSaveDto> devUpdateList = deviceList.stream().filter(e -> e.getRequestId() != null)
|
||||||
= deviceList.stream().filter(e -> e.getRequestId() != null).collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
// 编辑时,释放本身占用的库存发放
|
// 编辑时,释放本身占用的库存发放
|
||||||
for (AdviceSaveDto adviceSaveDto : medUpdateList) {
|
for (AdviceSaveDto adviceSaveDto : medUpdateList) {
|
||||||
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
|
iMedicationDispenseService.deleteMedicationDispense(adviceSaveDto.getRequestId());
|
||||||
@@ -427,9 +441,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
|
iDeviceDispenseService.deleteDeviceDispense(adviceSaveDto.getRequestId());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AdviceSaveDto> needCheckList
|
List<AdviceSaveDto> needCheckList = adviceSaveList.stream()
|
||||||
= adviceSaveList.stream().filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
|
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType())
|
||||||
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType())).collect(Collectors.toList());
|
&& !ItemType.ACTIVITY.getValue().equals(e.getAdviceType()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
// 校验库存
|
// 校验库存
|
||||||
String tipRes = adviceUtils.checkInventory(needCheckList);
|
String tipRes = adviceUtils.checkInventory(needCheckList);
|
||||||
if (tipRes != null) {
|
if (tipRes != null) {
|
||||||
@@ -444,8 +459,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
/**
|
/**
|
||||||
* 处理药品请求
|
* 处理药品请求
|
||||||
*/
|
*/
|
||||||
List<String> medRequestIdList
|
List<String> medRequestIdList = this.handMedication(medicineList, curDate, adviceOpType, organizationId,
|
||||||
= this.handMedication(medicineList, curDate, adviceOpType, organizationId, signCode);
|
signCode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理诊疗项目请求
|
* 处理诊疗项目请求
|
||||||
@@ -462,7 +477,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 签发的医嘱id集合
|
// 签发的医嘱id集合
|
||||||
List<Long> requestIds = adviceSaveList.stream()
|
List<Long> requestIds = adviceSaveList.stream()
|
||||||
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
|
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
|
||||||
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
|
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
// 就诊id
|
// 就诊id
|
||||||
Long encounterId = adviceSaveList.get(0).getEncounterId();
|
Long encounterId = adviceSaveList.get(0).getEncounterId();
|
||||||
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
|
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
|
||||||
@@ -476,7 +492,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
clearRelatedCache();
|
clearRelatedCache();
|
||||||
|
|
||||||
return R.ok(medRequestIdList,
|
return R.ok(medRequestIdList,
|
||||||
MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊医嘱"}));
|
MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] { "门诊医嘱" }));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 异常处理
|
// 异常处理
|
||||||
throw e;
|
throw e;
|
||||||
@@ -498,6 +514,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
Long organizationId, String signCode) {
|
Long organizationId, String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
// 获取当前登录用户的tenantId
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
// 获取当前登录用户名
|
||||||
|
String currentUsername = SecurityUtils.getUsername();
|
||||||
// 保存操作
|
// 保存操作
|
||||||
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
||||||
// 签发操作
|
// 签发操作
|
||||||
@@ -507,9 +527,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 声明费用项
|
// 声明费用项
|
||||||
ChargeItem chargeItem;
|
ChargeItem chargeItem;
|
||||||
// 新增 + 修改
|
// 新增 + 修改
|
||||||
List<AdviceSaveDto> insertOrUpdateList
|
List<AdviceSaveDto> insertOrUpdateList = medicineList.stream()
|
||||||
= medicineList.stream().filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
.filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
||||||
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType()))).collect(Collectors.toList());
|
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
// 删除
|
// 删除
|
||||||
List<AdviceSaveDto> deleteList = medicineList.stream()
|
List<AdviceSaveDto> deleteList = medicineList.stream()
|
||||||
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
||||||
@@ -534,7 +555,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
adviceSaveDto.getRequestId());
|
adviceSaveDto.getRequestId());
|
||||||
// 删除基于这个药品生成的需要执行的诊疗请求
|
// 删除基于这个药品生成的需要执行的诊疗请求
|
||||||
iServiceRequestService.remove(
|
iServiceRequestService.remove(
|
||||||
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getBasedOnId, adviceSaveDto.getRequestId())
|
new LambdaQueryWrapper<ServiceRequest>()
|
||||||
|
.eq(ServiceRequest::getBasedOnId, adviceSaveDto.getRequestId())
|
||||||
.isNotNull(ServiceRequest::getBasedOnTable)
|
.isNotNull(ServiceRequest::getBasedOnTable)
|
||||||
.eq(ServiceRequest::getStatusEnum, RequestStatus.COMPLETED.getValue()));
|
.eq(ServiceRequest::getStatusEnum, RequestStatus.COMPLETED.getValue()));
|
||||||
}
|
}
|
||||||
@@ -552,6 +574,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
medicationRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
medicationRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
||||||
medicationRequest.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
medicationRequest.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
||||||
medicationRequest.setGroupId(adviceSaveDto.getGroupId()); // 组号
|
medicationRequest.setGroupId(adviceSaveDto.getGroupId()); // 组号
|
||||||
|
medicationRequest.setTenantId(tenantId); // 设置租户id
|
||||||
|
medicationRequest.setCreateBy(currentUsername); // 设置创建人
|
||||||
|
medicationRequest.setCreateTime(curDate); // 设置创建时间
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
medicationRequest.setSignCode(signCode);
|
medicationRequest.setSignCode(signCode);
|
||||||
}
|
}
|
||||||
@@ -596,8 +621,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
}
|
}
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
// 处理药品发放
|
// 处理药品发放
|
||||||
Long dispenseId
|
Long dispenseId = iMedicationDispenseService.handleMedicationDispense(medicationRequest,
|
||||||
= iMedicationDispenseService.handleMedicationDispense(medicationRequest, adviceSaveDto.getDbOpType());
|
adviceSaveDto.getDbOpType());
|
||||||
|
|
||||||
// 保存药品费用项
|
// 保存药品费用项
|
||||||
chargeItem = new ChargeItem();
|
chargeItem = new ChargeItem();
|
||||||
@@ -622,12 +647,20 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
chargeItem.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
|
||||||
chargeItem.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
chargeItem.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
|
||||||
chargeItem.setDispenseId(dispenseId); // 发放ID
|
chargeItem.setDispenseId(dispenseId); // 发放ID
|
||||||
|
chargeItem.setTenantId(tenantId); // 设置租户ID (修复本次报错)
|
||||||
|
chargeItem.setCreateBy(currentUsername); // 设置创建人
|
||||||
|
chargeItem.setCreateTime(curDate); // 设置创建时间
|
||||||
|
|
||||||
chargeItem.setQuantityValue(adviceSaveDto.getQuantity()); // 数量
|
chargeItem.setQuantityValue(adviceSaveDto.getQuantity()); // 数量
|
||||||
chargeItem.setQuantityUnit(adviceSaveDto.getUnitCode()); // 单位
|
chargeItem.setQuantityUnit(adviceSaveDto.getUnitCode()); // 单位
|
||||||
chargeItem.setUnitPrice(adviceSaveDto.getUnitPrice()); // 单价
|
chargeItem.setUnitPrice(adviceSaveDto.getUnitPrice()); // 单价
|
||||||
chargeItem.setTotalPrice(adviceSaveDto.getTotalPrice()); // 总价
|
chargeItem.setTotalPrice(adviceSaveDto.getTotalPrice()); // 总价
|
||||||
|
|
||||||
|
// 显式设置tenantId、createBy和createTime字段,防止自动填充机制失效
|
||||||
|
chargeItem.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
chargeItem.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
chargeItem.setCreateTime(new Date());
|
||||||
|
|
||||||
iChargeItemService.saveOrUpdate(chargeItem);
|
iChargeItemService.saveOrUpdate(chargeItem);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -641,6 +674,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
private void handDevice(List<AdviceSaveDto> deviceList, Date curDate, String adviceOpType) {
|
private void handDevice(List<AdviceSaveDto> deviceList, Date curDate, String adviceOpType) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
// 获取当前登录用户的tenantId
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
// 获取当前登录用户名
|
||||||
|
String currentUsername = SecurityUtils.getUsername();
|
||||||
// 保存操作
|
// 保存操作
|
||||||
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
||||||
// 签发操作
|
// 签发操作
|
||||||
@@ -649,9 +686,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 声明费用项
|
// 声明费用项
|
||||||
ChargeItem chargeItem;
|
ChargeItem chargeItem;
|
||||||
// 新增 + 修改
|
// 新增 + 修改
|
||||||
List<AdviceSaveDto> insertOrUpdateList
|
List<AdviceSaveDto> insertOrUpdateList = deviceList.stream()
|
||||||
= deviceList.stream().filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
.filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
||||||
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType()))).collect(Collectors.toList());
|
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
// 删除
|
// 删除
|
||||||
List<AdviceSaveDto> deleteList = deviceList.stream()
|
List<AdviceSaveDto> deleteList = deviceList.stream()
|
||||||
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
||||||
@@ -680,6 +718,13 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
deviceRequest = new DeviceRequest();
|
deviceRequest = new DeviceRequest();
|
||||||
deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
|
deviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
|
||||||
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
deviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
||||||
|
deviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
|
// 显式设置审计字段,防止自动填充机制失效
|
||||||
|
deviceRequest.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
deviceRequest.setCreateTime(new Date());
|
||||||
|
deviceRequest.setTenantId(tenantId); // 设置租户id
|
||||||
|
deviceRequest.setCreateBy(currentUsername); // 设置创建人
|
||||||
|
deviceRequest.setCreateTime(curDate); // 设置创建时间
|
||||||
|
|
||||||
// 保存时,处理数据(请求,发放,账单)
|
// 保存时,处理数据(请求,发放,账单)
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
@@ -709,12 +754,15 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
iDeviceRequestService.saveOrUpdate(deviceRequest);
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
// 处理耗材发放
|
// 处理耗材发放
|
||||||
Long dispenseId
|
Long dispenseId = iDeviceDispenseService.handleDeviceDispense(deviceRequest,
|
||||||
= iDeviceDispenseService.handleDeviceDispense(deviceRequest, adviceSaveDto.getDbOpType());
|
adviceSaveDto.getDbOpType());
|
||||||
|
|
||||||
// 保存耗材费用项
|
// 保存耗材费用项
|
||||||
chargeItem = new ChargeItem();
|
chargeItem = new ChargeItem();
|
||||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
|
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||||
|
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
||||||
|
chargeItem.setCreateTime(curDate); // 补全创建时间
|
||||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(deviceRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
@@ -740,6 +788,11 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setUnitPrice(adviceSaveDto.getUnitPrice()); // 单价
|
chargeItem.setUnitPrice(adviceSaveDto.getUnitPrice()); // 单价
|
||||||
chargeItem.setTotalPrice(adviceSaveDto.getTotalPrice()); // 总价
|
chargeItem.setTotalPrice(adviceSaveDto.getTotalPrice()); // 总价
|
||||||
|
|
||||||
|
// 显式设置审计字段,防止自动填充机制失效
|
||||||
|
chargeItem.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
chargeItem.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
chargeItem.setCreateTime(new Date());
|
||||||
|
|
||||||
iChargeItemService.saveOrUpdate(chargeItem);
|
iChargeItemService.saveOrUpdate(chargeItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -752,6 +805,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
String signCode) {
|
String signCode) {
|
||||||
// 当前登录账号的科室id
|
// 当前登录账号的科室id
|
||||||
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
Long orgId = SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
// 获取当前登录用户的tenantId
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
// 获取当前登录用户名
|
||||||
|
String currentUsername = SecurityUtils.getUsername();
|
||||||
// 保存操作
|
// 保存操作
|
||||||
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
boolean is_save = AdviceOpType.SAVE_ADVICE.getCode().equals(adviceOpType);
|
||||||
// 签发操作
|
// 签发操作
|
||||||
@@ -760,9 +817,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 声明费用项
|
// 声明费用项
|
||||||
ChargeItem chargeItem;
|
ChargeItem chargeItem;
|
||||||
// 新增 + 修改
|
// 新增 + 修改
|
||||||
List<AdviceSaveDto> insertOrUpdateList
|
List<AdviceSaveDto> insertOrUpdateList = activityList.stream()
|
||||||
= activityList.stream().filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
.filter(e -> (DbOpType.INSERT.getCode().equals(e.getDbOpType())
|
||||||
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType()))).collect(Collectors.toList());
|
|| DbOpType.UPDATE.getCode().equals(e.getDbOpType())))
|
||||||
|
.collect(Collectors.toList());
|
||||||
// 删除
|
// 删除
|
||||||
List<AdviceSaveDto> deleteList = activityList.stream()
|
List<AdviceSaveDto> deleteList = activityList.stream()
|
||||||
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
.filter(e -> DbOpType.DELETE.getCode().equals(e.getDbOpType())).collect(Collectors.toList());
|
||||||
@@ -781,7 +839,8 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
for (AdviceSaveDto adviceSaveDto : deleteList) {
|
||||||
iServiceRequestService.removeById(adviceSaveDto.getRequestId());// 删除诊疗
|
iServiceRequestService.removeById(adviceSaveDto.getRequestId());// 删除诊疗
|
||||||
iServiceRequestService.remove(
|
iServiceRequestService.remove(
|
||||||
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId, adviceSaveDto.getRequestId()));// 删除诊疗套餐对应的子项
|
new LambdaQueryWrapper<ServiceRequest>().eq(ServiceRequest::getParentId,
|
||||||
|
adviceSaveDto.getRequestId()));// 删除诊疗套餐对应的子项
|
||||||
// 删除费用项
|
// 删除费用项
|
||||||
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_SERVICE_REQUEST,
|
iChargeItemService.deleteByServiceTableAndId(CommonConstants.TableName.WOR_SERVICE_REQUEST,
|
||||||
adviceSaveDto.getRequestId());
|
adviceSaveDto.getRequestId());
|
||||||
@@ -791,6 +850,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
serviceRequest = new ServiceRequest();
|
serviceRequest = new ServiceRequest();
|
||||||
serviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
|
serviceRequest.setId(adviceSaveDto.getRequestId()); // 主键id
|
||||||
serviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
serviceRequest.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
||||||
|
serviceRequest.setTenantId(tenantId); // 设置租户id
|
||||||
|
serviceRequest.setCreateBy(currentUsername); // 设置创建人
|
||||||
|
serviceRequest.setCreateTime(curDate); // 设置创建时间
|
||||||
|
serviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
serviceRequest.setSignCode(signCode);
|
serviceRequest.setSignCode(signCode);
|
||||||
}
|
}
|
||||||
@@ -828,6 +891,9 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
if (is_save) {
|
if (is_save) {
|
||||||
chargeItem = new ChargeItem();
|
chargeItem = new ChargeItem();
|
||||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
|
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||||
|
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
||||||
|
chargeItem.setCreateTime(curDate); // 补全创建时间
|
||||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(serviceRequest.getBusNo()));
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
|
|||||||
@@ -187,6 +187,12 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
condition.setTcmFlag(Whether.YES.getValue());// 中医标识
|
condition.setTcmFlag(Whether.YES.getValue());// 中医标识
|
||||||
condition.setRecordedDatetime(new Date());
|
condition.setRecordedDatetime(new Date());
|
||||||
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
condition.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
condition.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
condition.setCreateTime(new Date());
|
||||||
iConditionService.saveOrUpdate(condition);
|
iConditionService.saveOrUpdate(condition);
|
||||||
saveDiagnosisChildParam.setConditionId(condition.getId());
|
saveDiagnosisChildParam.setConditionId(condition.getId());
|
||||||
}
|
}
|
||||||
@@ -225,6 +231,12 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
||||||
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
||||||
encounterDiagnosis.setSyndromeGroupNo(saveDiagnosisChildParam.getSyndromeGroupNo());// 中医证候组号
|
encounterDiagnosis.setSyndromeGroupNo(saveDiagnosisChildParam.getSyndromeGroupNo());// 中医证候组号
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
if (saveDiagnosisChildParam.getDiagSrtNo() == null) {
|
if (saveDiagnosisChildParam.getDiagSrtNo() == null) {
|
||||||
if (i == 1) {
|
if (i == 1) {
|
||||||
@@ -268,6 +280,12 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
condition.setTcmFlag(Whether.YES.getValue());// 中医标识
|
condition.setTcmFlag(Whether.YES.getValue());// 中医标识
|
||||||
condition.setRecordedDatetime(new Date());
|
condition.setRecordedDatetime(new Date());
|
||||||
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
condition.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
condition.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
condition.setCreateTime(new Date());
|
||||||
iConditionService.saveOrUpdate(condition);
|
iConditionService.saveOrUpdate(condition);
|
||||||
saveDiagnosisChildParam.setConditionId(condition.getId());
|
saveDiagnosisChildParam.setConditionId(condition.getId());
|
||||||
}
|
}
|
||||||
@@ -289,6 +307,12 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
||||||
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
||||||
encounterDiagnosis.setSyndromeGroupNo(saveDiagnosisChildParam.getSyndromeGroupNo());// 中医证候组号
|
encounterDiagnosis.setSyndromeGroupNo(saveDiagnosisChildParam.getSyndromeGroupNo());// 中医证候组号
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
}
|
}
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"中医诊断"}));
|
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"中医诊断"}));
|
||||||
@@ -493,6 +517,7 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
medicationRequest.setDosageInstruction(adviceSaveDto.getDosageInstruction()); // 用药说明 , 即煎法
|
medicationRequest.setDosageInstruction(adviceSaveDto.getDosageInstruction()); // 用药说明 , 即煎法
|
||||||
}
|
}
|
||||||
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
iMedicationRequestService.saveOrUpdate(medicationRequest);
|
||||||
|
adviceSaveDto.setRequestId(medicationRequest.getId());
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
// 处理药品发放
|
// 处理药品发放
|
||||||
Long dispenseId
|
Long dispenseId
|
||||||
@@ -608,11 +633,13 @@ public class DoctorStationChineseMedicalAppServiceImpl implements IDoctorStation
|
|||||||
List<Long> requestIds = medicineList.stream()
|
List<Long> requestIds = medicineList.stream()
|
||||||
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
|
.filter(e -> !DbOpType.DELETE.getCode().equals(e.getDbOpType()) && e.getRequestId() != null)
|
||||||
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
|
.collect(Collectors.toList()).stream().map(AdviceSaveDto::getRequestId).collect(Collectors.toList());
|
||||||
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
|
if (!requestIds.isEmpty()) {
|
||||||
.set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue())
|
iChargeItemService.update(new LambdaUpdateWrapper<ChargeItem>()
|
||||||
.eq(ChargeItem::getEncounterId, encounterId)
|
.set(ChargeItem::getStatusEnum, ChargeItemStatus.PLANNED.getValue())
|
||||||
.eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue())
|
.eq(ChargeItem::getEncounterId, encounterId)
|
||||||
.in(ChargeItem::getServiceId, requestIds));
|
.eq(ChargeItem::getStatusEnum, ChargeItemStatus.DRAFT.getValue())
|
||||||
|
.in(ChargeItem::getServiceId, requestIds));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊中医医嘱"}));
|
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[]{"门诊中医医嘱"}));
|
||||||
|
|||||||
@@ -215,6 +215,12 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
condition.setYbNo(saveDiagnosisChildParam.getYbNo());
|
condition.setYbNo(saveDiagnosisChildParam.getYbNo());
|
||||||
condition.setRecordedDatetime(new Date());
|
condition.setRecordedDatetime(new Date());
|
||||||
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
condition.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
condition.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
condition.setCreateTime(new Date());
|
||||||
iConditionService.saveOrUpdate(condition);
|
iConditionService.saveOrUpdate(condition);
|
||||||
saveDiagnosisChildParam.setConditionId(condition.getId());
|
saveDiagnosisChildParam.setConditionId(condition.getId());
|
||||||
}
|
}
|
||||||
@@ -230,6 +236,12 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
encounterDiagnosis.setMedTypeCode(saveDiagnosisChildParam.getMedTypeCode());// 医疗类型
|
encounterDiagnosis.setMedTypeCode(saveDiagnosisChildParam.getMedTypeCode());// 医疗类型
|
||||||
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
||||||
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
||||||
|
// 设置租户ID,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
// 设置创建人,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
// 设置创建时间,避免数据库约束错误
|
||||||
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
}
|
}
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
||||||
@@ -260,7 +272,8 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
.eq(EncounterDiagnosis::getEncounterId, encounterId)
|
.eq(EncounterDiagnosis::getEncounterId, encounterId)
|
||||||
.set(EncounterDiagnosis::getMaindiseFlag, 0));
|
.set(EncounterDiagnosis::getMaindiseFlag, 0));
|
||||||
}
|
}
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
String username = SecurityUtils.getUsername();
|
||||||
// 保存诊断管理
|
// 保存诊断管理
|
||||||
Condition condition;
|
Condition condition;
|
||||||
for (SaveDiagnosisChildParam saveDiagnosisChildParam : diagnosisChildList) {
|
for (SaveDiagnosisChildParam saveDiagnosisChildParam : diagnosisChildList) {
|
||||||
@@ -277,13 +290,24 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
condition.setYbNo(saveDiagnosisChildParam.getYbNo());
|
condition.setYbNo(saveDiagnosisChildParam.getYbNo());
|
||||||
condition.setRecordedDatetime(new Date());
|
condition.setRecordedDatetime(new Date());
|
||||||
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
condition.setRecorderId(SecurityUtils.getLoginUser().getPractitionerId());// 记录人
|
||||||
|
|
||||||
|
if(condition.getCreateBy() == null){
|
||||||
|
condition.setCreateBy(username);
|
||||||
|
}
|
||||||
|
condition.setUpdateBy(username);
|
||||||
|
|
||||||
|
condition.setTenantId(tenantId);
|
||||||
|
if(condition.getCreateTime() == null){
|
||||||
|
condition.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
condition.setUpdateTime(new Date());
|
||||||
iConditionService.saveOrUpdate(condition);
|
iConditionService.saveOrUpdate(condition);
|
||||||
saveDiagnosisChildParam.setConditionId(condition.getId());
|
saveDiagnosisChildParam.setConditionId(condition.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 保存就诊诊断
|
// 保存就诊诊断
|
||||||
EncounterDiagnosis encounterDiagnosis;
|
EncounterDiagnosis encounterDiagnosis = null;
|
||||||
for (SaveDiagnosisChildParam saveDiagnosisChildParam : diagnosisChildList) {
|
for (SaveDiagnosisChildParam saveDiagnosisChildParam : diagnosisChildList) {
|
||||||
if (saveDiagnosisChildParam.getUpdateId() != null) {
|
if (saveDiagnosisChildParam.getUpdateId() != null) {
|
||||||
String updateId = saveDiagnosisChildParam.getUpdateId();
|
String updateId = saveDiagnosisChildParam.getUpdateId();
|
||||||
@@ -306,6 +330,19 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
||||||
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
||||||
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
encounterDiagnosis.setTcmFlag(Whether.YES.getValue());// 中医标识
|
||||||
|
encounterDiagnosis.setDoctor(saveDiagnosisChildParam.getDiagnosisDoctor());
|
||||||
|
encounterDiagnosis.setClassification(saveDiagnosisChildParam.getClassification());
|
||||||
|
encounterDiagnosis.setName(saveDiagnosisChildParam.getName());
|
||||||
|
encounterDiagnosis.setTenantId(tenantId);
|
||||||
|
encounterDiagnosis.setLongTermFlag(saveDiagnosisChildParam.getLongTermFlag());
|
||||||
|
if(encounterDiagnosis.getCreateBy() == null){
|
||||||
|
encounterDiagnosis.setCreateBy(username);
|
||||||
|
}
|
||||||
|
encounterDiagnosis.setUpdateBy(username);
|
||||||
|
if(encounterDiagnosis.getCreateTime() == null){
|
||||||
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
encounterDiagnosis.setUpdateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@@ -319,11 +356,24 @@ public class DoctorStationDiagnosisAppServiceImpl implements IDoctorStationDiagn
|
|||||||
encounterDiagnosis.setMedTypeCode(saveDiagnosisChildParam.getMedTypeCode());// 医疗类型
|
encounterDiagnosis.setMedTypeCode(saveDiagnosisChildParam.getMedTypeCode());// 医疗类型
|
||||||
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
encounterDiagnosis.setDiagnosisDesc(saveDiagnosisChildParam.getDiagnosisDesc()); // 诊断描述
|
||||||
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
encounterDiagnosis.setIptDiseTypeCode(saveDiagnosisChildParam.getIptDiseTypeCode()); // 患者疾病诊断类型代码
|
||||||
|
encounterDiagnosis.setDoctor(saveDiagnosisChildParam.getDiagnosisDoctor());
|
||||||
|
encounterDiagnosis.setClassification(saveDiagnosisChildParam.getClassification());
|
||||||
|
encounterDiagnosis.setName(saveDiagnosisChildParam.getName());
|
||||||
|
encounterDiagnosis.setTenantId(tenantId);
|
||||||
|
encounterDiagnosis.setLongTermFlag(saveDiagnosisChildParam.getLongTermFlag());
|
||||||
|
if(encounterDiagnosis.getCreateBy() == null){
|
||||||
|
encounterDiagnosis.setCreateBy(username);
|
||||||
|
}
|
||||||
|
encounterDiagnosis.setUpdateBy(username);
|
||||||
|
if(encounterDiagnosis.getCreateTime() == null){
|
||||||
|
encounterDiagnosis.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
encounterDiagnosis.setUpdateTime(new Date());
|
||||||
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
iEncounterDiagnosisService.saveOrUpdate(encounterDiagnosis);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
return R.ok(encounterDiagnosis, MessageUtils.createMessage(PromptMsgConstant.Common.M00002, new Object[] {"诊断"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|||||||
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.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.openhis.administration.domain.Encounter;
|
||||||
|
import com.openhis.administration.domain.Patient;
|
||||||
|
import com.openhis.administration.mapper.EncounterMapper;
|
||||||
|
import com.openhis.administration.mapper.PatientMapper;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.sql.Timestamp;
|
||||||
import com.openhis.common.enums.BindingType;
|
import com.openhis.common.enums.BindingType;
|
||||||
|
import com.openhis.common.enums.EncounterStatus;
|
||||||
import com.openhis.document.domain.Emr;
|
import com.openhis.document.domain.Emr;
|
||||||
import com.openhis.document.domain.EmrDetail;
|
import com.openhis.document.domain.EmrDetail;
|
||||||
import com.openhis.document.domain.EmrDict;
|
import com.openhis.document.domain.EmrDict;
|
||||||
@@ -46,6 +54,15 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
@Resource
|
@Resource
|
||||||
IEmrDictService emrDictService;
|
IEmrDictService emrDictService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EncounterMapper encounterMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PatientMapper patientMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private com.openhis.administration.mapper.EncounterParticipantMapper encounterParticipantMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加病人病历信息
|
* 添加病人病历信息
|
||||||
*
|
*
|
||||||
@@ -175,4 +192,131 @@ public class DoctorStationEmrAppServiceImpl implements IDoctorStationEmrAppServi
|
|||||||
return emrTemplateService.removeById(id) ? R.ok() : R.fail();
|
return emrTemplateService.removeById(id) ? R.ok() : R.fail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历列表
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public R<?> getPendingEmrList(Long doctorId) {
|
||||||
|
// 由于Encounter实体中没有jzPractitionerUserId字段,我们需要通过关联查询来获取相关信息
|
||||||
|
// 使用医生工作站的mapper来查询相关数据
|
||||||
|
// 这里我们直接使用医生工作站的查询逻辑
|
||||||
|
|
||||||
|
// 查询当前医生负责的、状态为"就诊中"但还没有写病历的患者
|
||||||
|
// 需要通过EncounterParticipant表来关联医生信息
|
||||||
|
List<Encounter> encounters = encounterMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<Encounter>()
|
||||||
|
.eq(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 过滤出由指定医生负责且还没有写病历的就诊记录
|
||||||
|
List<Map<String, Object>> pendingEmrs = new ArrayList<>();
|
||||||
|
for (Encounter encounter : encounters) {
|
||||||
|
// 检查该就诊记录是否已经有病历
|
||||||
|
Emr existingEmr = emrService.getOne(
|
||||||
|
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounter.getId())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查该就诊是否由指定医生负责
|
||||||
|
boolean isAssignedToDoctor = isEncounterAssignedToDoctor(encounter.getId(), doctorId);
|
||||||
|
|
||||||
|
if (existingEmr == null && isAssignedToDoctor) {
|
||||||
|
// 如果没有病历且由该医生负责,则添加到待写病历列表
|
||||||
|
Map<String, Object> pendingEmr = new java.util.HashMap<>();
|
||||||
|
|
||||||
|
// 获取患者信息
|
||||||
|
Patient patient = patientMapper.selectById(encounter.getPatientId());
|
||||||
|
|
||||||
|
pendingEmr.put("encounterId", encounter.getId());
|
||||||
|
pendingEmr.put("patientId", encounter.getPatientId());
|
||||||
|
pendingEmr.put("patientName", patient != null ? patient.getName() : "未知");
|
||||||
|
pendingEmr.put("gender", patient != null ? patient.getGenderEnum() : null);
|
||||||
|
// 使用出生日期计算年龄
|
||||||
|
pendingEmr.put("age", patient != null && patient.getBirthDate() != null ?
|
||||||
|
calculateAge(patient.getBirthDate()) : null);
|
||||||
|
// 使用创建时间作为挂号时间
|
||||||
|
pendingEmr.put("registerTime", encounter.getCreateTime());
|
||||||
|
pendingEmr.put("busNo", encounter.getBusNo()); // 病历号
|
||||||
|
|
||||||
|
pendingEmrs.add(pendingEmr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok(pendingEmrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历数量
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历数量
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public R<?> getPendingEmrCount(Long doctorId) {
|
||||||
|
// 获取待写病历列表,然后返回数量
|
||||||
|
R<?> result = getPendingEmrList(doctorId);
|
||||||
|
if (result.getCode() == 200) {
|
||||||
|
List<?> pendingEmrs = (List<?>) result.getData();
|
||||||
|
return R.ok(pendingEmrs.size());
|
||||||
|
}
|
||||||
|
return R.ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查患者是否需要写病历
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @return 患者是否需要写病历
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public R<?> checkNeedWriteEmr(Long encounterId) {
|
||||||
|
// 检查该就诊记录是否已经有病历
|
||||||
|
Emr existingEmr = emrService.getOne(
|
||||||
|
new LambdaQueryWrapper<Emr>().eq(Emr::getEncounterId, encounterId)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果没有病历,则需要写病历
|
||||||
|
boolean needWrite = existingEmr == null;
|
||||||
|
return R.ok(needWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查就诊是否分配给指定医生
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 是否分配给指定医生
|
||||||
|
*/
|
||||||
|
private boolean isEncounterAssignedToDoctor(Long encounterId, Long doctorId) {
|
||||||
|
// 查询就诊参与者表,检查是否有指定医生的接诊记录
|
||||||
|
com.openhis.administration.domain.EncounterParticipant participant =
|
||||||
|
encounterParticipantMapper.selectOne(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.openhis.administration.domain.EncounterParticipant>()
|
||||||
|
.eq(com.openhis.administration.domain.EncounterParticipant::getEncounterId, encounterId)
|
||||||
|
.eq(com.openhis.administration.domain.EncounterParticipant::getPractitionerId, doctorId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return participant != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据出生日期计算年龄
|
||||||
|
*
|
||||||
|
* @param birthDate 出生日期
|
||||||
|
* @return 年龄
|
||||||
|
*/
|
||||||
|
private String calculateAge(Date birthDate) {
|
||||||
|
if (birthDate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将java.util.Date转换为java.time.LocalDate
|
||||||
|
java.time.LocalDate birthLocalDate = new java.sql.Timestamp(birthDate.getTime()).toLocalDateTime().toLocalDate();
|
||||||
|
java.time.LocalDate currentDate = java.time.LocalDate.now();
|
||||||
|
|
||||||
|
int age = java.time.Period.between(birthLocalDate, currentDate).getYears();
|
||||||
|
return String.valueOf(age);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -109,6 +111,8 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
e.setAge(e.getBirthDate() != null ? AgeCalculatorUtil.getAge(e.getBirthDate()) : "");
|
e.setAge(e.getBirthDate() != null ? AgeCalculatorUtil.getAge(e.getBirthDate()) : "");
|
||||||
// 就诊状态
|
// 就诊状态
|
||||||
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(EncounterStatus.class, e.getStatusEnum()));
|
e.setStatusEnum_enumText(EnumUtils.getInfoByValue(EncounterStatus.class, e.getStatusEnum()));
|
||||||
|
// 初复诊
|
||||||
|
e.setFirstEnum_enumText(EnumUtils.getInfoByValue(EncounterType.class, e.getFirstEnum()));
|
||||||
});
|
});
|
||||||
return patientInfo;
|
return patientInfo;
|
||||||
}
|
}
|
||||||
@@ -121,6 +125,8 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> receiveEncounter(Long encounterId) {
|
public R<?> receiveEncounter(Long encounterId) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
String currentUsername = SecurityUtils.getUsername();
|
||||||
int update = encounterMapper.update(null,
|
int update = encounterMapper.update(null,
|
||||||
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
|
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
|
||||||
.set(Encounter::getReceptionTime, new Date())
|
.set(Encounter::getReceptionTime, new Date())
|
||||||
@@ -138,6 +144,9 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
encounterParticipant.setTypeCode(ParticipantType.ADMITTER.getCode());// 接诊医生
|
encounterParticipant.setTypeCode(ParticipantType.ADMITTER.getCode());// 接诊医生
|
||||||
encounterParticipant.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
|
encounterParticipant.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
|
||||||
encounterParticipant.setStatusEnum(EncounterActivityStatus.ACTIVE.getValue()); // 状态
|
encounterParticipant.setStatusEnum(EncounterActivityStatus.ACTIVE.getValue()); // 状态
|
||||||
|
encounterParticipant.setTenantId(tenantId);
|
||||||
|
encounterParticipant.setCreateBy(currentUsername);
|
||||||
|
encounterParticipant.setCreateTime(new Date());
|
||||||
iEncounterParticipantService.save(encounterParticipant);
|
iEncounterParticipantService.save(encounterParticipant);
|
||||||
return update > 0 ? R.ok() : R.fail();
|
return update > 0 ? R.ok() : R.fail();
|
||||||
}
|
}
|
||||||
@@ -164,46 +173,47 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> completeEncounter(Long encounterId) {
|
@Transactional(rollbackFor = Exception.class)
|
||||||
// 1. 检查当前患者状态是否为就诊中(20)
|
public R<?> completeEncounter(Long encounterId, Integer firstEnum) {
|
||||||
|
// 1. 检查当前患者状态
|
||||||
Encounter encounter = encounterMapper.selectById(encounterId);
|
Encounter encounter = encounterMapper.selectById(encounterId);
|
||||||
if (encounter == null) {
|
if (encounter == null) {
|
||||||
return R.fail("就诊记录不存在");
|
return R.fail("就诊记录不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查状态是否为就诊中
|
|
||||||
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
|
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
|
||||||
return R.fail("当前患者不在就诊中状态");
|
return R.fail("当前患者不在就诊中状态");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 更新状态为已完成(30),并写入完成时间
|
// 2. 更新状态、完成时间以及初复诊标识
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
int update = encounterMapper.update(null,
|
int update = encounterMapper.update(null,
|
||||||
new LambdaUpdateWrapper<Encounter>().eq(Encounter::getId, encounterId)
|
new LambdaUpdateWrapper<Encounter>()
|
||||||
.set(Encounter::getStatusEnum, EncounterStatus.DISCHARGED.getValue())
|
.eq(Encounter::getId, encounterId)
|
||||||
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.DEPARTED.getValue())
|
.set(Encounter::getStatusEnum, EncounterStatus.DISCHARGED.getValue())
|
||||||
.set(Encounter::getEndTime, now));
|
.set(Encounter::getSubjectStatusEnum, EncounterSubjectStatus.DEPARTED.getValue())
|
||||||
|
.set(Encounter::getEndTime, now)
|
||||||
|
.set(Encounter::getFirstEnum, firstEnum) // 直接在此处更新字段
|
||||||
|
.set(Encounter::getUpdateTime, now));
|
||||||
|
|
||||||
if (update <= 0) {
|
if (update <= 0) return R.fail("完诊失败");
|
||||||
return R.fail("更新状态失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 写入审计日志
|
// 3. 审计日志
|
||||||
try {
|
try {
|
||||||
String username = SecurityUtils.getUsernameSafe();
|
String username = SecurityUtils.getUsernameSafe();
|
||||||
String sql = "INSERT INTO sys_oper_log "
|
String sql = "INSERT INTO sys_oper_log "
|
||||||
+ "(title,oper_time,method,request_method,oper_name,oper_url,oper_param,json_result) "
|
+ "(title,oper_time,method,request_method,oper_name,oper_url,oper_param,json_result) "
|
||||||
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
|
||||||
jdbcTemplate.update(sql,
|
jdbcTemplate.update(sql,
|
||||||
"完诊操作",
|
"完诊操作",
|
||||||
now,
|
now,
|
||||||
"DoctorStationMainAppServiceImpl.completeEncounter()",
|
"DoctorStationMainAppServiceImpl.completeEncounter()",
|
||||||
"POST",
|
"POST",
|
||||||
username,
|
username,
|
||||||
"/doctorstation/main/complete-encounter",
|
"/doctorstation/main/complete-encounter",
|
||||||
"{\"encounterId\": " + encounterId + "}",
|
"{\"encounterId\": " + encounterId + "}",
|
||||||
"{\"code\": 200, \"msg\": \"就诊完成\", \"data\": null}");
|
"{\"code\": 200, \"msg\": \"就诊完成\", \"data\": null}");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("写入完诊审计日志失败", e);
|
log.error("写入完诊审计日志失败", e);
|
||||||
// 审计日志失败不影响主流程
|
// 审计日志失败不影响主流程
|
||||||
@@ -323,4 +333,45 @@ public class DoctorStationMainAppServiceImpl implements IDoctorStationMainAppSer
|
|||||||
practitionerId);
|
practitionerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过号重排核心实现
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class) // 事务保证原子性
|
||||||
|
public R<?> rearrangeMissedEncounter(Long encounterId) {
|
||||||
|
// 1. 校验就诊记录是否存在
|
||||||
|
Encounter encounter = encounterMapper.selectById(encounterId);
|
||||||
|
if (encounter == null) {
|
||||||
|
return R.fail("就诊记录不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 校验状态:仅「在诊(IN_PROGRESS=2)」可重排
|
||||||
|
if (!EncounterStatus.IN_PROGRESS.getValue().equals(encounter.getStatusEnum())) {
|
||||||
|
return R.fail("仅「在诊」状态的患者可执行过号重排");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 核心更新:改回待诊+更新missed_time
|
||||||
|
Date now = new Date();
|
||||||
|
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||||
|
int updateCount = encounterMapper.update(null,
|
||||||
|
new LambdaUpdateWrapper<Encounter>()
|
||||||
|
.eq(Encounter::getId, encounterId)
|
||||||
|
.set(Encounter::getStatusEnum, EncounterStatus.PLANNED.getValue()) // 改回1-待诊
|
||||||
|
.set(Encounter::getMissedTime, now) // 新增:设置过号时间为当前时间
|
||||||
|
.set(Encounter::getUpdateBy, practitionerId.toString()) // 操作医生ID
|
||||||
|
.eq(Encounter::getStatusEnum, EncounterStatus.IN_PROGRESS.getValue())); // 防并发
|
||||||
|
|
||||||
|
if (updateCount == 0) {
|
||||||
|
return R.fail("过号重排失败:状态更新异常");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 同步更新接诊参与记录
|
||||||
|
iEncounterParticipantService.update(new LambdaUpdateWrapper<EncounterParticipant>()
|
||||||
|
.eq(EncounterParticipant::getEncounterId, encounterId)
|
||||||
|
.eq(EncounterParticipant::getTypeCode, ParticipantType.ADMITTER.getCode())
|
||||||
|
.set(EncounterParticipant::getStatusEnum, EncounterActivityStatus.COMPLETED.getValue()));
|
||||||
|
|
||||||
|
return R.ok("过号重排成功");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.openhis.web.doctorstation.controller;
|
package com.openhis.web.doctorstation.controller;
|
||||||
|
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.common.enums.BindingType;
|
||||||
import com.openhis.template.domain.DoctorPhrase;
|
import com.openhis.template.domain.DoctorPhrase;
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorPhraseAppService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -23,7 +24,13 @@ public class DoctorPhraseController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public R<?> getDoctorPhraseList(){
|
public R<?> getDoctorPhraseList(){
|
||||||
return R.ok(doctorPhraseAppService.getDoctorPhraseList());
|
try {
|
||||||
|
return R.ok(doctorPhraseAppService.getDoctorPhraseList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 系统异常,使用error级别日志
|
||||||
|
log.error("获取医生常用语列表系统异常", e);
|
||||||
|
return R.fail("获取医生常用语列表失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +44,13 @@ public class DoctorPhraseController {
|
|||||||
@RequestParam(required = false) Integer phraseType,
|
@RequestParam(required = false) Integer phraseType,
|
||||||
@RequestParam(required = false) String phraseName
|
@RequestParam(required = false) String phraseName
|
||||||
){
|
){
|
||||||
return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName,phraseType));
|
try {
|
||||||
|
return R.ok(doctorPhraseAppService.searchDoctorPhraseList(phraseName, phraseType));
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 系统异常,使用error级别日志
|
||||||
|
log.error("查询医生常用语系统异常", e);
|
||||||
|
return R.fail("查询医生常用语失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +61,22 @@ public class DoctorPhraseController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public R<?> addDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
public R<?> addDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
||||||
return R.ok(doctorPhraseAppService.addDoctorPhrase(doctorPhrase));
|
try {
|
||||||
|
Boolean result = doctorPhraseAppService.addDoctorPhrase(doctorPhrase);
|
||||||
|
if (result != null && result) {
|
||||||
|
return R.ok("新增成功");
|
||||||
|
} else {
|
||||||
|
return R.fail("新增失败");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// 参数错误异常,使用warn级别日志
|
||||||
|
log.warn("新增医生常用语参数错误: {}", e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 系统异常,使用error级别日志
|
||||||
|
log.error("新增医生常用语系统异常", e);
|
||||||
|
return R.fail("新增失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +87,26 @@ public class DoctorPhraseController {
|
|||||||
*/
|
*/
|
||||||
@PutMapping("/update")
|
@PutMapping("/update")
|
||||||
public R<?> updateDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
public R<?> updateDoctorPhrase(@RequestBody DoctorPhrase doctorPhrase){
|
||||||
return R.ok(doctorPhraseAppService.updateDoctorPhrase(doctorPhrase));
|
try {
|
||||||
|
Boolean result = doctorPhraseAppService.updateDoctorPhrase(doctorPhrase);
|
||||||
|
if (result != null && result) {
|
||||||
|
return R.ok("更新成功");
|
||||||
|
} else {
|
||||||
|
return R.fail("更新失败");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// 参数错误异常,使用warn级别日志
|
||||||
|
log.warn("更新医生常用语参数错误: {}", e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
// 权限相关异常,使用warn级别日志
|
||||||
|
log.warn("更新医生常用语权限异常: {}", e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 系统异常,使用error级别日志
|
||||||
|
log.error("更新医生常用语系统异常", e);
|
||||||
|
return R.fail("更新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +117,26 @@ public class DoctorPhraseController {
|
|||||||
*/
|
*/
|
||||||
@DeleteMapping("/delete/{DoctorPhraseId}")
|
@DeleteMapping("/delete/{DoctorPhraseId}")
|
||||||
public R<?> deleteDoctorPhrase(@PathVariable Integer DoctorPhraseId){
|
public R<?> deleteDoctorPhrase(@PathVariable Integer DoctorPhraseId){
|
||||||
return R.ok(doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId));
|
try {
|
||||||
|
Boolean result = doctorPhraseAppService.deleteDoctorPhrase(DoctorPhraseId);
|
||||||
|
if (result != null && result) {
|
||||||
|
return R.ok("删除成功");
|
||||||
|
} else {
|
||||||
|
return R.fail("删除失败");
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// 参数错误异常,使用warn级别日志
|
||||||
|
log.warn("删除医生常用语参数错误: {}", e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
// 权限相关异常,使用warn级别日志
|
||||||
|
log.warn("删除医生常用语权限异常: {}", e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 系统异常,使用error级别日志
|
||||||
|
log.error("删除医生常用语系统异常", e);
|
||||||
|
return R.fail("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package com.openhis.web.doctorstation.controller;
|
package com.openhis.web.doctorstation.controller;
|
||||||
|
|
||||||
import com.core.common.core.domain.R;
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.StringUtils;
|
||||||
import com.openhis.common.enums.Whether;
|
import com.openhis.common.enums.Whether;
|
||||||
import com.openhis.web.doctorstation.appservice.IDoctorStationMainAppService;
|
import com.openhis.web.doctorstation.appservice.IDoctorStationMainAppService;
|
||||||
import com.openhis.web.doctorstation.dto.DoctorStationInitDto;
|
import com.openhis.web.doctorstation.dto.DoctorStationInitDto;
|
||||||
@@ -11,12 +12,10 @@ import com.openhis.web.doctorstation.dto.PatientInfoDto;
|
|||||||
import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto;
|
import com.openhis.web.doctorstation.dto.PrescriptionInfoBaseDto;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 医生站-主页面 controller
|
* 医生站-主页面 controller
|
||||||
@@ -98,19 +97,29 @@ public class DoctorStationMainController {
|
|||||||
/**
|
/**
|
||||||
* 就诊完成
|
* 就诊完成
|
||||||
*
|
*
|
||||||
* @param encounterId 就诊id
|
* @param params 包含 encounterId 和 firstEnum 的键值对
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/complete-encounter")
|
@PostMapping(value = "/complete-encounter") // JSON提交必须用POST
|
||||||
public R<?> completeEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
|
public R<?> completeEncounter(@RequestBody Map<String, Object> params) {
|
||||||
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
|
// 从 map 中提取数据
|
||||||
|
Object encounterIdObj = params.get("encounterId");
|
||||||
|
Object firstEnumObj = params.get("firstEnum");
|
||||||
|
|
||||||
|
if (encounterIdObj == null || StringUtils.isEmpty(encounterIdObj.toString())) {
|
||||||
return R.fail("就诊ID不能为空");
|
return R.fail("就诊ID不能为空");
|
||||||
}
|
}
|
||||||
|
if (firstEnumObj == null) {
|
||||||
|
return R.fail("初复诊状态不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Long id = Long.parseLong(encounterId);
|
Long id = Long.parseLong(encounterIdObj.toString());
|
||||||
return iDoctorStationMainAppService.completeEncounter(id);
|
Integer firstEnum = Integer.parseInt(firstEnumObj.toString());
|
||||||
|
// 调用 Service
|
||||||
|
return iDoctorStationMainAppService.completeEncounter(id, firstEnum);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return R.fail("就诊ID格式错误");
|
return R.fail("数据格式错误(ID或初复诊状态非数字)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +142,29 @@ public class DoctorStationMainController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过号重排
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊id
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/rearrange-missed-encounter")
|
||||||
|
public R<?> rearrangeMissedEncounter(@RequestParam(value = "encounterId", required = false) String encounterId) {
|
||||||
|
// 1. 空值校验(和现有接口保持一致)
|
||||||
|
if (encounterId == null || "undefined".equals(encounterId) || "null".equals(encounterId)) {
|
||||||
|
return R.fail("就诊ID不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 2. 字符串转Long(和现有接口保持一致)
|
||||||
|
Long id = Long.parseLong(encounterId);
|
||||||
|
// 3. 调用AppService的过号重排方法
|
||||||
|
return iDoctorStationMainAppService.rearrangeMissedEncounter(id);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 4. 格式错误处理(和现有接口保持一致)
|
||||||
|
return R.fail("就诊ID格式错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询处方号列表信息
|
* 查询处方号列表信息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.openhis.web.doctorstation.controller;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.web.doctorstation.appservice.IDoctorStationEmrAppService;
|
||||||
|
import com.openhis.web.doctorstation.dto.PatientEmrDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待写病历控制器
|
||||||
|
* 用于处理医生待写病历的相关操作
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/doctor-station/pending-emr")
|
||||||
|
@Slf4j
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PendingEmrController {
|
||||||
|
|
||||||
|
private final IDoctorStationEmrAppService iDoctorStationEmrAppService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历列表
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/pending-list")
|
||||||
|
public R<?> getPendingEmrList(@RequestParam(required = false) Long doctorId) {
|
||||||
|
// 如果没有传递医生ID,则使用当前登录用户ID
|
||||||
|
if (doctorId == null) {
|
||||||
|
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务获取待写病历列表
|
||||||
|
return iDoctorStationEmrAppService.getPendingEmrList(doctorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取待写病历数量
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @return 待写病历数量
|
||||||
|
*/
|
||||||
|
@GetMapping("/pending-count")
|
||||||
|
public R<?> getPendingEmrCount(@RequestParam(required = false) Long doctorId) {
|
||||||
|
// 如果没有传递医生ID,则使用当前登录用户ID
|
||||||
|
if (doctorId == null) {
|
||||||
|
doctorId = com.core.common.utils.SecurityUtils.getLoginUser().getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务获取待写病历数量
|
||||||
|
return iDoctorStationEmrAppService.getPendingEmrCount(doctorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查患者是否需要写病历
|
||||||
|
*
|
||||||
|
* @param encounterId 就诊ID
|
||||||
|
* @return 患者是否需要写病历
|
||||||
|
*/
|
||||||
|
@GetMapping("/need-write-emr")
|
||||||
|
public R<?> checkNeedWriteEmr(@RequestParam Long encounterId) {
|
||||||
|
return iDoctorStationEmrAppService.checkNeedWriteEmr(encounterId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,9 @@ public class AdviceBaseDto {
|
|||||||
/**
|
/**
|
||||||
* 医嘱详细分类
|
* 医嘱详细分类
|
||||||
*/
|
*/
|
||||||
|
@Dict(dictCode = "activity_category_code")
|
||||||
private String categoryCode;
|
private String categoryCode;
|
||||||
|
private String categoryCode_dictText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 药品性质
|
* 药品性质
|
||||||
@@ -104,9 +106,14 @@ public class AdviceBaseDto {
|
|||||||
private String productName;
|
private String productName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 活动类型
|
* 活动类型(诊疗项目使用目录类别)
|
||||||
*/
|
*/
|
||||||
|
@Dict(dictCode = "activity_category_code")
|
||||||
private Integer activityType;
|
private Integer activityType;
|
||||||
|
private String activityType_dictText;
|
||||||
|
/**
|
||||||
|
* 活动类型枚举文本 (手动赋值用)
|
||||||
|
*/
|
||||||
private String activityType_enumText;
|
private String activityType_enumText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.openhis.web.doctorstation.dto;
|
package com.openhis.web.doctorstation.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||||
import com.openhis.common.annotation.Dict;
|
import com.openhis.common.annotation.Dict;
|
||||||
@@ -127,4 +128,15 @@ public class PatientInfoDto {
|
|||||||
* 就诊卡号
|
* 就诊卡号
|
||||||
*/
|
*/
|
||||||
private String identifierNo;
|
private String identifierNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过号时间
|
||||||
|
*/
|
||||||
|
private Date missedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初复诊标识
|
||||||
|
*/
|
||||||
|
private Integer firstEnum;
|
||||||
|
private String firstEnum_enumText;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存诊断 子参数类
|
* 保存诊断 子参数类
|
||||||
*
|
*
|
||||||
@@ -33,6 +35,9 @@ public class SaveDiagnosisChildParam {
|
|||||||
@JsonSerialize(using = ToStringSerializer.class)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private Long definitionId;
|
private Long definitionId;
|
||||||
|
|
||||||
|
private String classification;
|
||||||
|
|
||||||
|
private String name;
|
||||||
/**
|
/**
|
||||||
* 医保编码
|
* 医保编码
|
||||||
*/
|
*/
|
||||||
@@ -64,6 +69,18 @@ public class SaveDiagnosisChildParam {
|
|||||||
*/
|
*/
|
||||||
private String diagnosisDesc;
|
private String diagnosisDesc;
|
||||||
|
|
||||||
|
private String diagnosisDoctor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 诊断时间
|
||||||
|
*/
|
||||||
|
private Date diagnosisTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发病时间
|
||||||
|
*/
|
||||||
|
private Date onsetDate;
|
||||||
|
|
||||||
/** 患者疾病诊断类型代码 */
|
/** 患者疾病诊断类型代码 */
|
||||||
private Integer iptDiseTypeCode;
|
private Integer iptDiseTypeCode;
|
||||||
|
|
||||||
@@ -77,4 +94,6 @@ public class SaveDiagnosisChildParam {
|
|||||||
*/
|
*/
|
||||||
private String updateConditionId;
|
private String updateConditionId;
|
||||||
|
|
||||||
|
private Integer longTermFlag;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,11 +293,8 @@ public class ATDManageAppServiceImpl implements IATDManageAppService {
|
|||||||
if (admissionPatientInfoDto.getPriorityEnum() != null) {
|
if (admissionPatientInfoDto.getPriorityEnum() != null) {
|
||||||
// 更新患者病情
|
// 更新患者病情
|
||||||
encounterService.updatePriorityEnumById(encounterId, admissionPatientInfoDto.getPriorityEnum());
|
encounterService.updatePriorityEnumById(encounterId, admissionPatientInfoDto.getPriorityEnum());
|
||||||
// 将之前的住院参与者更新为已完成
|
// 将之前的住院参与者更新为已完成(如果存在的话)
|
||||||
Integer result = encounterParticipantService.updateEncounterParticipantsStatus(encounterId);
|
encounterParticipantService.updateEncounterParticipantsStatus(encounterId);
|
||||||
if (result == 0) {
|
|
||||||
return R.fail("患者信息更新失败,请联系管理员");
|
|
||||||
}
|
|
||||||
// 更新住院参与者
|
// 更新住院参与者
|
||||||
// 住院医生
|
// 住院医生
|
||||||
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
|
encounterParticipantService.creatEncounterParticipants(encounterId, startTime,
|
||||||
|
|||||||
@@ -462,6 +462,7 @@ public class NurseBillingAppService implements INurseBillingAppService {
|
|||||||
|
|
||||||
// 基础配置:主键(新增为null,修改为已有ID)、状态、业务编号
|
// 基础配置:主键(新增为null,修改为已有ID)、状态、业务编号
|
||||||
deviceRequest.setId(adviceDto.getRequestId());
|
deviceRequest.setId(adviceDto.getRequestId());
|
||||||
|
deviceRequest.setTenantId(loginUser.getTenantId()); // 显式设置租户ID
|
||||||
// 业务编号:按日生成,前缀+4位序列号(确保每日唯一)
|
// 业务编号:按日生成,前缀+4位序列号(确保每日唯一)
|
||||||
deviceRequest
|
deviceRequest
|
||||||
.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), DEVICE_RES_NO_SEQ_LENGTH));
|
.setBusNo(assignSeqUtil.getSeqByDay(AssignSeqEnum.DEVICE_RES_NO.getPrefix(), DEVICE_RES_NO_SEQ_LENGTH));
|
||||||
@@ -533,6 +534,7 @@ public class NurseBillingAppService implements INurseBillingAppService {
|
|||||||
// 基础配置:主键、状态、业务编号、签发编码
|
// 基础配置:主键、状态、业务编号、签发编码
|
||||||
serviceRequest.setId(activityDto.getRequestId()); // 主键ID(新增为null,修改为已有ID)
|
serviceRequest.setId(activityDto.getRequestId()); // 主键ID(新增为null,修改为已有ID)
|
||||||
serviceRequest.setStatusEnum(RequestStatus.ACTIVE.getValue()); // 状态:激活(划价即生效)
|
serviceRequest.setStatusEnum(RequestStatus.ACTIVE.getValue()); // 状态:激活(划价即生效)
|
||||||
|
serviceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
serviceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
serviceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
serviceRequest.setSignCode(signCode); // 全局签发编码(关联同一批次划价的医嘱)
|
serviceRequest.setSignCode(signCode); // 全局签发编码(关联同一批次划价的医嘱)
|
||||||
serviceRequest.setOccurrenceStartTime(startTime); // 医嘱开始执行时间
|
serviceRequest.setOccurrenceStartTime(startTime); // 医嘱开始执行时间
|
||||||
|
|||||||
@@ -200,7 +200,29 @@ public class IInventoryAdjustPriceServiceImpl implements IInventoryAdjustPriceSe
|
|||||||
itemDefDetailPurchaseList.add(chargeItemDefDetail);
|
itemDefDetailPurchaseList.add(chargeItemDefDetail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 批量插入价格子表
|
// 批量插入价格子表(在保存前设置必需字段)
|
||||||
|
// 只对新插入的记录(id为null)设置字段
|
||||||
|
List<ChargeItemDefDetail> newRetailList = itemDefDetailRetailList.stream()
|
||||||
|
.filter(detail -> detail.getId() == null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!newRetailList.isEmpty()) {
|
||||||
|
this.chargeItemDefDetailService.setRequiredFieldsBatch(newRetailList);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ChargeItemDefDetail> newBuyingList = itemDefDetailBuyingList.stream()
|
||||||
|
.filter(detail -> detail.getId() == null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!newBuyingList.isEmpty()) {
|
||||||
|
this.chargeItemDefDetailService.setRequiredFieldsBatch(newBuyingList);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ChargeItemDefDetail> newPurchaseList = itemDefDetailPurchaseList.stream()
|
||||||
|
.filter(detail -> detail.getId() == null)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!newPurchaseList.isEmpty()) {
|
||||||
|
this.chargeItemDefDetailService.setRequiredFieldsBatch(newPurchaseList);
|
||||||
|
}
|
||||||
|
|
||||||
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailRetailList);
|
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailRetailList);
|
||||||
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailBuyingList);
|
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailBuyingList);
|
||||||
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailPurchaseList);
|
this.chargeItemDefDetailService.saveOrUpdateBatch(itemDefDetailPurchaseList);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.openhis.web.inventorymanage.dto.*;
|
|||||||
import com.openhis.web.inventorymanage.mapper.PurchaseInventoryMapper;
|
import com.openhis.web.inventorymanage.mapper.PurchaseInventoryMapper;
|
||||||
import com.openhis.workflow.domain.SupplyRequest;
|
import com.openhis.workflow.domain.SupplyRequest;
|
||||||
import com.openhis.workflow.service.ISupplyRequestService;
|
import com.openhis.workflow.service.ISupplyRequestService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -40,6 +41,7 @@ import java.util.stream.Stream;
|
|||||||
* @author zwh
|
* @author zwh
|
||||||
* @date 2025-03-08
|
* @date 2025-03-08
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppService {
|
public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppService {
|
||||||
|
|
||||||
@@ -159,6 +161,7 @@ public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppSer
|
|||||||
@Override
|
@Override
|
||||||
public R<List<ReceiptDetailDto>> getDetail(String busNo) {
|
public R<List<ReceiptDetailDto>> getDetail(String busNo) {
|
||||||
List<ReceiptDetailDto> receiptDetailList = purchaseInventoryMapper.selectDetail(busNo);
|
List<ReceiptDetailDto> receiptDetailList = purchaseInventoryMapper.selectDetail(busNo);
|
||||||
|
log.debug("返回查询结果,receiptDetailList:{}", receiptDetailList);
|
||||||
if (receiptDetailList.isEmpty()) {
|
if (receiptDetailList.isEmpty()) {
|
||||||
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, null));
|
return R.fail(MessageUtils.createMessage(PromptMsgConstant.Common.M00006, null));
|
||||||
}
|
}
|
||||||
@@ -182,7 +185,7 @@ public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppSer
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
log.debug("返回查询结果,receiptDetailList:{}", receiptDetailList);
|
||||||
return R.ok(receiptDetailList);
|
return R.ok(receiptDetailList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +197,6 @@ public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppSer
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public R<?> addOrEditInventoryReceipt(List<PurchaseInventoryDto> purchaseInventoryDtoList) {
|
public R<?> addOrEditInventoryReceipt(List<PurchaseInventoryDto> purchaseInventoryDtoList) {
|
||||||
|
|
||||||
// 校验(已经审批通过的单号(请求状态是同意),不能再重复编辑请求)
|
// 校验(已经审批通过的单号(请求状态是同意),不能再重复编辑请求)
|
||||||
boolean validation = supplyRequestService.supplyRequestValidation(purchaseInventoryDtoList.get(0).getBusNo());
|
boolean validation = supplyRequestService.supplyRequestValidation(purchaseInventoryDtoList.get(0).getBusNo());
|
||||||
if (validation) {
|
if (validation) {
|
||||||
@@ -232,11 +234,14 @@ public class PurchaseInventoryAppServiceImpl implements IPurchaseInventoryAppSer
|
|||||||
// 制单人
|
// 制单人
|
||||||
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
.setApplicantId(SecurityUtils.getLoginUser().getPractitionerId())
|
||||||
// 申请时间
|
// 申请时间
|
||||||
.setApplyTime(DateUtils.getNowDate());
|
.setApplyTime(DateUtils.getNowDate())
|
||||||
|
|
||||||
|
.setCreateBy(SecurityUtils.getLoginUser().getUsername())
|
||||||
|
.setCreateTime(DateUtils.getNowDate())
|
||||||
|
.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
supplyRequestList.add(supplyRequest);
|
supplyRequestList.add(supplyRequest);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
supplyRequestService.saveOrUpdateBatch(supplyRequestList);
|
supplyRequestService.saveOrUpdateBatch(supplyRequestList);
|
||||||
|
|
||||||
|
|||||||
@@ -51,10 +51,11 @@ public class InspectionPackageController extends BaseController {
|
|||||||
if (result) {
|
if (result) {
|
||||||
log.info("新增检验套餐成功:packageName={}, basicInformationId={}",
|
log.info("新增检验套餐成功:packageName={}, basicInformationId={}",
|
||||||
inspectionPackage.getPackageName(), inspectionPackage.getBasicInformationId());
|
inspectionPackage.getPackageName(), inspectionPackage.getBasicInformationId());
|
||||||
|
String idStr = inspectionPackage.getBasicInformationId() == null ? null : String.valueOf(inspectionPackage.getBasicInformationId());
|
||||||
return AjaxResult.success()
|
return AjaxResult.success()
|
||||||
.put("packageId", inspectionPackage.getBasicInformationId()) // 保持向后兼容
|
.put("packageId", idStr) // 保持向后兼容(前端按字符串处理,避免精度丢失)
|
||||||
.put("basicInformationId", inspectionPackage.getBasicInformationId())
|
.put("basicInformationId", idStr)
|
||||||
.put("id", inspectionPackage.getBasicInformationId());
|
.put("id", idStr);
|
||||||
} else {
|
} else {
|
||||||
return AjaxResult.error("新增失败");
|
return AjaxResult.error("新增失败");
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@ public class InspectionPackageController extends BaseController {
|
|||||||
* 查询检验套餐详情
|
* 查询检验套餐详情
|
||||||
*/
|
*/
|
||||||
@GetMapping("/{basicInformationId}")
|
@GetMapping("/{basicInformationId}")
|
||||||
public AjaxResult getInfo(@PathVariable String basicInformationId) {
|
public AjaxResult getInfo(@PathVariable Long basicInformationId) {
|
||||||
InspectionPackage inspectionPackage = inspectionPackageService.selectPackageById(basicInformationId);
|
InspectionPackage inspectionPackage = inspectionPackageService.selectPackageById(basicInformationId);
|
||||||
if (inspectionPackage == null) {
|
if (inspectionPackage == null) {
|
||||||
return AjaxResult.error("套餐不存在");
|
return AjaxResult.error("套餐不存在");
|
||||||
@@ -121,15 +122,57 @@ public class InspectionPackageController extends BaseController {
|
|||||||
if (pageNum == null) pageNum = 1;
|
if (pageNum == null) pageNum = 1;
|
||||||
if (pageSize == null) pageSize = 10;
|
if (pageSize == null) pageSize = 10;
|
||||||
|
|
||||||
List<InspectionPackage> list = inspectionPackageService.selectPackageList(inspectionPackage, pageNum, pageSize);
|
// 使用MyBatis Plus分页查询
|
||||||
return getDataTable(list);
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<InspectionPackage> page =
|
||||||
|
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize);
|
||||||
|
|
||||||
|
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<InspectionPackage> queryWrapper =
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
if (inspectionPackage != null) {
|
||||||
|
if (inspectionPackage.getPackageName() != null && !inspectionPackage.getPackageName().isEmpty()) {
|
||||||
|
queryWrapper.like("package_name", inspectionPackage.getPackageName());
|
||||||
|
}
|
||||||
|
if (inspectionPackage.getPackageLevel() != null && !inspectionPackage.getPackageLevel().isEmpty()) {
|
||||||
|
queryWrapper.eq("package_level", inspectionPackage.getPackageLevel());
|
||||||
|
}
|
||||||
|
if (inspectionPackage.getDepartment() != null && !inspectionPackage.getDepartment().isEmpty()) {
|
||||||
|
queryWrapper.eq("department", inspectionPackage.getDepartment());
|
||||||
|
}
|
||||||
|
if (inspectionPackage.getPackageCategory() != null && !inspectionPackage.getPackageCategory().isEmpty()) {
|
||||||
|
queryWrapper.eq("package_category", inspectionPackage.getPackageCategory());
|
||||||
|
}
|
||||||
|
if (inspectionPackage.getIsDisabled() != null) {
|
||||||
|
queryWrapper.eq("is_disabled", inspectionPackage.getIsDisabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认只查询未删除的记录
|
||||||
|
queryWrapper.eq("del_flag", false);
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
queryWrapper.orderByDesc("create_time");
|
||||||
|
|
||||||
|
// 执行分页查询
|
||||||
|
com.baomidou.mybatisplus.extension.plugins.pagination.Page<InspectionPackage> resultPage =
|
||||||
|
inspectionPackageService.page(page, queryWrapper);
|
||||||
|
|
||||||
|
// 构建返回结果
|
||||||
|
TableDataInfo dataTable = new TableDataInfo();
|
||||||
|
dataTable.setCode(com.core.common.constant.HttpStatus.SUCCESS);
|
||||||
|
dataTable.setMsg("查询成功");
|
||||||
|
dataTable.setRows(resultPage.getRecords());
|
||||||
|
dataTable.setTotal(resultPage.getTotal());
|
||||||
|
|
||||||
|
return dataTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除检验套餐
|
* 删除检验套餐
|
||||||
*/
|
*/
|
||||||
@DeleteMapping("/{basicInformationId}")
|
@DeleteMapping("/{basicInformationId}")
|
||||||
public AjaxResult remove(@PathVariable String basicInformationId) {
|
public AjaxResult remove(@PathVariable Long basicInformationId) {
|
||||||
// 校验套餐是否存在
|
// 校验套餐是否存在
|
||||||
InspectionPackage existing = inspectionPackageService.selectPackageById(basicInformationId);
|
InspectionPackage existing = inspectionPackageService.selectPackageById(basicInformationId);
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
@@ -178,7 +221,7 @@ public class InspectionPackageController extends BaseController {
|
|||||||
* 查询检验套餐明细列表
|
* 查询检验套餐明细列表
|
||||||
*/
|
*/
|
||||||
@GetMapping("/details/{basicInformationId}")
|
@GetMapping("/details/{basicInformationId}")
|
||||||
public AjaxResult getDetails(@PathVariable String basicInformationId) {
|
public AjaxResult getDetails(@PathVariable Long basicInformationId) {
|
||||||
// 校验套餐是否存在
|
// 校验套餐是否存在
|
||||||
InspectionPackage inspectionPackage = inspectionPackageService.selectPackageById(basicInformationId);
|
InspectionPackage inspectionPackage = inspectionPackageService.selectPackageById(basicInformationId);
|
||||||
if (inspectionPackage == null) {
|
if (inspectionPackage == null) {
|
||||||
@@ -235,11 +278,11 @@ public class InspectionPackageController extends BaseController {
|
|||||||
|
|
||||||
// 请求DTO类
|
// 请求DTO类
|
||||||
public static class BatchSaveDetailRequest {
|
public static class BatchSaveDetailRequest {
|
||||||
private String basicInformationId;
|
private Long basicInformationId;
|
||||||
private List<InspectionPackageDetail> details;
|
private List<InspectionPackageDetail> details;
|
||||||
|
|
||||||
public String getBasicInformationId() { return basicInformationId; }
|
public Long getBasicInformationId() { return basicInformationId; }
|
||||||
public void setBasicInformationId(String basicInformationId) { this.basicInformationId = basicInformationId; }
|
public void setBasicInformationId(Long basicInformationId) { this.basicInformationId = basicInformationId; }
|
||||||
public List<InspectionPackageDetail> getDetails() { return details; }
|
public List<InspectionPackageDetail> getDetails() { return details; }
|
||||||
public void setDetails(List<InspectionPackageDetail> details) { this.details = details; }
|
public void setDetails(List<InspectionPackageDetail> details) { this.details = details; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,12 +308,20 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
|||||||
* @return 患者信息
|
* @return 患者信息
|
||||||
*/
|
*/
|
||||||
private Patient handlePatientInfo(PatientBaseInfoDto patientInfoDto) {
|
private Patient handlePatientInfo(PatientBaseInfoDto patientInfoDto) {
|
||||||
Patient patient = new Patient();
|
Patient patient;
|
||||||
patient.setId(patientInfoDto.getId());
|
if (patientInfoDto.getId() != null) {
|
||||||
if (patientInfoDto.getId() == null) {
|
// 更新现有患者信息
|
||||||
|
patient = patientService.getById(patientInfoDto.getId());
|
||||||
|
if (patient == null) {
|
||||||
|
throw new ServiceException("患者信息不存在,无法更新");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新增患者信息
|
||||||
|
patient = new Patient();
|
||||||
patient.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.PATIENT_NUM.getPrefix(), 10));
|
patient.setBusNo(assignSeqUtil.getSeq(AssignSeqEnum.PATIENT_NUM.getPrefix(), 10));
|
||||||
patientInfoDto.setActiveFlag(PublicationStatus.ACTIVE.getValue()); // 默认启用
|
patient.setActiveFlag(PublicationStatus.ACTIVE.getValue()); // 默认启用
|
||||||
}
|
}
|
||||||
|
|
||||||
patient.setName(patientInfoDto.getName()); // 患者姓名
|
patient.setName(patientInfoDto.getName()); // 患者姓名
|
||||||
patient.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(patientInfoDto.getName())); // 拼音首拼
|
patient.setPyStr(ChineseConvertUtils.toPinyinFirstLetter(patientInfoDto.getName())); // 拼音首拼
|
||||||
patient.setWbStr(ChineseConvertUtils.toWBFirstLetter(patientInfoDto.getName())); // 五笔首拼
|
patient.setWbStr(ChineseConvertUtils.toWBFirstLetter(patientInfoDto.getName())); // 五笔首拼
|
||||||
@@ -337,7 +345,14 @@ public class PatientInformationServiceImpl implements IPatientInformationService
|
|||||||
patient.setDeceasedDate(patientInfoDto.getDeceasedDate()); // 死亡时间
|
patient.setDeceasedDate(patientInfoDto.getDeceasedDate()); // 死亡时间
|
||||||
patient.setNationalityCode(patientInfoDto.getNationalityCode());// 民族
|
patient.setNationalityCode(patientInfoDto.getNationalityCode());// 民族
|
||||||
patient.setActiveFlag(patientInfoDto.getActiveFlag());// 活动标识
|
patient.setActiveFlag(patientInfoDto.getActiveFlag());// 活动标识
|
||||||
patientService.saveOrUpdate(patient);
|
|
||||||
|
if (patientInfoDto.getId() != null) {
|
||||||
|
// 更新操作
|
||||||
|
patientService.updateById(patient);
|
||||||
|
} else {
|
||||||
|
// 新增操作
|
||||||
|
patientService.save(patient);
|
||||||
|
}
|
||||||
|
|
||||||
return patient;
|
return patient;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1419,6 +1419,19 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保所有PaymentRecDetail对象的审计字段被设置,防止违反NOT NULL约束
|
||||||
|
for (PaymentRecDetail detail : paymentRecDetails) {
|
||||||
|
if (detail.getTenantId() == null) {
|
||||||
|
detail.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
}
|
||||||
|
if (detail.getCreateBy() == null || detail.getCreateBy().isEmpty()) {
|
||||||
|
detail.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
}
|
||||||
|
if (detail.getCreateTime() == null) {
|
||||||
|
detail.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
paymentRecDetailService.saveBatch(paymentRecDetails);
|
paymentRecDetailService.saveBatch(paymentRecDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1754,6 +1767,17 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
BeanUtils.copyProperties(outpatientRegistrationAddParam.getChargeItemFormData(), chargeItem);
|
BeanUtils.copyProperties(outpatientRegistrationAddParam.getChargeItemFormData(), chargeItem);
|
||||||
chargeItem.setContextEnum(ChargeItemContext.REGISTER.getValue());// 挂号
|
chargeItem.setContextEnum(ChargeItemContext.REGISTER.getValue());// 挂号
|
||||||
|
|
||||||
|
// 确保必需的审计字段被设置,防止违反NOT NULL约束
|
||||||
|
if (chargeItem.getTenantId() == null) {
|
||||||
|
chargeItem.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
}
|
||||||
|
if (chargeItem.getCreateBy() == null || chargeItem.getCreateBy().isEmpty()) {
|
||||||
|
chargeItem.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
}
|
||||||
|
if (chargeItem.getCreateTime() == null) {
|
||||||
|
chargeItem.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
YbPsnSetlWay finCategory = YbPsnSetlWay.getByValue(outpatientRegistrationAddParam.getYbPsnSetlWay());
|
YbPsnSetlWay finCategory = YbPsnSetlWay.getByValue(outpatientRegistrationAddParam.getYbPsnSetlWay());
|
||||||
if (finCategory == null) {
|
if (finCategory == null) {
|
||||||
throw new ServiceException("请选择收费方式");
|
throw new ServiceException("请选择收费方式");
|
||||||
@@ -1818,11 +1842,23 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
// 保存就诊信息
|
// 保存就诊信息
|
||||||
Encounter encounter = new Encounter();
|
Encounter encounter = new Encounter();
|
||||||
BeanUtils.copyProperties(encounterFormData, encounter);
|
BeanUtils.copyProperties(encounterFormData, encounter);
|
||||||
// 将挂号医生ID提前塞入 encounter 的 registrarId,便于生成“科室+医生+当日”序号
|
// 将挂号医生ID提前塞入 encounter 的 registrarId,便于生成"科室+医生+当日"序号
|
||||||
if (encounterParticipantFormData.getPractitionerId() != null) {
|
if (encounterParticipantFormData.getPractitionerId() != null) {
|
||||||
encounter.setRegistrarId(encounterParticipantFormData.getPractitionerId());
|
encounter.setRegistrarId(encounterParticipantFormData.getPractitionerId());
|
||||||
}
|
}
|
||||||
encounter.setBusNo(outpatientRegistrationSettleParam.getBusNo());
|
encounter.setBusNo(outpatientRegistrationSettleParam.getBusNo());
|
||||||
|
|
||||||
|
// 确保必需的审计字段被设置,防止违反NOT NULL约束
|
||||||
|
if (encounter.getTenantId() == null) {
|
||||||
|
encounter.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
}
|
||||||
|
if (encounter.getCreateBy() == null || encounter.getCreateBy().isEmpty()) {
|
||||||
|
encounter.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
}
|
||||||
|
if (encounter.getCreateTime() == null) {
|
||||||
|
encounter.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
// 就诊ID
|
// 就诊ID
|
||||||
Long encounterId = iEncounterService.saveEncounterByRegister(encounter);
|
Long encounterId = iEncounterService.saveEncounterByRegister(encounter);
|
||||||
// 保存就诊位置信息
|
// 保存就诊位置信息
|
||||||
@@ -1920,6 +1956,18 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
.setChargeItemIds(chargeItemIdList.stream().map(String::valueOf).collect(Collectors.joining(",")))
|
.setChargeItemIds(chargeItemIdList.stream().map(String::valueOf).collect(Collectors.joining(",")))
|
||||||
.setTenderedAmount(chargeItem.getTotalPrice()).setDisplayAmount(paymentResult.getPsnPartAmt())
|
.setTenderedAmount(chargeItem.getTotalPrice()).setDisplayAmount(paymentResult.getPsnPartAmt())
|
||||||
.setReturnedAmount(new BigDecimal("0.0")).setEncounterId(encounter.getId());
|
.setReturnedAmount(new BigDecimal("0.0")).setEncounterId(encounter.getId());
|
||||||
|
|
||||||
|
// 确保必需的审计字段被设置,防止违反NOT NULL约束
|
||||||
|
if (payment.getTenantId() == null) {
|
||||||
|
payment.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
}
|
||||||
|
if (payment.getCreateBy() == null || payment.getCreateBy().isEmpty()) {
|
||||||
|
payment.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
}
|
||||||
|
if (payment.getCreateTime() == null) {
|
||||||
|
payment.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
// 保存付款信息
|
// 保存付款信息
|
||||||
paymentReconciliationService.save(payment);
|
paymentReconciliationService.save(payment);
|
||||||
// 保存付款详情
|
// 保存付款详情
|
||||||
@@ -1938,6 +1986,18 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
invoice.setPatientId(encounter.getPatientId()).setStatusEnum(InvoiceStatus.DRAFT)
|
invoice.setPatientId(encounter.getPatientId()).setStatusEnum(InvoiceStatus.DRAFT)
|
||||||
.setReconciliationId(payment.getId()).setTypeCode(InvoiceType.ISSUING_INVOICES.getValue())
|
.setReconciliationId(payment.getId()).setTypeCode(InvoiceType.ISSUING_INVOICES.getValue())
|
||||||
.setChargeItemIds(payment.getChargeItemIds().toString());
|
.setChargeItemIds(payment.getChargeItemIds().toString());
|
||||||
|
|
||||||
|
// 确保必需的审计字段被设置,防止违反NOT NULL约束
|
||||||
|
if (invoice.getTenantId() == null) {
|
||||||
|
invoice.setTenantId(SecurityUtils.getLoginUser().getTenantId());
|
||||||
|
}
|
||||||
|
if (invoice.getCreateBy() == null || invoice.getCreateBy().isEmpty()) {
|
||||||
|
invoice.setCreateBy(SecurityUtils.getLoginUser().getUsername());
|
||||||
|
}
|
||||||
|
if (invoice.getCreateTime() == null) {
|
||||||
|
invoice.setCreateTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
iInvoiceService.save(invoice);
|
iInvoiceService.save(invoice);
|
||||||
return R.ok(payment, "收费成功");
|
return R.ok(payment, "收费成功");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,15 +57,15 @@ public class PharmacyWarehouseProfitLossOrderServiceImpl implements IPharmacyWar
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new
|
supplyCategoryOption.add(new
|
||||||
// PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.GENERAL_PROFIT_AND_LOSS.getValue(),
|
PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.GENERAL_PROFIT_AND_LOSS.getValue(),
|
||||||
// SupplyCategory.GENERAL_PROFIT_AND_LOSS.getInfo()));
|
SupplyCategory.GENERAL_PROFIT_AND_LOSS.getInfo()));
|
||||||
// supplyCategoryOption.add(new
|
supplyCategoryOption.add(new
|
||||||
// PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.STOCKTAKING_PROFIT_AND_LOSS.getValue(),
|
PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.STOCKTAKING_PROFIT_AND_LOSS.getValue(),
|
||||||
// SupplyCategory.STOCKTAKING_PROFIT_AND_LOSS.getInfo()));
|
SupplyCategory.STOCKTAKING_PROFIT_AND_LOSS.getInfo()));
|
||||||
// supplyCategoryOption.add(new
|
supplyCategoryOption.add(new
|
||||||
// PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.PREPARATION_CONSUMPTION.getValue(),
|
PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.PREPARATION_CONSUMPTION.getValue(),
|
||||||
// SupplyCategory.PREPARATION_CONSUMPTION.getInfo()));
|
SupplyCategory.PREPARATION_CONSUMPTION.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ public class PharmacyWarehousePurchaseOrderServiceImpl implements IPharmacyWareh
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NORMAL.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.STOCK_SUPPLY.getValue(),
|
||||||
// SupplyCategory.NORMAL.getInfo()));
|
SupplyCategory.STOCK_SUPPLY.getInfo()));
|
||||||
// supplyCategoryOption.add(new
|
supplyCategoryOption.add(new
|
||||||
// PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.PURCHASE_PLAN_GENERATION.getValue(),
|
PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NON_STOCK.getValue(),
|
||||||
// SupplyCategory.PURCHASE_PLAN_GENERATION.getInfo()));
|
SupplyCategory.NON_STOCK.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public class PharmacyWarehouseReturnOrderServiceImpl implements IPharmacyWarehou
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NORMAL.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NORMAL.getValue(),
|
||||||
// SupplyCategory.NORMAL.getInfo()));
|
SupplyCategory.NORMAL.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public class PharmacyWarehouseReturnToWarehouseOrderServiceImpl implements IPhar
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NORMAL.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.NORMAL.getValue(),
|
||||||
// SupplyCategory.NORMAL.getInfo()));
|
SupplyCategory.NORMAL.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -57,18 +57,16 @@ public class PharmacyWarehouseStockInOrderServiceImpl implements IPharmacyWareho
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.PURCHASED_DRUGS_WAREHOUSING.getValue(),
|
||||||
// .add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.PURCHASED_DRUGS_WAREHOUSING.getValue(),
|
SupplyCategory.PURCHASED_DRUGS_WAREHOUSING.getInfo()));
|
||||||
// SupplyCategory.PURCHASED_DRUGS_WAREHOUSING.getInfo()));
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
SupplyCategory.HOMEMADE_DRUGS_WAREHOUSING.getValue(), SupplyCategory.HOMEMADE_DRUGS_WAREHOUSING.getInfo()));
|
||||||
// SupplyCategory.HOMEMADE_DRUGS_WAREHOUSING.getValue(), SupplyCategory.HOMEMADE_DRUGS_WAREHOUSING.getInfo()));
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.CONSIGNMENT_DRUGS_WAREHOUSING.getValue(),
|
||||||
// supplyCategoryOption
|
SupplyCategory.CONSIGNMENT_DRUGS_WAREHOUSING.getInfo()));
|
||||||
// .add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.CONSIGNMENT_DRUGS_WAREHOUSING.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
||||||
// SupplyCategory.CONSIGNMENT_DRUGS_WAREHOUSING.getInfo()));
|
SupplyCategory.OTHER_DRUGS_WAREHOUSING.getValue(), SupplyCategory.OTHER_DRUGS_WAREHOUSING.getInfo()));
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
||||||
// SupplyCategory.OTHER_DRUGS_WAREHOUSING.getValue(), SupplyCategory.OTHER_DRUGS_WAREHOUSING.getInfo()));
|
SupplyCategory.DONATED_DRUGS_WAREHOUSING.getValue(), SupplyCategory.DONATED_DRUGS_WAREHOUSING.getInfo()));
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
|
||||||
// SupplyCategory.DONATED_DRUGS_WAREHOUSING.getValue(), SupplyCategory.DONATED_DRUGS_WAREHOUSING.getInfo()));
|
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ public class PharmacyWarehouseStockOutOrderServiceImpl implements IPharmacyWareh
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.IN_HOSPITAL_OUTBOUND.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.IN_HOSPITAL_OUTBOUND.getValue(),
|
||||||
// SupplyCategory.IN_HOSPITAL_OUTBOUND.getInfo()));
|
SupplyCategory.IN_HOSPITAL_OUTBOUND.getInfo()));
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.OUT_OF_HOSPITAL_OUTBOUND.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.OUT_OF_HOSPITAL_OUTBOUND.getValue(),
|
||||||
// SupplyCategory.OUT_OF_HOSPITAL_OUTBOUND.getInfo()));
|
SupplyCategory.OUT_OF_HOSPITAL_OUTBOUND.getInfo()));
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.OTHER_OUTBOUND.getValue(),
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(SupplyCategory.OTHER_OUTBOUND.getValue(),
|
||||||
// SupplyCategory.OTHER_OUTBOUND.getInfo()));
|
SupplyCategory.OTHER_OUTBOUND.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ public class PharmacyWarehouseStocktakingOrderServiceImpl implements IPharmacyWa
|
|||||||
|
|
||||||
// 单据分类
|
// 单据分类
|
||||||
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
List<PharmacyWarehouseInitDto.IntegerOption> supplyCategoryOption = new ArrayList<>();
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
||||||
// SupplyCategory.GENERAL_STOCKTAKING.getValue(), SupplyCategory.GENERAL_STOCKTAKING.getInfo()));
|
SupplyCategory.GENERAL_STOCKTAKING.getValue(), SupplyCategory.GENERAL_STOCKTAKING.getInfo()));
|
||||||
// supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
supplyCategoryOption.add(new PharmacyWarehouseInitDto.IntegerOption(
|
||||||
// SupplyCategory.MONTHLY_STOCKTAKING.getValue(), SupplyCategory.MONTHLY_STOCKTAKING.getInfo()));
|
SupplyCategory.MONTHLY_STOCKTAKING.getValue(), SupplyCategory.MONTHLY_STOCKTAKING.getInfo()));
|
||||||
|
|
||||||
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
purchaseOrderInitDto.setSupplyCategoryOptions(supplyCategoryOption);
|
||||||
|
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
||||||
longMedicationRequest.setPrescriptionNo(regAdviceSaveDto.getPrescriptionNo()); // 处方号
|
longMedicationRequest.setPrescriptionNo(regAdviceSaveDto.getPrescriptionNo()); // 处方号
|
||||||
longMedicationRequest.setGroupId(regAdviceSaveDto.getGroupId()); // 组号
|
longMedicationRequest.setGroupId(regAdviceSaveDto.getGroupId()); // 组号
|
||||||
|
longMedicationRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
longMedicationRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
longMedicationRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
longMedicationRequest.setSignCode(signCode);
|
longMedicationRequest.setSignCode(signCode);
|
||||||
@@ -340,6 +341,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue()); // 请求状态
|
||||||
tempMedicationRequest.setPrescriptionNo(regAdviceSaveDto.getPrescriptionNo()); // 处方号
|
tempMedicationRequest.setPrescriptionNo(regAdviceSaveDto.getPrescriptionNo()); // 处方号
|
||||||
tempMedicationRequest.setGroupId(regAdviceSaveDto.getGroupId()); // 组号
|
tempMedicationRequest.setGroupId(regAdviceSaveDto.getGroupId()); // 组号
|
||||||
|
tempMedicationRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
tempMedicationRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
tempMedicationRequest.setReqAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
tempMedicationRequest.setSignCode(signCode);
|
tempMedicationRequest.setSignCode(signCode);
|
||||||
@@ -458,6 +460,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
longServiceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
|
longServiceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
|
||||||
longServiceRequest
|
longServiceRequest
|
||||||
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
||||||
|
longServiceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
longServiceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
longServiceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
longServiceRequest.setSignCode(signCode);
|
longServiceRequest.setSignCode(signCode);
|
||||||
@@ -505,6 +508,7 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
|
|||||||
tempServiceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
|
tempServiceRequest.setId(regAdviceSaveDto.getRequestId()); // 主键id
|
||||||
tempServiceRequest
|
tempServiceRequest
|
||||||
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
.setStatusEnum(is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue());// 请求状态
|
||||||
|
tempServiceRequest.setTenantId(SecurityUtils.getLoginUser().getTenantId()); // 显式设置租户ID
|
||||||
if (is_sign) {
|
if (is_sign) {
|
||||||
tempServiceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
tempServiceRequest.setAuthoredTime(authoredTime); // 医嘱签发时间
|
||||||
tempServiceRequest.setSignCode(signCode);
|
tempServiceRequest.setSignCode(signCode);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.openhis.web.system.controller;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.openhis.web.doctorstation.appservice.IDoctorStationEmrAppService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统首页控制器
|
||||||
|
*/
|
||||||
|
@Api(tags = "系统首页")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/home")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class HomeController {
|
||||||
|
|
||||||
|
private final IDoctorStationEmrAppService doctorStationEmrAppService;
|
||||||
|
|
||||||
|
@ApiOperation("获取首页统计数据")
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public R<?> getStatistics() {
|
||||||
|
// 这里可以返回各种统计数据
|
||||||
|
// 为了简化,我们只返回待写病历数量
|
||||||
|
Long userId = SecurityUtils.getLoginUser().getUserId();
|
||||||
|
R<?> pendingEmrCount = doctorStationEmrAppService.getPendingEmrCount(userId);
|
||||||
|
|
||||||
|
// 构建返回数据
|
||||||
|
java.util.Map<String, Object> data = new java.util.HashMap<>();
|
||||||
|
data.put("pendingEmr", pendingEmrCount.getData());
|
||||||
|
|
||||||
|
return R.ok(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.appservice;
|
||||||
|
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public interface TriageQueueAppService {
|
||||||
|
R<?> list(Long organizationId, LocalDate date);
|
||||||
|
R<?> add(TriageQueueAddReq req);
|
||||||
|
R<?> remove(Long id);
|
||||||
|
R<?> adjust(TriageQueueAdjustReq req);
|
||||||
|
/** 选呼:将之前叫号中置为完成,选中的置为叫号中 */
|
||||||
|
R<?> call(TriageQueueActionReq req);
|
||||||
|
/** 完成:叫号中 -> 完成(移出列表),并自动推进下一个等待为叫号中 */
|
||||||
|
R<?> complete(TriageQueueActionReq req);
|
||||||
|
/** 过号重排:叫号中 -> 跳过并移到末尾,并自动推进下一个等待为叫号中 */
|
||||||
|
R<?> requeue(TriageQueueActionReq req);
|
||||||
|
/** 跳过:兼容前端按钮(当前实现等同于过号重排) */
|
||||||
|
R<?> skip(TriageQueueActionReq req);
|
||||||
|
/** 下一患者:当前叫号中 -> 完成,下一位等待 -> 叫号中 */
|
||||||
|
R<?> next(TriageQueueActionReq req);
|
||||||
|
|
||||||
|
/** 叫号显示屏:获取当前叫号和等候队列信息 */
|
||||||
|
CallNumberDisplayResp getDisplayData(Long organizationId, LocalDate date, Integer tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,747 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.appservice.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
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.utils.SecurityUtils;
|
||||||
|
import com.openhis.triageandqueuemanage.domain.TriageQueueItem;
|
||||||
|
import com.openhis.triageandqueuemanage.domain.TriageCandidateExclusion;
|
||||||
|
import com.openhis.triageandqueuemanage.service.TriageQueueItemService;
|
||||||
|
import com.openhis.triageandqueuemanage.service.TriageCandidateExclusionService;
|
||||||
|
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueEncounterItem;
|
||||||
|
import com.openhis.web.triageandqueuemanage.sse.CallNumberSseManager;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TriageQueueAppServiceImpl implements TriageQueueAppService {
|
||||||
|
|
||||||
|
private static final String STATUS_WAITING = "WAITING";
|
||||||
|
private static final String STATUS_CALLING = "CALLING";
|
||||||
|
private static final String STATUS_SKIPPED = "SKIPPED";
|
||||||
|
private static final String STATUS_COMPLETED = "COMPLETED";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TriageQueueItemService triageQueueItemService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CallNumberSseManager callNumberSseManager;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TriageCandidateExclusionService triageCandidateExclusionService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R<?> list(Long organizationId, LocalDate date) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
// 只查询今天的患者
|
||||||
|
LocalDate qd = date != null ? date : LocalDate.now();
|
||||||
|
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> wrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getQueueDate, qd)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED)
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder);
|
||||||
|
|
||||||
|
// 如果指定了科室,按科室过滤;否则查询所有科室(全科模式)
|
||||||
|
if (organizationId != null) {
|
||||||
|
wrapper.eq(TriageQueueItem::getOrganizationId, organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TriageQueueItem> list = triageQueueItemService.list(wrapper);
|
||||||
|
|
||||||
|
// 双重保险:再次过滤掉 COMPLETED 状态的患者(防止数据库中有异常数据)
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
int beforeSize = list.size();
|
||||||
|
list = list.stream()
|
||||||
|
.filter(item -> !STATUS_COMPLETED.equals(item.getStatus()))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
if (beforeSize != list.size()) {
|
||||||
|
System.out.println(">>> [TriageQueue] list() 警告:过滤掉了 " + (beforeSize - list.size()) + " 条 COMPLETED 状态的记录");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试日志:检查状态值
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
System.out.println(">>> [TriageQueue] list() 返回 " + list.size() + " 条记录(已排除 COMPLETED)");
|
||||||
|
for (int i = 0; i < Math.min(3, list.size()); i++) {
|
||||||
|
TriageQueueItem item = list.get(i);
|
||||||
|
System.out.println(" [" + i + "] patientName=" + item.getPatientName()
|
||||||
|
+ ", status=" + item.getStatus()
|
||||||
|
+ ", organizationId=" + item.getOrganizationId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return R.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> add(TriageQueueAddReq req) {
|
||||||
|
if (req == null || ObjectUtil.isNull(req.getOrganizationId())) {
|
||||||
|
return R.fail("organizationId 不能为空");
|
||||||
|
}
|
||||||
|
if (CollUtil.isEmpty(req.getItems())) {
|
||||||
|
return R.fail("items 不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
LocalDate qd = LocalDate.now();
|
||||||
|
Long orgId = req.getOrganizationId();
|
||||||
|
|
||||||
|
List<TriageQueueItem> existing = triageQueueItemService.list(new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getOrganizationId, orgId)
|
||||||
|
.eq(TriageQueueItem::getQueueDate, qd)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED));
|
||||||
|
|
||||||
|
int maxOrder = existing.stream().map(TriageQueueItem::getQueueOrder).filter(Objects::nonNull).max(Integer::compareTo).orElse(0);
|
||||||
|
|
||||||
|
int added = 0;
|
||||||
|
for (TriageQueueEncounterItem it : req.getItems()) {
|
||||||
|
if (it == null || it.getEncounterId() == null) continue;
|
||||||
|
boolean exists = existing.stream().anyMatch(e -> Objects.equals(e.getEncounterId(), it.getEncounterId()));
|
||||||
|
if (exists) continue;
|
||||||
|
|
||||||
|
TriageQueueItem qi = new TriageQueueItem()
|
||||||
|
.setTenantId(tenantId)
|
||||||
|
.setQueueDate(qd)
|
||||||
|
.setOrganizationId(orgId)
|
||||||
|
.setOrganizationName(req.getOrganizationName())
|
||||||
|
.setEncounterId(it.getEncounterId())
|
||||||
|
.setPatientId(it.getPatientId())
|
||||||
|
.setPatientName(it.getPatientName())
|
||||||
|
.setHealthcareName(it.getHealthcareName())
|
||||||
|
.setPractitionerName(it.getPractitionerName())
|
||||||
|
.setPractitionerId(it.getPractitionerId()) // ✅ 新增字段(可选)
|
||||||
|
.setRoomNo(it.getRoomNo()) // ✅ 新增字段(可选)
|
||||||
|
.setStatus(STATUS_WAITING)
|
||||||
|
.setQueueOrder(++maxOrder)
|
||||||
|
.setDeleteFlag("0")
|
||||||
|
.setCreateTime(LocalDateTime.now())
|
||||||
|
.setUpdateTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
triageQueueItemService.save(qi);
|
||||||
|
|
||||||
|
// 记录到候选池排除列表(避免刷新后重新出现在候选池)
|
||||||
|
TriageCandidateExclusion exclusion = triageCandidateExclusionService.getOne(
|
||||||
|
new LambdaQueryWrapper<TriageCandidateExclusion>()
|
||||||
|
.eq(TriageCandidateExclusion::getTenantId, tenantId)
|
||||||
|
.eq(TriageCandidateExclusion::getExclusionDate, qd)
|
||||||
|
.eq(TriageCandidateExclusion::getEncounterId, it.getEncounterId())
|
||||||
|
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
|
||||||
|
);
|
||||||
|
if (exclusion == null) {
|
||||||
|
exclusion = new TriageCandidateExclusion()
|
||||||
|
.setTenantId(tenantId)
|
||||||
|
.setExclusionDate(qd)
|
||||||
|
.setEncounterId(it.getEncounterId())
|
||||||
|
.setPatientId(it.getPatientId())
|
||||||
|
.setPatientName(it.getPatientName())
|
||||||
|
.setOrganizationId(orgId)
|
||||||
|
.setOrganizationName(req.getOrganizationName())
|
||||||
|
.setReason("ADDED_TO_QUEUE")
|
||||||
|
.setDeleteFlag("0")
|
||||||
|
.setCreateTime(LocalDateTime.now())
|
||||||
|
.setUpdateTime(LocalDateTime.now());
|
||||||
|
triageCandidateExclusionService.save(exclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return R.ok(added);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> remove(Long id) {
|
||||||
|
if (id == null) return R.fail("id 不能为空");
|
||||||
|
TriageQueueItem item = triageQueueItemService.getById(id);
|
||||||
|
if (item == null) return R.fail("队列项不存在");
|
||||||
|
|
||||||
|
// 逻辑删除队列项
|
||||||
|
item.setDeleteFlag("1").setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(item);
|
||||||
|
|
||||||
|
// 从排除列表中删除记录,使患者重新出现在候选池中
|
||||||
|
Integer tenantId = item.getTenantId();
|
||||||
|
LocalDate exclusionDate = item.getQueueDate();
|
||||||
|
Long encounterId = item.getEncounterId();
|
||||||
|
|
||||||
|
if (tenantId != null && exclusionDate != null && encounterId != null) {
|
||||||
|
TriageCandidateExclusion exclusion = triageCandidateExclusionService.getOne(
|
||||||
|
new LambdaQueryWrapper<TriageCandidateExclusion>()
|
||||||
|
.eq(TriageCandidateExclusion::getTenantId, tenantId)
|
||||||
|
.eq(TriageCandidateExclusion::getExclusionDate, exclusionDate)
|
||||||
|
.eq(TriageCandidateExclusion::getEncounterId, encounterId)
|
||||||
|
.eq(TriageCandidateExclusion::getDeleteFlag, "0")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (exclusion != null) {
|
||||||
|
// 逻辑删除排除记录
|
||||||
|
exclusion.setDeleteFlag("1").setUpdateTime(LocalDateTime.now());
|
||||||
|
triageCandidateExclusionService.updateById(exclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recalcOrders(item.getOrganizationId(), item.getQueueDate());
|
||||||
|
return R.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> adjust(TriageQueueAdjustReq req) {
|
||||||
|
if (req == null || req.getId() == null) return R.fail("id 不能为空");
|
||||||
|
if (!"up".equalsIgnoreCase(req.getDirection()) && !"down".equalsIgnoreCase(req.getDirection())) {
|
||||||
|
return R.fail("direction 只能是 up/down");
|
||||||
|
}
|
||||||
|
TriageQueueItem cur = triageQueueItemService.getById(req.getId());
|
||||||
|
if (cur == null) return R.fail("队列项不存在");
|
||||||
|
|
||||||
|
List<TriageQueueItem> list = listInternal(cur.getOrganizationId(), cur.getQueueDate());
|
||||||
|
list.sort(Comparator.comparing(TriageQueueItem::getQueueOrder).thenComparing(TriageQueueItem::getId));
|
||||||
|
int idx = -1;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (Objects.equals(list.get(i).getId(), cur.getId())) { idx = i; break; }
|
||||||
|
}
|
||||||
|
if (idx == -1) return R.fail("队列项不在当前队列");
|
||||||
|
|
||||||
|
int targetIdx = "up".equalsIgnoreCase(req.getDirection()) ? idx - 1 : idx + 1;
|
||||||
|
if (targetIdx < 0 || targetIdx >= list.size()) return R.ok(false);
|
||||||
|
|
||||||
|
TriageQueueItem other = list.get(targetIdx);
|
||||||
|
Integer tmp = cur.getQueueOrder();
|
||||||
|
cur.setQueueOrder(other.getQueueOrder()).setUpdateTime(LocalDateTime.now());
|
||||||
|
other.setQueueOrder(tmp).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(cur);
|
||||||
|
triageQueueItemService.updateById(other);
|
||||||
|
|
||||||
|
recalcOrders(cur.getOrganizationId(), cur.getQueueDate());
|
||||||
|
return R.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> call(TriageQueueActionReq req) {
|
||||||
|
if (req == null || req.getId() == null) return R.fail("id 不能为空");
|
||||||
|
TriageQueueItem selected = triageQueueItemService.getById(req.getId());
|
||||||
|
if (selected == null) return R.fail("队列项不存在");
|
||||||
|
|
||||||
|
// 只将"等待"状态的患者转为"叫号中",允许有多个"叫号中"的患者
|
||||||
|
if (STATUS_WAITING.equals(selected.getStatus())) {
|
||||||
|
selected.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(selected);
|
||||||
|
|
||||||
|
// 叫号后推送 SSE 消息(实时通知显示屏刷新)
|
||||||
|
pushDisplayUpdate(selected.getOrganizationId(), selected.getQueueDate(), selected.getTenantId());
|
||||||
|
|
||||||
|
return R.ok(true);
|
||||||
|
} else if (STATUS_CALLING.equals(selected.getStatus())) {
|
||||||
|
// 如果已经是"叫号中"状态,直接返回成功(不做任何操作)
|
||||||
|
return R.ok(true);
|
||||||
|
} else {
|
||||||
|
// 其他状态(如 SKIPPED、COMPLETED)不能选呼
|
||||||
|
return R.fail("只能选呼\"等待\"状态的患者,当前患者状态为:" + selected.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> complete(TriageQueueActionReq req) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
TriageQueueItem calling = null;
|
||||||
|
|
||||||
|
// 关键改进:如果提供了 id,直接通过ID获取(像 call 方法一样),不依赖查询条件
|
||||||
|
if (req != null && req.getId() != null) {
|
||||||
|
calling = triageQueueItemService.getById(req.getId());
|
||||||
|
if (calling == null) {
|
||||||
|
return R.fail("队列项不存在");
|
||||||
|
}
|
||||||
|
// 验证状态
|
||||||
|
if (!STATUS_CALLING.equals(calling.getStatus())) {
|
||||||
|
return R.fail("只能完成\"叫号中\"状态的患者,当前患者状态为:" + calling.getStatus());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有提供 id,通过查询条件查找(兼容旧逻辑)
|
||||||
|
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
|
||||||
|
System.out.println(">>> [TriageQueue] complete() 开始执行(不限制日期,通过查询条件), tenantId=" + tenantId + ", orgId=" + orgId);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
if (orgId != null) {
|
||||||
|
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
calling = triageQueueItemService.getOne(callingWrapper, false);
|
||||||
|
|
||||||
|
System.out.println(">>> [TriageQueue] complete() 查询叫号中患者(不限制日期): orgId=" + orgId + ", 结果=" + (calling != null ? calling.getPatientName() + "(status=" + calling.getStatus() + ", queueDate=" + calling.getQueueDate() + ")" : "null"));
|
||||||
|
|
||||||
|
if (calling == null) {
|
||||||
|
return R.fail("当前没有叫号中的患者");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用实际找到的科室ID
|
||||||
|
Long actualOrgId = calling.getOrganizationId();
|
||||||
|
|
||||||
|
// 1) 叫号中 -> 完成(移出列表)
|
||||||
|
calling.setStatus(STATUS_COMPLETED).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(calling);
|
||||||
|
|
||||||
|
// 2) 自动推进下一个等待为叫号中(同一科室,包含跳过状态,不限制日期)
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
|
||||||
|
.or()
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_SKIPPED))
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
// 如果指定了科室ID,则按科室过滤;否则查询所有科室(全科模式)
|
||||||
|
if (actualOrgId != null) {
|
||||||
|
nextWrapper.eq(TriageQueueItem::getOrganizationId, actualOrgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||||
|
|
||||||
|
System.out.println(">>> [TriageQueue] complete() 查询等待患者(不限制日期): actualOrgId=" + actualOrgId + ", 结果=" + (next != null ? next.getPatientName() + "(status=" + next.getStatus() + ")" : "null"));
|
||||||
|
|
||||||
|
if (next != null) {
|
||||||
|
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
recalcOrders(actualOrgId, null);
|
||||||
|
|
||||||
|
// 完成后推送 SSE 消息(实时通知显示屏刷新)
|
||||||
|
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||||
|
|
||||||
|
return R.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> requeue(TriageQueueActionReq req) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
TriageQueueItem calling = null;
|
||||||
|
|
||||||
|
|
||||||
|
if (req != null && req.getId() != null) {
|
||||||
|
calling = triageQueueItemService.getById(req.getId());
|
||||||
|
if (calling == null) {
|
||||||
|
return R.fail("队列项不存在");
|
||||||
|
}
|
||||||
|
// 验证状态
|
||||||
|
if (!STATUS_CALLING.equals(calling.getStatus())) {
|
||||||
|
return R.fail("只能对\"叫号中\"状态的患者进行过号重排,当前患者状态为:" + calling.getStatus());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有提供 id,通过查询条件查找(兼容旧逻辑)
|
||||||
|
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
|
||||||
|
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
if (orgId != null) {
|
||||||
|
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
calling = triageQueueItemService.getOne(callingWrapper, false);
|
||||||
|
|
||||||
|
if (calling == null) return R.fail("当前没有叫号中的患者");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用实际找到的科室ID
|
||||||
|
Long actualOrgId = calling.getOrganizationId();
|
||||||
|
|
||||||
|
// 关键改进:在执行"跳过"操作之前,先检查是否有等待中的患者(判断队列状态)
|
||||||
|
// 如果没有等待中的患者,就不应该执行"过号重排"操作
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
|
||||||
|
.or()
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_SKIPPED))
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
// 如果指定了科室ID,则按科室过滤;否则查询所有科室(全科模式)
|
||||||
|
if (actualOrgId != null) {
|
||||||
|
nextWrapper.eq(TriageQueueItem::getOrganizationId, actualOrgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||||
|
|
||||||
|
// 调试日志:检查查询结果
|
||||||
|
System.out.println(">>> [TriageQueue] requeue() 查询等待中的患者:");
|
||||||
|
System.out.println(">>> - 科室ID: " + actualOrgId);
|
||||||
|
System.out.println(">>> - 找到的等待患者: " + (next != null ? next.getPatientName() + " (状态: " + next.getStatus() + ")" : "null"));
|
||||||
|
|
||||||
|
// 如果找不到等待中的患者,直接返回失败(不执行跳过操作)
|
||||||
|
if (next == null) {
|
||||||
|
System.out.println(">>> [TriageQueue] requeue() 失败:没有等待中的患者");
|
||||||
|
return R.fail("当前没有等待中的患者");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找末尾序号(同一科室,不限制日期)
|
||||||
|
Integer maxOrder = triageQueueItemService.list(new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getOrganizationId, actualOrgId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED))
|
||||||
|
.stream()
|
||||||
|
.map(TriageQueueItem::getQueueOrder)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.max(Integer::compareTo)
|
||||||
|
.orElse(0);
|
||||||
|
|
||||||
|
// 1) 叫号中 -> 跳过,并移到末尾
|
||||||
|
calling.setStatus(STATUS_SKIPPED).setQueueOrder(maxOrder + 1).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(calling);
|
||||||
|
|
||||||
|
// 2) 自动推进下一个等待为叫号中
|
||||||
|
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(next);
|
||||||
|
|
||||||
|
recalcOrders(actualOrgId, null);
|
||||||
|
|
||||||
|
// ✅ 过号重排后推送 SSE 消息(实时通知显示屏刷新)
|
||||||
|
pushDisplayUpdate(actualOrgId, calling.getQueueDate(), tenantId);
|
||||||
|
|
||||||
|
return R.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> skip(TriageQueueActionReq req) {
|
||||||
|
// 当前业务“跳过”按“过号重排”处理:叫号中 -> 跳过并移到末尾,自动推进下一等待
|
||||||
|
return requeue(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public R<?> next(TriageQueueActionReq req) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
TriageQueueItem calling = null;
|
||||||
|
|
||||||
|
System.out.println(">>> [TriageQueue] next() 开始执行(不限制日期), tenantId=" + tenantId);
|
||||||
|
|
||||||
|
// 关键改进:如果提供了 id,优先使用 id 直接查找(像 call 方法一样)
|
||||||
|
if (req != null && req.getId() != null) {
|
||||||
|
calling = triageQueueItemService.getById(req.getId());
|
||||||
|
if (calling == null) {
|
||||||
|
return R.fail("队列项不存在");
|
||||||
|
}
|
||||||
|
// 验证状态:必须是"叫号中"状态
|
||||||
|
if (!STATUS_CALLING.equals(calling.getStatus())) {
|
||||||
|
return R.fail("只能对\"叫号中\"状态的患者执行\"下一患者\"操作,当前患者状态为:" + calling.getStatus());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有提供 id,通过查询条件查找(兼容旧逻辑)
|
||||||
|
Long orgId = req != null && req.getOrganizationId() != null ? req.getOrganizationId() : null;
|
||||||
|
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> callingWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
if (orgId != null) {
|
||||||
|
callingWrapper.eq(TriageQueueItem::getOrganizationId, orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
calling = triageQueueItemService.getOne(callingWrapper, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long actualOrgId = null;
|
||||||
|
|
||||||
|
// 当前叫号中 -> 完成(如果不存在,就当作从头找第一位等待)
|
||||||
|
if (calling != null) {
|
||||||
|
calling.setStatus(STATUS_COMPLETED).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(calling);
|
||||||
|
actualOrgId = calling.getOrganizationId(); // 使用叫号中患者所在的科室
|
||||||
|
} else {
|
||||||
|
// 如果没有叫号中的患者,使用请求中的 organizationId(如果有)
|
||||||
|
if (req != null && req.getOrganizationId() != null) {
|
||||||
|
actualOrgId = req.getOrganizationId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一位等待 -> 叫号中(如果之前有叫号中的,就在同一科室找;否则在全科找)
|
||||||
|
// 注意:也包含"跳过"状态的患者,因为跳过后的患者也可以重新叫号(不限制日期)
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> nextWrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.and(w -> w.eq(TriageQueueItem::getStatus, STATUS_WAITING)
|
||||||
|
.or()
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_SKIPPED))
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
|
||||||
|
if (actualOrgId != null) {
|
||||||
|
nextWrapper.eq(TriageQueueItem::getOrganizationId, actualOrgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriageQueueItem next = triageQueueItemService.getOne(nextWrapper, false);
|
||||||
|
|
||||||
|
// 调试日志:打印查询条件和结果
|
||||||
|
System.out.println(">>> [TriageQueue] next() 查询条件(不限制日期): tenantId=" + tenantId
|
||||||
|
+ ", actualOrgId=" + actualOrgId
|
||||||
|
+ ", deleteFlag=0"
|
||||||
|
+ ", status IN (WAITING, SKIPPED)");
|
||||||
|
System.out.println(">>> [TriageQueue] next() 查询结果: calling=" + (calling != null ? calling.getPatientName() + "(status=" + calling.getStatus() + ", queueDate=" + calling.getQueueDate() + ")" : "null")
|
||||||
|
+ ", next=" + (next != null ? next.getPatientName() + "(status=" + next.getStatus() + ", queueDate=" + next.getQueueDate() + ")" : "null"));
|
||||||
|
|
||||||
|
if (next == null) {
|
||||||
|
if (actualOrgId != null) {
|
||||||
|
recalcOrders(actualOrgId, null);
|
||||||
|
}
|
||||||
|
return R.fail("当前没有等待的患者");
|
||||||
|
}
|
||||||
|
|
||||||
|
next.setStatus(STATUS_CALLING).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(next);
|
||||||
|
|
||||||
|
if (next.getOrganizationId() != null) {
|
||||||
|
recalcOrders(next.getOrganizationId(), null);
|
||||||
|
}
|
||||||
|
return R.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TriageQueueItem> listInternal(Long orgId, LocalDate qd) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
LambdaQueryWrapper<TriageQueueItem> wrapper = new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getOrganizationId, orgId)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.ne(TriageQueueItem::getStatus, STATUS_COMPLETED);
|
||||||
|
// 如果 qd 不为 null,才添加日期限制
|
||||||
|
if (qd != null) {
|
||||||
|
wrapper.eq(TriageQueueItem::getQueueDate, qd);
|
||||||
|
}
|
||||||
|
return triageQueueItemService.list(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TriageQueueItem findCalling(Long orgId, LocalDate qd) {
|
||||||
|
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
return triageQueueItemService.getOne(new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.eq(TriageQueueItem::getOrganizationId, orgId)
|
||||||
|
.eq(TriageQueueItem::getQueueDate, qd)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.eq(TriageQueueItem::getStatus, STATUS_CALLING)
|
||||||
|
.last("LIMIT 1"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalcOrders(Long orgId, LocalDate qd) {
|
||||||
|
List<TriageQueueItem> list = listInternal(orgId, qd);
|
||||||
|
list.sort(Comparator.comparing(TriageQueueItem::getQueueOrder).thenComparing(TriageQueueItem::getId));
|
||||||
|
int i = 1;
|
||||||
|
for (TriageQueueItem it : list) {
|
||||||
|
if (!Objects.equals(it.getQueueOrder(), i)) {
|
||||||
|
it.setQueueOrder(i).setUpdateTime(LocalDateTime.now());
|
||||||
|
triageQueueItemService.updateById(it);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取叫号显示屏数据
|
||||||
|
* @param organizationId 科室ID
|
||||||
|
* @param date 日期
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
* @return 显示屏数据
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CallNumberDisplayResp getDisplayData(Long organizationId, LocalDate date, Integer tenantId) {
|
||||||
|
// 如果没有传入租户ID,尝试从登录用户获取,否则默认为1
|
||||||
|
if (tenantId == null) {
|
||||||
|
try {
|
||||||
|
tenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
} catch (Exception e) {
|
||||||
|
tenantId = 1; // 默认租户ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LocalDate qd = date != null ? date : LocalDate.now();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有队列项(WAITING 和 CALLING 状态)某天的某个科室的某个状态
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
List<TriageQueueItem> allItems = triageQueueItemService.list(
|
||||||
|
new LambdaQueryWrapper<TriageQueueItem>()
|
||||||
|
.eq(TriageQueueItem::getQueueDate, qd)
|
||||||
|
.eq(TriageQueueItem::getOrganizationId, organizationId)
|
||||||
|
.eq(TriageQueueItem::getTenantId, tenantId)
|
||||||
|
.in(TriageQueueItem::getStatus, STATUS_WAITING, STATUS_CALLING)
|
||||||
|
.eq(TriageQueueItem::getDeleteFlag, "0")
|
||||||
|
.orderByAsc(TriageQueueItem::getQueueOrder)
|
||||||
|
);
|
||||||
|
|
||||||
|
CallNumberDisplayResp resp = new CallNumberDisplayResp();
|
||||||
|
|
||||||
|
// 1. 获取科室名称(从第一条数据中取)
|
||||||
|
if (!allItems.isEmpty()) {
|
||||||
|
resp.setDepartmentName(allItems.get(0).getOrganizationName() + " 叫号显示屏");
|
||||||
|
} else {
|
||||||
|
resp.setDepartmentName("叫号显示屏");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 查找当前叫号中的患者(CALLING 状态)
|
||||||
|
TriageQueueItem callingItem = allItems.stream()
|
||||||
|
.filter(item -> STATUS_CALLING.equals(item.getStatus()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (callingItem != null) {
|
||||||
|
CallNumberDisplayResp.CurrentCallInfo currentCall = new CallNumberDisplayResp.CurrentCallInfo();
|
||||||
|
currentCall.setNumber(callingItem.getQueueOrder());
|
||||||
|
currentCall.setName(maskPatientName(callingItem.getPatientName()));
|
||||||
|
currentCall.setRoom(callingItem.getRoomNo() != null ? callingItem.getRoomNo() : "1号");
|
||||||
|
currentCall.setDoctor(callingItem.getPractitionerName());
|
||||||
|
resp.setCurrentCall(currentCall);
|
||||||
|
} else {
|
||||||
|
// 没有叫号中的患者,返回默认值
|
||||||
|
CallNumberDisplayResp.CurrentCallInfo currentCall = new CallNumberDisplayResp.CurrentCallInfo();
|
||||||
|
currentCall.setNumber(null);
|
||||||
|
currentCall.setName("-");
|
||||||
|
currentCall.setRoom("-");
|
||||||
|
currentCall.setDoctor("-");
|
||||||
|
resp.setCurrentCall(currentCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 按医生分组(包括 CALLING 和 WAITING 状态)
|
||||||
|
Map<Long, List<TriageQueueItem>> groupedByDoctor = allItems.stream()
|
||||||
|
// 严格按医生分组:仅保留有 practitionerId 的记录
|
||||||
|
.filter(item -> item.getPractitionerId() != null)
|
||||||
|
.collect(Collectors.groupingBy(TriageQueueItem::getPractitionerId));
|
||||||
|
|
||||||
|
// 每个医生的等待队列
|
||||||
|
List<CallNumberDisplayResp.DoctorGroup> waitingList = new ArrayList<>();
|
||||||
|
int totalWaiting = 0;
|
||||||
|
|
||||||
|
for (Map.Entry<Long, List<TriageQueueItem>> entry : groupedByDoctor.entrySet()) {
|
||||||
|
|
||||||
|
List<TriageQueueItem> doctorItems = entry.getValue();
|
||||||
|
String doctorName = doctorItems.get(0).getPractitionerName();
|
||||||
|
if (doctorName == null || doctorName.isEmpty()) {
|
||||||
|
doctorName = "未分配";
|
||||||
|
}
|
||||||
|
// 按排队顺序排序
|
||||||
|
doctorItems.sort(Comparator.comparing(TriageQueueItem::getQueueOrder));
|
||||||
|
|
||||||
|
// 该医生 下边的患者列表 和 诊室号
|
||||||
|
CallNumberDisplayResp.DoctorGroup doctorGroup = new CallNumberDisplayResp.DoctorGroup();
|
||||||
|
doctorGroup.setDoctorName(doctorName);
|
||||||
|
|
||||||
|
// 获取诊室号(从该医生的任一患者中取)
|
||||||
|
String roomNo = doctorItems.stream()
|
||||||
|
.map(TriageQueueItem::getRoomNo)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.findFirst()
|
||||||
|
.orElse("1号");
|
||||||
|
doctorGroup.setRoomNo(roomNo);
|
||||||
|
|
||||||
|
// 转换患者列表
|
||||||
|
List<CallNumberDisplayResp.PatientInfo> patients = new ArrayList<>();
|
||||||
|
for (TriageQueueItem item : doctorItems) {
|
||||||
|
CallNumberDisplayResp.PatientInfo patient = new CallNumberDisplayResp.PatientInfo();
|
||||||
|
patient.setId(item.getId());
|
||||||
|
patient.setName(maskPatientName(item.getPatientName()));
|
||||||
|
patient.setStatus(item.getStatus());
|
||||||
|
patient.setQueueOrder(item.getQueueOrder());
|
||||||
|
patients.add(patient);
|
||||||
|
|
||||||
|
// 统计等待人数(不包括 CALLING 状态)
|
||||||
|
if (STATUS_WAITING.equals(item.getStatus())) {
|
||||||
|
totalWaiting++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doctorGroup.setPatients(patients);
|
||||||
|
waitingList.add(doctorGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 按医生名称排序
|
||||||
|
waitingList.sort(Comparator.comparing(CallNumberDisplayResp.DoctorGroup::getDoctorName));
|
||||||
|
|
||||||
|
resp.setWaitingList(waitingList);
|
||||||
|
resp.setWaitingCount(totalWaiting);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者姓名脱敏处理
|
||||||
|
* @param name 原始姓名
|
||||||
|
* @return 脱敏后的姓名(如:张*三)
|
||||||
|
*/
|
||||||
|
private String maskPatientName(String name) {
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
if (name.length() == 1) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
if (name.length() == 2) {
|
||||||
|
return name.charAt(0) + "*";
|
||||||
|
}
|
||||||
|
// 3个字及以上:保留首尾,中间用*代替
|
||||||
|
return name.charAt(0) + "*" + name.charAt(name.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送显示屏更新消息到 SSE
|
||||||
|
* @param organizationId 科室ID
|
||||||
|
* @param queueDate 队列日期
|
||||||
|
* @param tenantId 租户ID
|
||||||
|
*/
|
||||||
|
private void pushDisplayUpdate(Long organizationId, LocalDate queueDate, Integer tenantId) {
|
||||||
|
try {
|
||||||
|
// 获取最新的显示屏数据
|
||||||
|
CallNumberDisplayResp displayData = getDisplayData(organizationId, queueDate, tenantId);
|
||||||
|
|
||||||
|
// 构造推送消息
|
||||||
|
Map<String, Object> message = new HashMap<>();
|
||||||
|
message.put("type", "update");
|
||||||
|
message.put("action", "queue_changed");
|
||||||
|
message.put("data", displayData);
|
||||||
|
message.put("timestamp", System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 推送到该科室的所有 SSE 连接
|
||||||
|
callNumberSseManager.pushToOrganization(organizationId, message);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// SSE 推送失败不应该影响业务逻辑
|
||||||
|
System.err.println("推送显示屏更新失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.controller;
|
||||||
|
|
||||||
|
import com.core.common.annotation.Anonymous;
|
||||||
|
import com.core.common.core.domain.R;
|
||||||
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import com.openhis.web.triageandqueuemanage.appservice.TriageQueueAppService;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.CallNumberDisplayResp;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueActionReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAddReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.dto.TriageQueueAdjustReq;
|
||||||
|
import com.openhis.web.triageandqueuemanage.sse.CallNumberSseManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
@RequestMapping("/triage/queue")
|
||||||
|
public class TriageQueueController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TriageQueueAppService triageQueueAppService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CallNumberSseManager callNumberSseManager;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public R<?> list(@RequestParam(value = "organizationId", required = false) Long organizationId,
|
||||||
|
@RequestParam(value = "date", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
|
||||||
|
return triageQueueAppService.list(organizationId, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/add")
|
||||||
|
public R<?> add(@RequestBody TriageQueueAddReq req) {
|
||||||
|
return triageQueueAppService.add(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/remove/{id}")
|
||||||
|
public R<?> remove(@PathVariable("id") Long id) {
|
||||||
|
return triageQueueAppService.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/adjust")
|
||||||
|
public R<?> adjust(@RequestBody TriageQueueAdjustReq req) {
|
||||||
|
return triageQueueAppService.adjust(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/call")
|
||||||
|
public R<?> call(@RequestBody TriageQueueActionReq req) {
|
||||||
|
return triageQueueAppService.call(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/complete")
|
||||||
|
public R<?> complete(@RequestBody(required = false) TriageQueueActionReq req) {
|
||||||
|
return triageQueueAppService.complete(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/requeue")
|
||||||
|
public R<?> requeue(@RequestBody(required = false) TriageQueueActionReq req) {
|
||||||
|
return triageQueueAppService.requeue(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/skip")
|
||||||
|
public R<?> skip(@RequestBody(required = false) TriageQueueActionReq req) {
|
||||||
|
return triageQueueAppService.skip(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/next")
|
||||||
|
public R<?> next(@RequestBody(required = false) TriageQueueActionReq req) {
|
||||||
|
return triageQueueAppService.next(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 叫号显示屏:获取当前叫号和等候队列信息
|
||||||
|
* @param organizationId 科室ID
|
||||||
|
* @param date 日期(可选,默认今天)
|
||||||
|
* @param tenantId 租户ID(可选,默认1)
|
||||||
|
* @return 显示屏数据
|
||||||
|
*/
|
||||||
|
@Anonymous // 显示屏不需要登录
|
||||||
|
@GetMapping("/display")
|
||||||
|
public R<CallNumberDisplayResp> getDisplayData(
|
||||||
|
@RequestParam(required = false) String organizationId,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
|
||||||
|
@RequestParam(required = false) Integer tenantId
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
Long orgId = resolveOrganizationId(organizationId);
|
||||||
|
if (orgId == null) {
|
||||||
|
return R.fail("organizationId参数不合法或未获取到登录用户科室");
|
||||||
|
}
|
||||||
|
Integer actualTenantId = resolveTenantId(tenantId);
|
||||||
|
CallNumberDisplayResp data = triageQueueAppService.getDisplayData(orgId, date, actualTenantId);
|
||||||
|
return R.ok(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取显示屏数据失败", e);
|
||||||
|
return R.fail("获取显示屏数据失败:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 叫号显示屏:SSE 实时推送
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@GetMapping(value = "/display/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public SseEmitter streamDisplayData(
|
||||||
|
@RequestParam(required = false) String organizationId,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date,
|
||||||
|
@RequestParam(required = false) Integer tenantId
|
||||||
|
) {
|
||||||
|
// 1) 解析科室与租户(SSE 连接根据科室分组管理)
|
||||||
|
Long orgId = resolveOrganizationId(organizationId);
|
||||||
|
if (orgId == null) {
|
||||||
|
SseEmitter emitter = new SseEmitter(0L);
|
||||||
|
Map<String, Object> error = new HashMap<>();
|
||||||
|
error.put("type", "error");
|
||||||
|
error.put("message", "organizationId参数不合法或未获取到登录用户科室");
|
||||||
|
callNumberSseManager.sendToEmitter(emitter, error);
|
||||||
|
emitter.complete();
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
Integer actualTenantId = resolveTenantId(tenantId);
|
||||||
|
// 2) 创建并注册 SSE 连接
|
||||||
|
SseEmitter emitter = callNumberSseManager.addEmitter(orgId);
|
||||||
|
try {
|
||||||
|
// 3) 连接建立后,先推送一次初始化数据
|
||||||
|
CallNumberDisplayResp data = triageQueueAppService.getDisplayData(orgId, date, actualTenantId);
|
||||||
|
Map<String, Object> init = new HashMap<>();
|
||||||
|
init.put("type", "init");
|
||||||
|
init.put("data", data);
|
||||||
|
init.put("timestamp", System.currentTimeMillis());
|
||||||
|
callNumberSseManager.sendToEmitter(emitter, init);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("SSE初始化数据发送失败", e);
|
||||||
|
}
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long resolveOrganizationId(String organizationId) {
|
||||||
|
if (!StringUtils.hasText(organizationId)) {
|
||||||
|
try {
|
||||||
|
return SecurityUtils.getLoginUser().getOrgId();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(organizationId.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("非法organizationId: {}", organizationId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveTenantId(Integer tenantId) {
|
||||||
|
if (tenantId != null) {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Integer loginTenantId = SecurityUtils.getLoginUser().getTenantId();
|
||||||
|
return loginTenantId != null ? loginTenantId : 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 叫号显示屏响应DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CallNumberDisplayResp {
|
||||||
|
/** 科室名称 */
|
||||||
|
private String departmentName;
|
||||||
|
|
||||||
|
/** 当前叫号信息 */
|
||||||
|
private CurrentCallInfo currentCall;
|
||||||
|
|
||||||
|
/** 等候患者列表(按医生分组) */
|
||||||
|
private List<DoctorGroup> waitingList;
|
||||||
|
|
||||||
|
/** 等待总人数 */
|
||||||
|
private Integer waitingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前叫号信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class CurrentCallInfo {
|
||||||
|
/** 排队号 */
|
||||||
|
private Integer number;
|
||||||
|
/** 患者姓名(脱敏) */
|
||||||
|
private String name;
|
||||||
|
/** 诊室号 */
|
||||||
|
private String room;
|
||||||
|
/** 医生姓名 */
|
||||||
|
private String doctor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 医生分组信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class DoctorGroup {
|
||||||
|
/** 医生姓名 */
|
||||||
|
private String doctorName;
|
||||||
|
/** 诊室号 */
|
||||||
|
private String roomNo;
|
||||||
|
/** 该医生的患者列表 */
|
||||||
|
private List<PatientInfo> patients;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 患者信息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class PatientInfo {
|
||||||
|
/** 队列项ID */
|
||||||
|
private Long id;
|
||||||
|
/** 患者姓名(脱敏) */
|
||||||
|
private String name;
|
||||||
|
/** 状态:CALLING=就诊中,WAITING=等待 */
|
||||||
|
private String status;
|
||||||
|
/** 排队号 */
|
||||||
|
private Integer queueOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TriageQueueActionReq {
|
||||||
|
/** 目标队列项ID(例如:选呼时选中的患者) */
|
||||||
|
private Long id;
|
||||||
|
/** 科室ID(可选:不传则用当前登录人orgId) */
|
||||||
|
private Long organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TriageQueueAddReq {
|
||||||
|
/** 科室ID(就诊科室) */
|
||||||
|
private Long organizationId;
|
||||||
|
/** 科室名称(冗余存储,便于展示) */
|
||||||
|
private String organizationName;
|
||||||
|
/** 要加入队列的就诊记录 */
|
||||||
|
private List<TriageQueueEncounterItem> items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TriageQueueAdjustReq {
|
||||||
|
private Long id;
|
||||||
|
/** up / down */
|
||||||
|
private String direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TriageQueueEncounterItem {
|
||||||
|
private Long encounterId;
|
||||||
|
private Long patientId;
|
||||||
|
private String patientName;
|
||||||
|
private String healthcareName;
|
||||||
|
private String practitionerName;
|
||||||
|
|
||||||
|
// ========== 新增字段(可选,用于叫号显示屏)==========
|
||||||
|
/** 医生ID(可选) */
|
||||||
|
private Long practitionerId;
|
||||||
|
/** 诊室号(可选) */
|
||||||
|
private String roomNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.openhis.web.triageandqueuemanage.sse;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 叫号显示屏 SSE 管理器(服务端推送)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class CallNumberSseManager {
|
||||||
|
|
||||||
|
private static final long NO_TIMEOUT = 0L; // 0 表示“永不超时”
|
||||||
|
// 按科室分组保存连接(消化内科有3个屏、心内科有2个屏)
|
||||||
|
// 很多屏幕同时连、同时断。故用 ConcurrentHashMap 存储,线程安全。内部分段锁,不阻塞其他科室的操作。
|
||||||
|
private static final Map<Long, CopyOnWriteArraySet<SseEmitter>> emitterMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建并注册一个 SSE 连接(按科室分组保存)
|
||||||
|
*/
|
||||||
|
public SseEmitter addEmitter(Long organizationId) {
|
||||||
|
SseEmitter emitter = new SseEmitter(NO_TIMEOUT);
|
||||||
|
emitterMap.computeIfAbsent(organizationId, k -> new CopyOnWriteArraySet<>()).add(emitter);
|
||||||
|
|
||||||
|
emitter.onCompletion(() -> removeEmitter(organizationId, emitter));
|
||||||
|
emitter.onTimeout(() -> removeEmitter(organizationId, emitter));
|
||||||
|
emitter.onError((ex) -> removeEmitter(organizationId, emitter));
|
||||||
|
|
||||||
|
log.info("SSE连接建立:科室ID={}, 当前该科室连接数={}",
|
||||||
|
organizationId, emitterMap.get(organizationId).size());
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向指定科室的所有 SSE 连接推送消息
|
||||||
|
*/
|
||||||
|
public void pushToOrganization(Long organizationId, Object message) {
|
||||||
|
CopyOnWriteArraySet<SseEmitter> emitters = emitterMap.get(organizationId);
|
||||||
|
if (emitters == null || emitters.isEmpty()) {
|
||||||
|
log.debug("科室{}没有SSE连接,跳过推送", organizationId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SseEmitter emitter : emitters) {
|
||||||
|
if (!sendToEmitter(emitter, message)) {
|
||||||
|
removeEmitter(organizationId, emitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向单个 SSE 连接发送数据
|
||||||
|
*/
|
||||||
|
public boolean sendToEmitter(SseEmitter emitter, Object data) {
|
||||||
|
try {
|
||||||
|
emitter.send(SseEmitter.event().data(data));
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("SSE推送失败:{}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开或异常时移除 SSE 连接
|
||||||
|
*/
|
||||||
|
private void removeEmitter(Long organizationId, SseEmitter emitter) {
|
||||||
|
CopyOnWriteArraySet<SseEmitter> emitters = emitterMap.get(organizationId);
|
||||||
|
if (emitters != null) {
|
||||||
|
emitters.remove(emitter);
|
||||||
|
if (emitters.isEmpty()) {
|
||||||
|
emitterMap.remove(organizationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,11 +8,11 @@ spring:
|
|||||||
master:
|
master:
|
||||||
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
|
url: jdbc:postgresql://192.168.110.252:15432/postgresql?currentSchema=hisdev&characterEncoding=UTF-8&client_encoding=UTF-8
|
||||||
username: postgresql
|
username: postgresql
|
||||||
password: Jchl1528
|
password: Jchl1528 # 请替换为实际的数据库密码
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
slave:
|
slave:
|
||||||
# 从数据源开关/默认关闭
|
# 从数据源开关/默认关闭
|
||||||
enabled:
|
enabled: false
|
||||||
url:
|
url:
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
@@ -35,10 +35,12 @@ spring:
|
|||||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
maxEvictableIdleTimeMillis: 900000
|
maxEvictableIdleTimeMillis: 900000
|
||||||
# 配置检测连接是否有效
|
# 配置检测连接是否有效
|
||||||
validationQuery: SELECT 1 # FROM DUAL
|
validationQuery: SELECT 1
|
||||||
testWhileIdle: true
|
testWhileIdle: true
|
||||||
testOnBorrow: false
|
testOnBorrow: true # 改为true以确保连接有效
|
||||||
testOnReturn: false
|
testOnReturn: false
|
||||||
|
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
|
||||||
|
filters: stat,wall,slf4j
|
||||||
webStatFilter:
|
webStatFilter:
|
||||||
enabled: true
|
enabled: true
|
||||||
statViewServlet:
|
statViewServlet:
|
||||||
@@ -59,8 +61,6 @@ spring:
|
|||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
|
||||||
|
|
||||||
# redis 配置
|
# redis 配置
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
@@ -83,10 +83,8 @@ spring:
|
|||||||
max-active: 8
|
max-active: 8
|
||||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
max-wait: -1ms
|
max-wait: -1ms
|
||||||
# 文言
|
|
||||||
messages:
|
# 服务器配置
|
||||||
basename: i18n/general_message/messages
|
|
||||||
encoding: utf-8
|
|
||||||
server:
|
server:
|
||||||
# 服务器的HTTP端口,默认为18080
|
# 服务器的HTTP端口,默认为18080
|
||||||
port: 18080
|
port: 18080
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user