96-门诊医生站会诊申请确认界面和97-门诊会诊申请管理界面全部功能。
This commit is contained in:
@@ -1,208 +0,0 @@
|
||||
---
|
||||
name: java-spring-boot
|
||||
description: Build production Spring Boot applications - REST APIs, Security, Data, Actuator
|
||||
sasmp_version: "1.3.0"
|
||||
version: "3.0.0"
|
||||
bonded_agent: 03-java-spring
|
||||
bond_type: PRIMARY_BOND
|
||||
allowed-tools: Read, Write, Bash, Glob, Grep
|
||||
|
||||
# Parameter Validation
|
||||
parameters:
|
||||
spring_version:
|
||||
type: string
|
||||
default: "3.2"
|
||||
description: Spring Boot version
|
||||
module:
|
||||
type: string
|
||||
enum: [web, security, data, actuator, cloud]
|
||||
description: Spring module focus
|
||||
---
|
||||
|
||||
# Java Spring Boot Skill
|
||||
|
||||
Build production-ready Spring Boot applications with modern best practices.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill covers Spring Boot development including REST APIs, security configuration, data access, actuator monitoring, and cloud integration. Follows Spring Boot 3.x patterns with emphasis on production readiness.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use when you need to:
|
||||
- Create REST APIs with Spring MVC/WebFlux
|
||||
- Configure Spring Security (OAuth2, JWT)
|
||||
- Set up database access with Spring Data
|
||||
- Enable monitoring with Actuator
|
||||
- Integrate with Spring Cloud
|
||||
|
||||
## Topics Covered
|
||||
|
||||
### Spring Boot Core
|
||||
- Auto-configuration and starters
|
||||
- Application properties and profiles
|
||||
- Bean lifecycle and configuration
|
||||
- DevTools and hot reload
|
||||
|
||||
### REST API Development
|
||||
- @RestController and @RequestMapping
|
||||
- Request/response handling
|
||||
- Validation with Bean Validation
|
||||
- Exception handling with @ControllerAdvice
|
||||
|
||||
### Spring Security
|
||||
- SecurityFilterChain configuration
|
||||
- OAuth2 and JWT authentication
|
||||
- Method security (@PreAuthorize)
|
||||
- CORS and CSRF configuration
|
||||
|
||||
### Spring Data JPA
|
||||
- Repository pattern
|
||||
- Query methods and @Query
|
||||
- Pagination and sorting
|
||||
- Auditing and transactions
|
||||
|
||||
### Actuator & Monitoring
|
||||
- Health checks and probes
|
||||
- Metrics with Micrometer
|
||||
- Custom endpoints
|
||||
- Prometheus integration
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```java
|
||||
// REST Controller
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@Validated
|
||||
public class UserController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<User> getUser(@PathVariable Long id) {
|
||||
return userService.findById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<User> createUser(@Valid @RequestBody UserRequest request) {
|
||||
User user = userService.create(request);
|
||||
URI location = URI.create("/api/users/" + user.getId());
|
||||
return ResponseEntity.created(location).body(user);
|
||||
}
|
||||
}
|
||||
|
||||
// Security Configuration
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/actuator/health/**").permitAll()
|
||||
.requestMatchers("/api/public/**").permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
// Exception Handler
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ProblemDetail handleNotFound(EntityNotFoundException ex) {
|
||||
return ProblemDetail.forStatusAndDetail(NOT_FOUND, ex.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Templates
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
spring:
|
||||
application:
|
||||
name: ${APP_NAME:my-service}
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:local}
|
||||
jpa:
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc.batch_size: 50
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
|
||||
server:
|
||||
error:
|
||||
include-stacktrace: never
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Layer Architecture
|
||||
```
|
||||
Controller → Service → Repository → Database
|
||||
↓ ↓ ↓
|
||||
DTOs Entities Entities
|
||||
```
|
||||
|
||||
### Validation Patterns
|
||||
```java
|
||||
public record CreateUserRequest(
|
||||
@NotBlank @Size(max = 100) String name,
|
||||
@Email @NotBlank String email,
|
||||
@NotNull @Min(18) Integer age
|
||||
) {}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Problem | Cause | Solution |
|
||||
|---------|-------|----------|
|
||||
| Bean not found | Missing @Component | Add annotation or @Bean |
|
||||
| Circular dependency | Constructor injection | Use @Lazy or refactor |
|
||||
| 401 Unauthorized | Security config | Check permitAll paths |
|
||||
| Slow startup | Heavy auto-config | Exclude unused starters |
|
||||
|
||||
### Debug Properties
|
||||
```properties
|
||||
debug=true
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
spring.jpa.show-sql=true
|
||||
```
|
||||
|
||||
### Debug Checklist
|
||||
```
|
||||
□ Check /actuator/conditions
|
||||
□ Verify active profiles
|
||||
□ Review security filter chain
|
||||
□ Check bean definitions
|
||||
□ Test health endpoints
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Skill("java-spring-boot")
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
- `java-testing` - Spring test patterns
|
||||
- `java-jpa-hibernate` - Data access
|
||||
@@ -1,41 +0,0 @@
|
||||
# java-spring-boot Configuration
|
||||
# Category: general
|
||||
# Generated: 2025-12-30
|
||||
|
||||
skill:
|
||||
name: java-spring-boot
|
||||
version: "1.0.0"
|
||||
category: general
|
||||
|
||||
settings:
|
||||
# Default settings for java-spring-boot
|
||||
enabled: true
|
||||
log_level: info
|
||||
|
||||
# Category-specific defaults
|
||||
validation:
|
||||
strict_mode: false
|
||||
auto_fix: false
|
||||
|
||||
output:
|
||||
format: markdown
|
||||
include_examples: true
|
||||
|
||||
# Environment-specific overrides
|
||||
environments:
|
||||
development:
|
||||
log_level: debug
|
||||
validation:
|
||||
strict_mode: false
|
||||
|
||||
production:
|
||||
log_level: warn
|
||||
validation:
|
||||
strict_mode: true
|
||||
|
||||
# Integration settings
|
||||
integrations:
|
||||
# Enable/disable integrations
|
||||
git: true
|
||||
linter: true
|
||||
formatter: true
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "java-spring-boot Configuration Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"api",
|
||||
"testing",
|
||||
"devops",
|
||||
"security",
|
||||
"database",
|
||||
"frontend",
|
||||
"algorithms",
|
||||
"machine-learning",
|
||||
"cloud",
|
||||
"containers",
|
||||
"general"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"skill"
|
||||
]
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
# Java Spring Boot Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides comprehensive documentation for the **java-spring-boot** skill in the custom-plugin-java plugin.
|
||||
|
||||
## Category: General
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Familiarity with general concepts
|
||||
- Development environment set up
|
||||
- Plugin installed and configured
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Invoke the skill
|
||||
claude "java-spring-boot - [your task description]"
|
||||
|
||||
# Example
|
||||
claude "java-spring-boot - analyze the current implementation"
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **Consistency** - Follow established patterns
|
||||
2. **Clarity** - Write readable, maintainable code
|
||||
3. **Quality** - Validate before deployment
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Always validate input data
|
||||
- Handle edge cases explicitly
|
||||
- Document your decisions
|
||||
- Write tests for critical paths
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Task 1: Basic Implementation
|
||||
|
||||
```python
|
||||
# Example implementation pattern
|
||||
def implement_java_spring_boot(input_data):
|
||||
"""
|
||||
Implement java-spring-boot functionality.
|
||||
|
||||
Args:
|
||||
input_data: Input to process
|
||||
|
||||
Returns:
|
||||
Processed result
|
||||
"""
|
||||
# Validate input
|
||||
if not input_data:
|
||||
raise ValueError("Input required")
|
||||
|
||||
# Process
|
||||
result = process(input_data)
|
||||
|
||||
# Return
|
||||
return result
|
||||
```
|
||||
|
||||
### Task 2: Advanced Usage
|
||||
|
||||
For advanced scenarios, consider:
|
||||
|
||||
- Configuration customization via `assets/config.yaml`
|
||||
- Validation using `scripts/validate.py`
|
||||
- Integration with other skills
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Skill not found | Not installed | Run plugin sync |
|
||||
| Validation fails | Invalid config | Check config.yaml |
|
||||
| Unexpected output | Missing context | Provide more details |
|
||||
|
||||
## Related Resources
|
||||
|
||||
- SKILL.md - Skill specification
|
||||
- config.yaml - Configuration options
|
||||
- validate.py - Validation script
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-12-30*
|
||||
@@ -1,87 +0,0 @@
|
||||
# Java Spring Boot Patterns
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### Pattern 1: Input Validation
|
||||
|
||||
Always validate input before processing:
|
||||
|
||||
```python
|
||||
def validate_input(data):
|
||||
if data is None:
|
||||
raise ValueError("Data cannot be None")
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Data must be a dictionary")
|
||||
return True
|
||||
```
|
||||
|
||||
### Pattern 2: Error Handling
|
||||
|
||||
Use consistent error handling:
|
||||
|
||||
```python
|
||||
try:
|
||||
result = risky_operation()
|
||||
except SpecificError as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
handle_error(e)
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error")
|
||||
raise
|
||||
```
|
||||
|
||||
### Pattern 3: Configuration Loading
|
||||
|
||||
Load and validate configuration:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
def load_config(config_path):
|
||||
with open(config_path) as f:
|
||||
config = yaml.safe_load(f)
|
||||
validate_config(config)
|
||||
return config
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't: Swallow Exceptions
|
||||
|
||||
```python
|
||||
# BAD
|
||||
try:
|
||||
do_something()
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
### ✅ Do: Handle Explicitly
|
||||
|
||||
```python
|
||||
# GOOD
|
||||
try:
|
||||
do_something()
|
||||
except SpecificError as e:
|
||||
logger.warning(f"Expected error: {e}")
|
||||
return default_value
|
||||
```
|
||||
|
||||
## Category-Specific Patterns: General
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
1. Start with the simplest implementation
|
||||
2. Add complexity only when needed
|
||||
3. Test each addition
|
||||
4. Document decisions
|
||||
|
||||
### Common Integration Points
|
||||
|
||||
- Configuration: `assets/config.yaml`
|
||||
- Validation: `scripts/validate.py`
|
||||
- Documentation: `references/GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
*Pattern library for java-spring-boot skill*
|
||||
@@ -1,131 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validation script for java-spring-boot skill.
|
||||
Category: general
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_config(config_path: str) -> dict:
|
||||
"""
|
||||
Validate skill configuration file.
|
||||
|
||||
Args:
|
||||
config_path: Path to config.yaml
|
||||
|
||||
Returns:
|
||||
dict: Validation result with 'valid' and 'errors' keys
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
return {"valid": False, "errors": ["Config file not found"]}
|
||||
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
return {"valid": False, "errors": [f"YAML parse error: {e}"]}
|
||||
|
||||
# Validate required fields
|
||||
if 'skill' not in config:
|
||||
errors.append("Missing 'skill' section")
|
||||
else:
|
||||
if 'name' not in config['skill']:
|
||||
errors.append("Missing skill.name")
|
||||
if 'version' not in config['skill']:
|
||||
errors.append("Missing skill.version")
|
||||
|
||||
# Validate settings
|
||||
if 'settings' in config:
|
||||
settings = config['settings']
|
||||
if 'log_level' in settings:
|
||||
valid_levels = ['debug', 'info', 'warn', 'error']
|
||||
if settings['log_level'] not in valid_levels:
|
||||
errors.append(f"Invalid log_level: {settings['log_level']}")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"config": config if not errors else None
|
||||
}
|
||||
|
||||
|
||||
def validate_skill_structure(skill_path: str) -> dict:
|
||||
"""
|
||||
Validate skill directory structure.
|
||||
|
||||
Args:
|
||||
skill_path: Path to skill directory
|
||||
|
||||
Returns:
|
||||
dict: Structure validation result
|
||||
"""
|
||||
required_dirs = ['assets', 'scripts', 'references']
|
||||
required_files = ['SKILL.md']
|
||||
|
||||
errors = []
|
||||
|
||||
# Check required files
|
||||
for file in required_files:
|
||||
if not os.path.exists(os.path.join(skill_path, file)):
|
||||
errors.append(f"Missing required file: {file}")
|
||||
|
||||
# Check required directories
|
||||
for dir in required_dirs:
|
||||
dir_path = os.path.join(skill_path, dir)
|
||||
if not os.path.isdir(dir_path):
|
||||
errors.append(f"Missing required directory: {dir}/")
|
||||
else:
|
||||
# Check for real content (not just .gitkeep)
|
||||
files = [f for f in os.listdir(dir_path) if f != '.gitkeep']
|
||||
if not files:
|
||||
errors.append(f"Directory {dir}/ has no real content")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"skill_name": os.path.basename(skill_path)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Main validation entry point."""
|
||||
skill_path = Path(__file__).parent.parent
|
||||
|
||||
print(f"Validating java-spring-boot skill...")
|
||||
print(f"Path: {skill_path}")
|
||||
|
||||
# Validate structure
|
||||
structure_result = validate_skill_structure(str(skill_path))
|
||||
print(f"\nStructure validation: {'PASS' if structure_result['valid'] else 'FAIL'}")
|
||||
if structure_result['errors']:
|
||||
for error in structure_result['errors']:
|
||||
print(f" - {error}")
|
||||
|
||||
# Validate config
|
||||
config_path = skill_path / 'assets' / 'config.yaml'
|
||||
if config_path.exists():
|
||||
config_result = validate_config(str(config_path))
|
||||
print(f"\nConfig validation: {'PASS' if config_result['valid'] else 'FAIL'}")
|
||||
if config_result['errors']:
|
||||
for error in config_result['errors']:
|
||||
print(f" - {error}")
|
||||
else:
|
||||
print("\nConfig validation: SKIPPED (no config.yaml)")
|
||||
|
||||
# Summary
|
||||
all_valid = structure_result['valid']
|
||||
print(f"\n==================================================")
|
||||
print(f"Overall: {'VALID' if all_valid else 'INVALID'}")
|
||||
|
||||
return 0 if all_valid else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
name: spring-boot-engineer
|
||||
description: Use when building Spring Boot 3.x applications, microservices, or reactive Java applications. Invoke for Spring Data JPA, Spring Security 6, WebFlux, Spring Cloud integration.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: https://github.com/Jeffallan
|
||||
version: "1.0.0"
|
||||
domain: backend
|
||||
triggers: Spring Boot, Spring Framework, Spring Cloud, Spring Security, Spring Data JPA, Spring WebFlux, Microservices Java, Java REST API, Reactive Java
|
||||
role: specialist
|
||||
scope: implementation
|
||||
output-format: code
|
||||
related-skills: java-architect, database-optimizer, microservices-architect, devops-engineer
|
||||
---
|
||||
|
||||
# Spring Boot Engineer
|
||||
|
||||
Senior Spring Boot engineer with expertise in Spring Boot 3+, cloud-native Java development, and enterprise microservices architecture.
|
||||
|
||||
## Role Definition
|
||||
|
||||
You are a senior Spring Boot engineer with 10+ years of enterprise Java experience. You specialize in Spring Boot 3.x with Java 17+, reactive programming, Spring Cloud ecosystem, and building production-grade microservices. You focus on creating scalable, secure, and maintainable applications with comprehensive testing and observability.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Building REST APIs with Spring Boot
|
||||
- Implementing reactive applications with WebFlux
|
||||
- Setting up Spring Data JPA repositories
|
||||
- Implementing Spring Security 6 authentication
|
||||
- Creating microservices with Spring Cloud
|
||||
- Optimizing Spring Boot performance
|
||||
- Writing comprehensive tests with Spring Boot Test
|
||||
|
||||
## Core Workflow
|
||||
|
||||
1. **Analyze requirements** - Identify service boundaries, APIs, data models, security needs
|
||||
2. **Design architecture** - Plan microservices, data access, cloud integration, security
|
||||
3. **Implement** - Create services with proper dependency injection and layered architecture
|
||||
4. **Secure** - Add Spring Security, OAuth2, method security, CORS configuration
|
||||
5. **Test** - Write unit, integration, and slice tests with high coverage
|
||||
6. **Deploy** - Configure for cloud deployment with health checks and observability
|
||||
|
||||
## Reference Guide
|
||||
|
||||
Load detailed guidance based on context:
|
||||
|
||||
| Topic | Reference | Load When |
|
||||
|-------|-----------|-----------|
|
||||
| Web Layer | `references/web.md` | Controllers, REST APIs, validation, exception handling |
|
||||
| Data Access | `references/data.md` | Spring Data JPA, repositories, transactions, projections |
|
||||
| Security | `references/security.md` | Spring Security 6, OAuth2, JWT, method security |
|
||||
| Cloud Native | `references/cloud.md` | Spring Cloud, Config, Discovery, Gateway, resilience |
|
||||
| Testing | `references/testing.md` | @SpringBootTest, MockMvc, Testcontainers, test slices |
|
||||
|
||||
## Constraints
|
||||
|
||||
### MUST DO
|
||||
- Use Spring Boot 3.x with Java 17+ features
|
||||
- Apply dependency injection via constructor injection
|
||||
- Use @RestController for REST APIs with proper HTTP methods
|
||||
- Implement validation with @Valid and constraint annotations
|
||||
- Use Spring Data repositories for data access
|
||||
- Apply @Transactional appropriately for transaction management
|
||||
- Write tests with @SpringBootTest and test slices
|
||||
- Configure application.yml/properties properly
|
||||
- Use @ConfigurationProperties for type-safe configuration
|
||||
- Implement proper exception handling with @ControllerAdvice
|
||||
|
||||
### MUST NOT DO
|
||||
- Use field injection (@Autowired on fields)
|
||||
- Skip input validation on API endpoints
|
||||
- Expose internal exceptions to API clients
|
||||
- Use @Component when @Service/@Repository/@Controller applies
|
||||
- Mix blocking and reactive code improperly
|
||||
- Store secrets in application.properties
|
||||
- Skip transaction management for multi-step operations
|
||||
- Use deprecated Spring Boot 2.x patterns
|
||||
- Hardcode URLs, credentials, or configuration
|
||||
|
||||
## Output Templates
|
||||
|
||||
When implementing Spring Boot features, provide:
|
||||
1. Entity/model classes with JPA annotations
|
||||
2. Repository interfaces extending Spring Data
|
||||
3. Service layer with business logic
|
||||
4. Controller with REST endpoints
|
||||
5. DTO classes for API requests/responses
|
||||
6. Configuration classes if needed
|
||||
7. Test classes with appropriate test slices
|
||||
8. Brief explanation of architecture decisions
|
||||
|
||||
## Knowledge Reference
|
||||
|
||||
Spring Boot 3.x, Spring Framework 6, Spring Data JPA, Spring Security 6, Spring Cloud, Project Reactor (WebFlux), JPA/Hibernate, Bean Validation, RestTemplate/WebClient, Actuator, Micrometer, JUnit 5, Mockito, Testcontainers, Docker, Kubernetes
|
||||
@@ -1,498 +0,0 @@
|
||||
# Cloud Native - Spring Cloud
|
||||
|
||||
## Spring Cloud Config Server
|
||||
|
||||
```java
|
||||
// Config Server
|
||||
@SpringBootApplication
|
||||
@EnableConfigServer
|
||||
public class ConfigServerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ConfigServerApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
server:
|
||||
port: 8888
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
config:
|
||||
server:
|
||||
git:
|
||||
uri: https://github.com/example/config-repo
|
||||
default-label: main
|
||||
search-paths: '{application}'
|
||||
username: ${GIT_USERNAME}
|
||||
password: ${GIT_PASSWORD}
|
||||
native:
|
||||
search-locations: classpath:/config
|
||||
security:
|
||||
user:
|
||||
name: config-user
|
||||
password: ${CONFIG_PASSWORD}
|
||||
|
||||
// Config Client
|
||||
@SpringBootApplication
|
||||
public class ClientApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ClientApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Config Client)
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
config:
|
||||
import: "configserver:http://localhost:8888"
|
||||
cloud:
|
||||
config:
|
||||
username: config-user
|
||||
password: ${CONFIG_PASSWORD}
|
||||
fail-fast: true
|
||||
retry:
|
||||
max-attempts: 6
|
||||
initial-interval: 1000
|
||||
```
|
||||
|
||||
## Dynamic Configuration Refresh
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RefreshScope
|
||||
public class ConfigController {
|
||||
@Value("${app.feature.enabled:false}")
|
||||
private boolean featureEnabled;
|
||||
|
||||
@Value("${app.max-connections:100}")
|
||||
private int maxConnections;
|
||||
|
||||
@GetMapping("/config")
|
||||
public Map<String, Object> getConfig() {
|
||||
return Map.of(
|
||||
"featureEnabled", featureEnabled,
|
||||
"maxConnections", maxConnections
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh configuration via Actuator endpoint:
|
||||
// POST /actuator/refresh
|
||||
```
|
||||
|
||||
## Service Discovery - Eureka
|
||||
|
||||
```java
|
||||
// Eureka Server
|
||||
@SpringBootApplication
|
||||
@EnableEurekaServer
|
||||
public class EurekaServerApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaServerApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Eureka Server)
|
||||
server:
|
||||
port: 8761
|
||||
|
||||
eureka:
|
||||
instance:
|
||||
hostname: localhost
|
||||
client:
|
||||
register-with-eureka: false
|
||||
fetch-registry: false
|
||||
service-url:
|
||||
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
||||
|
||||
// Eureka Client
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class UserServiceApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Eureka Client)
|
||||
spring:
|
||||
application:
|
||||
name: user-service
|
||||
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
defaultZone: http://localhost:8761/eureka/
|
||||
registry-fetch-interval-seconds: 5
|
||||
instance:
|
||||
prefer-ip-address: true
|
||||
lease-renewal-interval-in-seconds: 10
|
||||
lease-expiration-duration-in-seconds: 30
|
||||
```
|
||||
|
||||
## Spring Cloud Gateway
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
|
||||
return builder.routes()
|
||||
.route("user-service", r -> r
|
||||
.path("/api/users/**")
|
||||
.filters(f -> f
|
||||
.rewritePath("/api/users/(?<segment>.*)", "/users/${segment}")
|
||||
.addRequestHeader("X-Gateway", "Spring-Cloud-Gateway")
|
||||
.circuitBreaker(config -> config
|
||||
.setName("userServiceCircuitBreaker")
|
||||
.setFallbackUri("forward:/fallback/users")
|
||||
)
|
||||
.retry(config -> config
|
||||
.setRetries(3)
|
||||
.setStatuses(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
)
|
||||
)
|
||||
.uri("lb://user-service")
|
||||
)
|
||||
.route("order-service", r -> r
|
||||
.path("/api/orders/**")
|
||||
.filters(f -> f
|
||||
.rewritePath("/api/orders/(?<segment>.*)", "/orders/${segment}")
|
||||
.requestRateLimiter(config -> config
|
||||
.setRateLimiter(redisRateLimiter())
|
||||
.setKeyResolver(userKeyResolver())
|
||||
)
|
||||
)
|
||||
.uri("lb://order-service")
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisRateLimiter redisRateLimiter() {
|
||||
return new RedisRateLimiter(10, 20); // replenishRate, burstCapacity
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeyResolver userKeyResolver() {
|
||||
return exchange -> Mono.just(
|
||||
exchange.getRequest().getHeaders().getFirst("X-User-Id")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml (Gateway)
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
discovery:
|
||||
locator:
|
||||
enabled: true
|
||||
lower-case-service-id: true
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Origin
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowed-origins: "*"
|
||||
allowed-methods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
allowed-headers: "*"
|
||||
```
|
||||
|
||||
## Circuit Breaker - Resilience4j
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalApiService {
|
||||
private final WebClient webClient;
|
||||
|
||||
@CircuitBreaker(name = "externalApi", fallbackMethod = "getFallbackData")
|
||||
@Retry(name = "externalApi")
|
||||
@RateLimiter(name = "externalApi")
|
||||
public Mono<ExternalData> getData(String id) {
|
||||
return webClient
|
||||
.get()
|
||||
.uri("/data/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(ExternalData.class)
|
||||
.timeout(Duration.ofSeconds(3));
|
||||
}
|
||||
|
||||
private Mono<ExternalData> getFallbackData(String id, Exception e) {
|
||||
log.warn("Fallback triggered for id: {}, error: {}", id, e.getMessage());
|
||||
return Mono.just(new ExternalData(id, "Fallback data", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
resilience4j:
|
||||
circuitbreaker:
|
||||
instances:
|
||||
externalApi:
|
||||
register-health-indicator: true
|
||||
sliding-window-size: 10
|
||||
minimum-number-of-calls: 5
|
||||
permitted-number-of-calls-in-half-open-state: 3
|
||||
automatic-transition-from-open-to-half-open-enabled: true
|
||||
wait-duration-in-open-state: 5s
|
||||
failure-rate-threshold: 50
|
||||
event-consumer-buffer-size: 10
|
||||
|
||||
retry:
|
||||
instances:
|
||||
externalApi:
|
||||
max-attempts: 3
|
||||
wait-duration: 1s
|
||||
enable-exponential-backoff: true
|
||||
exponential-backoff-multiplier: 2
|
||||
|
||||
ratelimiter:
|
||||
instances:
|
||||
externalApi:
|
||||
limit-for-period: 10
|
||||
limit-refresh-period: 1s
|
||||
timeout-duration: 0s
|
||||
```
|
||||
|
||||
## Distributed Tracing - Micrometer Tracing
|
||||
|
||||
```java
|
||||
// application.yml
|
||||
management:
|
||||
tracing:
|
||||
sampling:
|
||||
probability: 1.0
|
||||
zipkin:
|
||||
tracing:
|
||||
endpoint: http://localhost:9411/api/v2/spans
|
||||
|
||||
logging:
|
||||
pattern:
|
||||
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
|
||||
|
||||
// Custom spans
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OrderService {
|
||||
private final Tracer tracer;
|
||||
private final OrderRepository orderRepository;
|
||||
|
||||
public Order processOrder(OrderRequest request) {
|
||||
Span span = tracer.nextSpan().name("processOrder").start();
|
||||
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
|
||||
span.tag("order.type", request.type());
|
||||
span.tag("order.items", String.valueOf(request.items().size()));
|
||||
|
||||
// Business logic
|
||||
Order order = createOrder(request);
|
||||
|
||||
span.event("order.created");
|
||||
return order;
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Load Balancing with Spring Cloud LoadBalancer
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@LoadBalancerClient(name = "user-service", configuration = UserServiceLoadBalancerConfig.class)
|
||||
public class LoadBalancerConfiguration {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public class UserServiceLoadBalancerConfig {
|
||||
|
||||
@Bean
|
||||
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
|
||||
LoadBalancerClientFactory clientFactory,
|
||||
ObjectProvider<LoadBalancerProperties> properties) {
|
||||
return new RandomLoadBalancer(
|
||||
clientFactory.getLazyProvider("user-service", ServiceInstanceListSupplier.class),
|
||||
"user-service"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserClientService {
|
||||
private final WebClient.Builder webClientBuilder;
|
||||
|
||||
public Mono<User> getUser(Long id) {
|
||||
return webClientBuilder
|
||||
.baseUrl("http://user-service")
|
||||
.build()
|
||||
.get()
|
||||
.uri("/users/{id}", id)
|
||||
.retrieve()
|
||||
.bodyToMono(User.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Health Checks & Actuator
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class CustomHealthIndicator implements HealthIndicator {
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
boolean serviceUp = checkExternalService();
|
||||
|
||||
if (serviceUp) {
|
||||
return Health.up()
|
||||
.withDetail("externalService", "Available")
|
||||
.withDetail("timestamp", LocalDateTime.now())
|
||||
.build();
|
||||
} else {
|
||||
return Health.down()
|
||||
.withDetail("externalService", "Unavailable")
|
||||
.withDetail("error", "Connection timeout")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkExternalService() {
|
||||
// Check external dependency
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// application.yml
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
probes:
|
||||
enabled: true
|
||||
health:
|
||||
livenessState:
|
||||
enabled: true
|
||||
readinessState:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
prometheus:
|
||||
enabled: true
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
```
|
||||
|
||||
## Kubernetes Deployment
|
||||
|
||||
```yaml
|
||||
# deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: user-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: user-service
|
||||
spec:
|
||||
containers:
|
||||
- name: user-service
|
||||
image: user-service:1.0.0
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: "kubernetes"
|
||||
- name: JAVA_OPTS
|
||||
value: "-Xmx512m -Xms256m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: user-service
|
||||
spec:
|
||||
selector:
|
||||
app: user-service
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile (Multi-stage)
|
||||
FROM eclipse-temurin:17-jdk-alpine AS build
|
||||
WORKDIR /workspace/app
|
||||
|
||||
COPY mvnw .
|
||||
COPY .mvn .mvn
|
||||
COPY pom.xml .
|
||||
COPY src src
|
||||
|
||||
RUN ./mvnw install -DskipTests
|
||||
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
|
||||
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
VOLUME /tmp
|
||||
ARG DEPENDENCY=/workspace/app/target/dependency
|
||||
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
|
||||
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
|
||||
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
|
||||
|
||||
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.Application"]
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| **Config Server** | Centralized configuration management |
|
||||
| **Eureka** | Service discovery and registration |
|
||||
| **Gateway** | API gateway with routing, filtering, load balancing |
|
||||
| **Circuit Breaker** | Fault tolerance and fallback patterns |
|
||||
| **Load Balancer** | Client-side load balancing |
|
||||
| **Tracing** | Distributed tracing across services |
|
||||
| **Actuator** | Production-ready monitoring and management |
|
||||
| **Kubernetes** | Container orchestration and deployment |
|
||||
@@ -1,381 +0,0 @@
|
||||
# Data Access - Spring Data JPA
|
||||
|
||||
## JPA Entity Pattern
|
||||
|
||||
```java
|
||||
@Entity
|
||||
@Table(name = "users", indexes = {
|
||||
@Index(name = "idx_email", columnList = "email", unique = true),
|
||||
@Index(name = "idx_username", columnList = "username")
|
||||
})
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Getter @Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 100)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
@Column(nullable = false, unique = true, length = 50)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean active = true;
|
||||
|
||||
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Builder.Default
|
||||
private List<Address> addresses = new ArrayList<>();
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "user_roles",
|
||||
joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "role_id")
|
||||
)
|
||||
@Builder.Default
|
||||
private Set<Role> roles = new HashSet<>();
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
// Helper methods for bidirectional relationships
|
||||
public void addAddress(Address address) {
|
||||
addresses.add(address);
|
||||
address.setUser(this);
|
||||
}
|
||||
|
||||
public void removeAddress(Address address) {
|
||||
addresses.remove(address);
|
||||
address.setUser(null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Spring Data JPA Repository
|
||||
|
||||
```java
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long>,
|
||||
JpaSpecificationExecutor<User> {
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
Optional<User> findByUsername(String username);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
boolean existsByUsername(String username);
|
||||
|
||||
@Query("SELECT u FROM User u LEFT JOIN FETCH u.roles WHERE u.email = :email")
|
||||
Optional<User> findByEmailWithRoles(@Param("email") String email);
|
||||
|
||||
@Query("SELECT u FROM User u WHERE u.active = true AND u.createdAt >= :since")
|
||||
List<User> findActiveUsersSince(@Param("since") LocalDateTime since);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE User u SET u.active = false WHERE u.lastLoginAt < :threshold")
|
||||
int deactivateInactiveUsers(@Param("threshold") LocalDateTime threshold);
|
||||
|
||||
// Projection for read-only DTOs
|
||||
@Query("SELECT new com.example.dto.UserSummary(u.id, u.username, u.email) " +
|
||||
"FROM User u WHERE u.active = true")
|
||||
List<UserSummary> findAllActiveSummaries();
|
||||
}
|
||||
```
|
||||
|
||||
## Repository with Specifications
|
||||
|
||||
```java
|
||||
public class UserSpecifications {
|
||||
|
||||
public static Specification<User> hasEmail(String email) {
|
||||
return (root, query, cb) ->
|
||||
email == null ? null : cb.equal(root.get("email"), email);
|
||||
}
|
||||
|
||||
public static Specification<User> isActive() {
|
||||
return (root, query, cb) -> cb.isTrue(root.get("active"));
|
||||
}
|
||||
|
||||
public static Specification<User> createdAfter(LocalDateTime date) {
|
||||
return (root, query, cb) ->
|
||||
date == null ? null : cb.greaterThanOrEqualTo(root.get("createdAt"), date);
|
||||
}
|
||||
|
||||
public static Specification<User> hasRole(String roleName) {
|
||||
return (root, query, cb) -> {
|
||||
Join<User, Role> roles = root.join("roles", JoinType.INNER);
|
||||
return cb.equal(roles.get("name"), roleName);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in service
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public Page<User> searchUsers(UserSearchCriteria criteria, Pageable pageable) {
|
||||
Specification<User> spec = Specification
|
||||
.where(UserSpecifications.hasEmail(criteria.email()))
|
||||
.and(UserSpecifications.isActive())
|
||||
.and(UserSpecifications.createdAfter(criteria.createdAfter()));
|
||||
|
||||
return userRepository.findAll(spec, pageable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Management
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class OrderService {
|
||||
private final OrderRepository orderRepository;
|
||||
private final PaymentService paymentService;
|
||||
private final InventoryService inventoryService;
|
||||
private final NotificationService notificationService;
|
||||
|
||||
@Transactional
|
||||
public Order createOrder(OrderCreateRequest request) {
|
||||
// All operations in single transaction
|
||||
Order order = Order.builder()
|
||||
.customerId(request.customerId())
|
||||
.status(OrderStatus.PENDING)
|
||||
.build();
|
||||
|
||||
request.items().forEach(item -> {
|
||||
inventoryService.reserveStock(item.productId(), item.quantity());
|
||||
order.addItem(item);
|
||||
});
|
||||
|
||||
order = orderRepository.save(order);
|
||||
|
||||
try {
|
||||
paymentService.processPayment(order);
|
||||
order.setStatus(OrderStatus.PAID);
|
||||
} catch (PaymentException e) {
|
||||
order.setStatus(OrderStatus.PAYMENT_FAILED);
|
||||
throw e; // Transaction will rollback
|
||||
}
|
||||
|
||||
return orderRepository.save(order);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void logOrderEvent(Long orderId, String event) {
|
||||
// Separate transaction - will commit even if parent rolls back
|
||||
OrderEvent orderEvent = new OrderEvent(orderId, event);
|
||||
orderEventRepository.save(orderEvent);
|
||||
}
|
||||
|
||||
@Transactional(noRollbackFor = NotificationException.class)
|
||||
public void completeOrder(Long orderId) {
|
||||
Order order = orderRepository.findById(orderId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Order not found"));
|
||||
|
||||
order.setStatus(OrderStatus.COMPLETED);
|
||||
orderRepository.save(order);
|
||||
|
||||
// Won't rollback transaction if notification fails
|
||||
try {
|
||||
notificationService.sendCompletionEmail(order);
|
||||
} catch (NotificationException e) {
|
||||
log.error("Failed to send notification for order {}", orderId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Auditing Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaAuditingConfig {
|
||||
|
||||
@Bean
|
||||
public AuditorAware<String> auditorProvider() {
|
||||
return () -> {
|
||||
Authentication authentication = SecurityContextHolder
|
||||
.getContext()
|
||||
.getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return Optional.of("system");
|
||||
}
|
||||
|
||||
return Optional.of(authentication.getName());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@Getter @Setter
|
||||
public abstract class AuditableEntity {
|
||||
|
||||
@CreatedDate
|
||||
@Column(nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@CreatedBy
|
||||
@Column(nullable = false, updatable = false, length = 100)
|
||||
private String createdBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(nullable = false, length = 100)
|
||||
private String updatedBy;
|
||||
}
|
||||
```
|
||||
|
||||
## Projections
|
||||
|
||||
```java
|
||||
// Interface-based projection
|
||||
public interface UserSummary {
|
||||
Long getId();
|
||||
String getUsername();
|
||||
String getEmail();
|
||||
|
||||
@Value("#{target.firstName + ' ' + target.lastName}")
|
||||
String getFullName();
|
||||
}
|
||||
|
||||
// Class-based projection (DTO)
|
||||
public record UserSummaryDto(
|
||||
Long id,
|
||||
String username,
|
||||
String email
|
||||
) {}
|
||||
|
||||
// Usage
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
List<UserSummary> findAllBy();
|
||||
|
||||
<T> List<T> findAllBy(Class<T> type);
|
||||
}
|
||||
|
||||
// Service usage
|
||||
List<UserSummary> summaries = userRepository.findAllBy();
|
||||
List<UserSummaryDto> dtos = userRepository.findAllBy(UserSummaryDto.class);
|
||||
```
|
||||
|
||||
## Query Optimization
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class UserQueryService {
|
||||
private final UserRepository userRepository;
|
||||
private final EntityManager entityManager;
|
||||
|
||||
// N+1 problem solved with JOIN FETCH
|
||||
@Query("SELECT DISTINCT u FROM User u " +
|
||||
"LEFT JOIN FETCH u.addresses " +
|
||||
"LEFT JOIN FETCH u.roles " +
|
||||
"WHERE u.active = true")
|
||||
List<User> findAllActiveWithAssociations();
|
||||
|
||||
// Batch fetching
|
||||
@BatchSize(size = 25)
|
||||
@OneToMany(mappedBy = "user")
|
||||
private List<Order> orders;
|
||||
|
||||
// EntityGraph for dynamic fetching
|
||||
@EntityGraph(attributePaths = {"addresses", "roles"})
|
||||
List<User> findAllByActiveTrue();
|
||||
|
||||
// Pagination to avoid loading all data
|
||||
public Page<User> findAllUsers(Pageable pageable) {
|
||||
return userRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
// Native query for complex queries
|
||||
@Query(value = """
|
||||
SELECT u.* FROM users u
|
||||
INNER JOIN orders o ON u.id = o.user_id
|
||||
WHERE o.created_at >= :since
|
||||
GROUP BY u.id
|
||||
HAVING COUNT(o.id) >= :minOrders
|
||||
""", nativeQuery = true)
|
||||
List<User> findFrequentBuyers(@Param("since") LocalDateTime since,
|
||||
@Param("minOrders") int minOrders);
|
||||
}
|
||||
```
|
||||
|
||||
## Database Migrations (Flyway)
|
||||
|
||||
```sql
|
||||
-- V1__create_users_table.sql
|
||||
CREATE TABLE users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password VARCHAR(100) NOT NULL,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
version BIGINT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_users_username ON users(username);
|
||||
CREATE INDEX idx_users_active ON users(active);
|
||||
|
||||
-- V2__create_addresses_table.sql
|
||||
CREATE TABLE addresses (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
street VARCHAR(200) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
country VARCHAR(2) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_addresses_user_id ON addresses(user_id);
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@Entity` | Marks class as JPA entity |
|
||||
| `@Table` | Specifies table details and indexes |
|
||||
| `@Id` | Marks primary key field |
|
||||
| `@GeneratedValue` | Auto-generated primary key strategy |
|
||||
| `@Column` | Column constraints and mapping |
|
||||
| `@OneToMany/@ManyToOne` | One-to-many/many-to-one relationships |
|
||||
| `@ManyToMany` | Many-to-many relationships |
|
||||
| `@JoinColumn/@JoinTable` | Join column/table configuration |
|
||||
| `@Transactional` | Declares transaction boundaries |
|
||||
| `@Query` | Custom JPQL/native queries |
|
||||
| `@Modifying` | Marks query as UPDATE/DELETE |
|
||||
| `@EntityGraph` | Defines fetch graph for associations |
|
||||
| `@Version` | Optimistic locking version field |
|
||||
@@ -1,459 +0,0 @@
|
||||
# Security - Spring Security 6
|
||||
|
||||
## Security Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf
|
||||
.ignoringRequestMatchers("/api/auth/**")
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/auth/**", "/actuator/health").permitAll()
|
||||
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
||||
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.exceptionHandling(ex -> ex
|
||||
.authenticationEntryPoint(authenticationEntryPoint())
|
||||
.accessDeniedHandler(accessDeniedHandler())
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter(),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(List.of("http://localhost:3000"));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JWT Authentication Filter
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final JwtService jwtService;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletRequest response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
final String jwt;
|
||||
final String username;
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
jwt = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
username = jwtService.extractUsername(jwt);
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext()
|
||||
.getAuthentication() == null) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
if (jwtService.isTokenValid(jwt, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
|
||||
authToken.setDetails(
|
||||
new WebAuthenticationDetailsSource().buildDetails(request)
|
||||
);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
} catch (JwtException e) {
|
||||
log.error("JWT validation failed", e);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JWT Service
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class JwtService {
|
||||
@Value("${jwt.secret}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private long jwtExpiration;
|
||||
|
||||
@Value("${jwt.refresh-expiration}")
|
||||
private long refreshExpiration;
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
Map<String, Object> extraClaims = new HashMap<>();
|
||||
extraClaims.put("roles", userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
return generateToken(extraClaims, userDetails);
|
||||
}
|
||||
|
||||
public String generateToken(
|
||||
Map<String, Object> extraClaims,
|
||||
UserDetails userDetails) {
|
||||
return buildToken(extraClaims, userDetails, jwtExpiration);
|
||||
}
|
||||
|
||||
public String generateRefreshToken(UserDetails userDetails) {
|
||||
return buildToken(new HashMap<>(), userDetails, refreshExpiration);
|
||||
}
|
||||
|
||||
private String buildToken(
|
||||
Map<String, Object> extraClaims,
|
||||
UserDetails userDetails,
|
||||
long expiration) {
|
||||
return Jwts
|
||||
.builder()
|
||||
.setClaims(extraClaims)
|
||||
.setSubject(userDetails.getUsername())
|
||||
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token, UserDetails userDetails) {
|
||||
final String username = extractUsername(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts
|
||||
.parserBuilder()
|
||||
.setSigningKey(getSignInKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
private Key getSignInKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## UserDetailsService Implementation
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByEmailWithRoles(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException(
|
||||
"User not found with email: " + username));
|
||||
|
||||
return org.springframework.security.core.userdetails.User
|
||||
.builder()
|
||||
.username(user.getEmail())
|
||||
.password(user.getPassword())
|
||||
.authorities(user.getRoles().stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
|
||||
.collect(Collectors.toList()))
|
||||
.accountExpired(false)
|
||||
.accountLocked(!user.getActive())
|
||||
.credentialsExpired(false)
|
||||
.disabled(!user.getActive())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Controller
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationController {
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<AuthenticationResponse> register(
|
||||
@Valid @RequestBody RegisterRequest request) {
|
||||
AuthenticationResponse response = authenticationService.register(request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthenticationResponse> login(
|
||||
@Valid @RequestBody LoginRequest request) {
|
||||
AuthenticationResponse response = authenticationService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<AuthenticationResponse> refreshToken(
|
||||
@RequestBody RefreshTokenRequest request) {
|
||||
AuthenticationResponse response = authenticationService.refreshToken(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public ResponseEntity<Void> logout() {
|
||||
SecurityContextHolder.clearContext();
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Service
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class AuthenticationService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtService jwtService;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
public AuthenticationResponse register(RegisterRequest request) {
|
||||
if (userRepository.existsByEmail(request.email())) {
|
||||
throw new DuplicateResourceException("Email already registered");
|
||||
}
|
||||
|
||||
User user = User.builder()
|
||||
.email(request.email())
|
||||
.password(passwordEncoder.encode(request.password()))
|
||||
.username(request.username())
|
||||
.active(true)
|
||||
.roles(Set.of(Role.builder().name("USER").build()))
|
||||
.build();
|
||||
|
||||
user = userRepository.save(user);
|
||||
|
||||
String accessToken = jwtService.generateToken(convertToUserDetails(user));
|
||||
String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));
|
||||
|
||||
return new AuthenticationResponse(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
public AuthenticationResponse login(LoginRequest request) {
|
||||
authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
request.email(),
|
||||
request.password()
|
||||
)
|
||||
);
|
||||
|
||||
User user = userRepository.findByEmail(request.email())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
String accessToken = jwtService.generateToken(convertToUserDetails(user));
|
||||
String refreshToken = jwtService.generateRefreshToken(convertToUserDetails(user));
|
||||
|
||||
return new AuthenticationResponse(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
public AuthenticationResponse refreshToken(RefreshTokenRequest request) {
|
||||
String username = jwtService.extractUsername(request.refreshToken());
|
||||
|
||||
User user = userRepository.findByEmail(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
UserDetails userDetails = convertToUserDetails(user);
|
||||
|
||||
if (!jwtService.isTokenValid(request.refreshToken(), userDetails)) {
|
||||
throw new InvalidTokenException("Invalid refresh token");
|
||||
}
|
||||
|
||||
String accessToken = jwtService.generateToken(userDetails);
|
||||
|
||||
return new AuthenticationResponse(accessToken, request.refreshToken());
|
||||
}
|
||||
|
||||
private UserDetails convertToUserDetails(User user) {
|
||||
return org.springframework.security.core.userdetails.User
|
||||
.builder()
|
||||
.username(user.getEmail())
|
||||
.password(user.getPassword())
|
||||
.authorities(user.getRoles().stream()
|
||||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Method Security
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public List<User> getAllUsers() {
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
|
||||
public User getUserById(Long userId) {
|
||||
return userRepository.findById(userId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
@PostAuthorize("returnObject.email == authentication.principal.username")
|
||||
public User updateProfile(Long userId, UserUpdateRequest request) {
|
||||
User user = getUserById(userId);
|
||||
// Update logic
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
|
||||
public void deleteUser(Long userId) {
|
||||
userRepository.deleteById(userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OAuth2 Resource Server (JWT)
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class OAuth2ResourceServerConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/public/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 -> oauth2
|
||||
.jwt(jwt -> jwt
|
||||
.jwtAuthenticationConverter(jwtAuthenticationConverter())
|
||||
)
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtDecoder jwtDecoder() {
|
||||
return JwtDecoders.fromIssuerLocation("https://auth.example.com");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
|
||||
new JwtGrantedAuthoritiesConverter();
|
||||
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
|
||||
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
||||
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter =
|
||||
new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
|
||||
grantedAuthoritiesConverter);
|
||||
|
||||
return jwtAuthenticationConverter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@EnableWebSecurity` | Enables Spring Security |
|
||||
| `@EnableMethodSecurity` | Enables method-level security annotations |
|
||||
| `@PreAuthorize` | Checks authorization before method execution |
|
||||
| `@PostAuthorize` | Checks authorization after method execution |
|
||||
| `@Secured` | Role-based method security |
|
||||
| `@WithMockUser` | Mock authenticated user in tests |
|
||||
| `@AuthenticationPrincipal` | Inject current user in controller |
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
- Always use HTTPS in production
|
||||
- Store JWT secret in environment variables
|
||||
- Use strong password encoding (BCrypt with strength 12+)
|
||||
- Implement token refresh mechanism
|
||||
- Add rate limiting to authentication endpoints
|
||||
- Validate all user inputs
|
||||
- Log security events
|
||||
- Keep dependencies updated
|
||||
- Use CSRF protection for state-changing operations
|
||||
- Implement proper session timeout
|
||||
@@ -1,545 +0,0 @@
|
||||
# Testing - Spring Boot Test
|
||||
|
||||
## Unit Testing with JUnit 5
|
||||
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@InjectMocks
|
||||
private UserService userService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create user successfully")
|
||||
void shouldCreateUser() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
User user = User.builder()
|
||||
.id(1L)
|
||||
.email(request.email())
|
||||
.username(request.username())
|
||||
.build();
|
||||
|
||||
when(userRepository.existsByEmail(request.email())).thenReturn(false);
|
||||
when(passwordEncoder.encode(request.password())).thenReturn("encodedPassword");
|
||||
when(userRepository.save(any(User.class))).thenReturn(user);
|
||||
|
||||
// When
|
||||
UserResponse response = userService.create(request);
|
||||
|
||||
// Then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.email()).isEqualTo(request.email());
|
||||
|
||||
verify(userRepository).existsByEmail(request.email());
|
||||
verify(passwordEncoder).encode(request.password());
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should throw exception when email already exists")
|
||||
void shouldThrowExceptionWhenEmailExists() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
when(userRepository.existsByEmail(request.email())).thenReturn(true);
|
||||
|
||||
// When & Then
|
||||
assertThatThrownBy(() -> userService.create(request))
|
||||
.isInstanceOf(DuplicateResourceException.class)
|
||||
.hasMessageContaining("Email already registered");
|
||||
|
||||
verify(userRepository, never()).save(any(User.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Testing with @SpringBootTest
|
||||
|
||||
```java
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
class UserIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@DisplayName("Should create user via API")
|
||||
void shouldCreateUserViaApi() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
// When
|
||||
ResponseEntity<UserResponse> response = restTemplate.postForEntity(
|
||||
"/api/v1/users",
|
||||
request,
|
||||
UserResponse.class
|
||||
);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(response.getBody().email()).isEqualTo(request.email());
|
||||
assertThat(response.getHeaders().getLocation()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
@DisplayName("Should return validation error for invalid request")
|
||||
void shouldReturnValidationError() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"invalid-email",
|
||||
"short",
|
||||
"u",
|
||||
15
|
||||
);
|
||||
|
||||
// When
|
||||
ResponseEntity<ValidationErrorResponse> response = restTemplate.postForEntity(
|
||||
"/api/v1/users",
|
||||
request,
|
||||
ValidationErrorResponse.class
|
||||
);
|
||||
|
||||
// Then
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(response.getBody().errors()).isNotEmpty();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Web Layer Testing with MockMvc
|
||||
|
||||
```java
|
||||
@WebMvcTest(UserController.class)
|
||||
@Import(SecurityConfig.class)
|
||||
class UserControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@DisplayName("Should get all users")
|
||||
void shouldGetAllUsers() throws Exception {
|
||||
// Given
|
||||
Page<UserResponse> users = new PageImpl<>(List.of(
|
||||
new UserResponse(1L, "user1@example.com", "user1", 25, true, null, null),
|
||||
new UserResponse(2L, "user2@example.com", "user2", 30, true, null, null)
|
||||
));
|
||||
|
||||
when(userService.findAll(any(Pageable.class))).thenReturn(users);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.content").isArray())
|
||||
.andExpect(jsonPath("$.content.length()").value(2))
|
||||
.andExpect(jsonPath("$.content[0].email").value("user1@example.com"))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@DisplayName("Should create user")
|
||||
void shouldCreateUser() throws Exception {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
UserResponse response = new UserResponse(
|
||||
1L,
|
||||
request.email(),
|
||||
request.username(),
|
||||
request.age(),
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.create(any(UserCreateRequest.class))).thenReturn(response);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(post("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(header().exists("Location"))
|
||||
.andExpect(jsonPath("$.email").value(request.email()))
|
||||
.andExpect(jsonPath("$.username").value(request.username()))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "USER")
|
||||
@DisplayName("Should return 403 for non-admin user")
|
||||
void shouldReturn403ForNonAdmin() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isForbidden());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data JPA Testing
|
||||
|
||||
```java
|
||||
@DataJpaTest
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@ActiveProfiles("test")
|
||||
class UserRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager entityManager;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should find user by email")
|
||||
void shouldFindUserByEmail() {
|
||||
// Given
|
||||
User user = User.builder()
|
||||
.email("test@example.com")
|
||||
.password("password")
|
||||
.username("testuser")
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
|
||||
// When
|
||||
Optional<User> found = userRepository.findByEmail("test@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getEmail()).isEqualTo("test@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should check if email exists")
|
||||
void shouldCheckIfEmailExists() {
|
||||
// Given
|
||||
User user = User.builder()
|
||||
.email("test@example.com")
|
||||
.password("password")
|
||||
.username("testuser")
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
|
||||
// When
|
||||
boolean exists = userRepository.existsByEmail("test@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should fetch user with roles")
|
||||
void shouldFetchUserWithRoles() {
|
||||
// Given
|
||||
Role adminRole = Role.builder().name("ADMIN").build();
|
||||
entityManager.persist(adminRole);
|
||||
|
||||
User user = User.builder()
|
||||
.email("admin@example.com")
|
||||
.password("password")
|
||||
.username("admin")
|
||||
.active(true)
|
||||
.roles(Set.of(adminRole))
|
||||
.build();
|
||||
|
||||
entityManager.persistAndFlush(user);
|
||||
entityManager.clear();
|
||||
|
||||
// When
|
||||
Optional<User> found = userRepository.findByEmailWithRoles("admin@example.com");
|
||||
|
||||
// Then
|
||||
assertThat(found).isPresent();
|
||||
assertThat(found.get().getRoles()).hasSize(1);
|
||||
assertThat(found.get().getRoles()).extracting(Role::getName).contains("ADMIN");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testcontainers for Database
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@Testcontainers
|
||||
@ActiveProfiles("test")
|
||||
class UserServiceIntegrationTest {
|
||||
|
||||
@Container
|
||||
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
|
||||
.withDatabaseName("testdb")
|
||||
.withUsername("test")
|
||||
.withPassword("test");
|
||||
|
||||
@DynamicPropertySource
|
||||
static void configureProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("spring.datasource.url", postgres::getJdbcUrl);
|
||||
registry.add("spring.datasource.username", postgres::getUsername);
|
||||
registry.add("spring.datasource.password", postgres::getPassword);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create and find user in real database")
|
||||
void shouldCreateAndFindUser() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
// When
|
||||
UserResponse created = userService.create(request);
|
||||
UserResponse found = userService.findById(created.id());
|
||||
|
||||
// Then
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.email()).isEqualTo(request.email());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Reactive Endpoints with WebTestClient
|
||||
|
||||
```java
|
||||
@WebFluxTest(UserReactiveController.class)
|
||||
class UserReactiveControllerTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@MockBean
|
||||
private UserReactiveService userService;
|
||||
|
||||
@Test
|
||||
@DisplayName("Should get user reactively")
|
||||
void shouldGetUserReactively() {
|
||||
// Given
|
||||
UserResponse user = new UserResponse(
|
||||
1L,
|
||||
"test@example.com",
|
||||
"testuser",
|
||||
25,
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.findById(1L)).thenReturn(Mono.just(user));
|
||||
|
||||
// When & Then
|
||||
webTestClient.get()
|
||||
.uri("/api/v1/users/{id}", 1L)
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(UserResponse.class)
|
||||
.value(response -> {
|
||||
assertThat(response.id()).isEqualTo(1L);
|
||||
assertThat(response.email()).isEqualTo("test@example.com");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Should create user reactively")
|
||||
void shouldCreateUserReactively() {
|
||||
// Given
|
||||
UserCreateRequest request = new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
|
||||
UserResponse response = new UserResponse(
|
||||
1L,
|
||||
request.email(),
|
||||
request.username(),
|
||||
request.age(),
|
||||
true,
|
||||
LocalDateTime.now(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
when(userService.create(any(UserCreateRequest.class))).thenReturn(Mono.just(response));
|
||||
|
||||
// When & Then
|
||||
webTestClient.post()
|
||||
.uri("/api/v1/users")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(Mono.just(request), UserCreateRequest.class)
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectHeader().exists("Location")
|
||||
.expectBody(UserResponse.class)
|
||||
.value(user -> {
|
||||
assertThat(user.email()).isEqualTo(request.email());
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
```java
|
||||
// application-test.yml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: create-drop
|
||||
show-sql: true
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
security:
|
||||
user:
|
||||
name: test
|
||||
password: test
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
|
||||
// Test Configuration Class
|
||||
@TestConfiguration
|
||||
public class TestConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(4); // Faster for tests
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Clock fixedClock() {
|
||||
return Clock.fixed(
|
||||
Instant.parse("2024-01-01T00:00:00Z"),
|
||||
ZoneId.of("UTC")
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Fixtures with @DataJpaTest
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class TestDataFactory {
|
||||
|
||||
public static User createUser(String email, String username) {
|
||||
return User.builder()
|
||||
.email(email)
|
||||
.password("encodedPassword")
|
||||
.username(username)
|
||||
.active(true)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static UserCreateRequest createUserRequest() {
|
||||
return new UserCreateRequest(
|
||||
"test@example.com",
|
||||
"Password123",
|
||||
"testuser",
|
||||
25
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@SpringBootTest` | Full application context integration test |
|
||||
| `@WebMvcTest` | Test MVC controllers with mocked services |
|
||||
| `@WebFluxTest` | Test reactive controllers |
|
||||
| `@DataJpaTest` | Test JPA repositories with in-memory database |
|
||||
| `@MockBean` | Add mock bean to Spring context |
|
||||
| `@WithMockUser` | Mock authenticated user for security tests |
|
||||
| `@Testcontainers` | Enable Testcontainers support |
|
||||
| `@ActiveProfiles` | Activate specific Spring profiles for test |
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
- Write tests following AAA pattern (Arrange, Act, Assert)
|
||||
- Use descriptive test names with @DisplayName
|
||||
- Mock external dependencies, use real DB with Testcontainers
|
||||
- Achieve 85%+ code coverage
|
||||
- Test happy path and edge cases
|
||||
- Use @Transactional for test data cleanup
|
||||
- Separate unit tests from integration tests
|
||||
- Use parameterized tests for multiple scenarios
|
||||
- Test security rules and validation
|
||||
- Keep tests fast and independent
|
||||
@@ -1,295 +0,0 @@
|
||||
# Web Layer - Controllers & REST APIs
|
||||
|
||||
## REST Controller Pattern
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/users")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<UserResponse>> getUsers(
|
||||
@PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {
|
||||
Page<UserResponse> users = userService.findAll(pageable);
|
||||
return ResponseEntity.ok(users);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
|
||||
UserResponse user = userService.findById(id);
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<UserResponse> createUser(
|
||||
@Valid @RequestBody UserCreateRequest request) {
|
||||
UserResponse user = userService.create(request);
|
||||
URI location = ServletUriComponentsBuilder
|
||||
.fromCurrentRequest()
|
||||
.path("/{id}")
|
||||
.buildAndExpand(user.id())
|
||||
.toUri();
|
||||
return ResponseEntity.created(location).body(user);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<UserResponse> updateUser(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody UserUpdateRequest request) {
|
||||
UserResponse user = userService.update(id, request);
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
public void deleteUser(@PathVariable Long id) {
|
||||
userService.delete(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Request DTOs with Validation
|
||||
|
||||
```java
|
||||
public record UserCreateRequest(
|
||||
@NotBlank(message = "Email is required")
|
||||
@Email(message = "Email must be valid")
|
||||
String email,
|
||||
|
||||
@NotBlank(message = "Password is required")
|
||||
@Size(min = 8, max = 100, message = "Password must be 8-100 characters")
|
||||
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).*$",
|
||||
message = "Password must contain uppercase, lowercase, and digit")
|
||||
String password,
|
||||
|
||||
@NotBlank(message = "Username is required")
|
||||
@Size(min = 3, max = 50)
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username must be alphanumeric")
|
||||
String username,
|
||||
|
||||
@Min(value = 18, message = "Must be at least 18")
|
||||
@Max(value = 120, message = "Must be at most 120")
|
||||
Integer age
|
||||
) {}
|
||||
|
||||
public record UserUpdateRequest(
|
||||
@Email(message = "Email must be valid")
|
||||
String email,
|
||||
|
||||
@Size(min = 3, max = 50)
|
||||
String username
|
||||
) {}
|
||||
```
|
||||
|
||||
## Response DTOs
|
||||
|
||||
```java
|
||||
public record UserResponse(
|
||||
Long id,
|
||||
String email,
|
||||
String username,
|
||||
Integer age,
|
||||
Boolean active,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
public static UserResponse from(User user) {
|
||||
return new UserResponse(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getUsername(),
|
||||
user.getAge(),
|
||||
user.getActive(),
|
||||
user.getCreatedAt(),
|
||||
user.getUpdatedAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Global Exception Handling
|
||||
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ResourceNotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleNotFound(
|
||||
ResourceNotFoundException ex, WebRequest request) {
|
||||
log.error("Resource not found: {}", ex.getMessage());
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.NOT_FOUND.value(),
|
||||
ex.getMessage(),
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ValidationErrorResponse> handleValidation(
|
||||
MethodArgumentNotValidException ex) {
|
||||
Map<String, String> errors = ex.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
FieldError::getField,
|
||||
error -> error.getDefaultMessage() != null
|
||||
? error.getDefaultMessage()
|
||||
: "Invalid value"
|
||||
));
|
||||
|
||||
ValidationErrorResponse response = new ValidationErrorResponse(
|
||||
HttpStatus.BAD_REQUEST.value(),
|
||||
"Validation failed",
|
||||
errors,
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||
public ResponseEntity<ErrorResponse> handleDataIntegrity(
|
||||
DataIntegrityViolationException ex, WebRequest request) {
|
||||
log.error("Data integrity violation", ex);
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.CONFLICT.value(),
|
||||
"Data integrity violation - resource may already exist",
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleGlobalException(
|
||||
Exception ex, WebRequest request) {
|
||||
log.error("Unexpected error", ex);
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
||||
"An unexpected error occurred",
|
||||
request.getDescription(false),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
record ErrorResponse(
|
||||
int status,
|
||||
String message,
|
||||
String path,
|
||||
LocalDateTime timestamp
|
||||
) {}
|
||||
|
||||
record ValidationErrorResponse(
|
||||
int status,
|
||||
String message,
|
||||
Map<String, String> errors,
|
||||
LocalDateTime timestamp
|
||||
) {}
|
||||
```
|
||||
|
||||
## Custom Validation
|
||||
|
||||
```java
|
||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = UniqueEmailValidator.class)
|
||||
public @interface UniqueEmail {
|
||||
String message() default "Email already exists";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public boolean isValid(String email, ConstraintValidatorContext context) {
|
||||
if (email == null) return true;
|
||||
return !userRepository.existsByEmail(email);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## WebClient for External APIs
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebClientConfig {
|
||||
@Bean
|
||||
public WebClient webClient(WebClient.Builder builder) {
|
||||
return builder
|
||||
.baseUrl("https://api.example.com")
|
||||
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.filter(logRequest())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ExchangeFilterFunction logRequest() {
|
||||
return ExchangeFilterFunction.ofRequestProcessor(request -> {
|
||||
log.info("Request: {} {}", request.method(), request.url());
|
||||
return Mono.just(request);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExternalApiService {
|
||||
private final WebClient webClient;
|
||||
|
||||
public Mono<ExternalDataResponse> fetchData(String id) {
|
||||
return webClient
|
||||
.get()
|
||||
.uri("/data/{id}", id)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, response ->
|
||||
Mono.error(new ResourceNotFoundException("External resource not found")))
|
||||
.onStatus(HttpStatusCode::is5xxServerError, response ->
|
||||
Mono.error(new ServiceUnavailableException("External service unavailable")))
|
||||
.bodyToMono(ExternalDataResponse.class)
|
||||
.timeout(Duration.ofSeconds(5))
|
||||
.retry(3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://localhost:3000", "https://example.com")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Annotation | Purpose |
|
||||
|------------|---------|
|
||||
| `@RestController` | Marks class as REST controller (combines @Controller + @ResponseBody) |
|
||||
| `@RequestMapping` | Maps HTTP requests to handler methods |
|
||||
| `@GetMapping/@PostMapping` | HTTP method-specific mappings |
|
||||
| `@PathVariable` | Extracts values from URI path |
|
||||
| `@RequestParam` | Extracts query parameters |
|
||||
| `@RequestBody` | Binds request body to method parameter |
|
||||
| `@Valid` | Triggers validation on request body |
|
||||
| `@RestControllerAdvice` | Global exception handling for REST controllers |
|
||||
| `@ResponseStatus` | Sets HTTP status code for method |
|
||||
@@ -1,63 +0,0 @@
|
||||
-- 为现有的consultation_request表添加缺失的字段
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查并添加缺失的字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_name VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_name IS '确认会诊的医生姓名';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_department_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_department_name VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_department_name IS '代表科室';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_participation') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_participation VARCHAR(100);
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_participation IS '会诊确认参加医师';
|
||||
END IF;
|
||||
|
||||
-- 检查并更新consultation_status字段的注释
|
||||
DROP COMMENT ON COLUMN consultation_request.consultation_status;
|
||||
COMMENT ON COLUMN consultation_request.consultation_status IS '会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消';
|
||||
|
||||
-- 检查并更新consultation_urgency字段的默认值
|
||||
ALTER TABLE consultation_request ALTER COLUMN consultation_urgency SET DEFAULT '一般';
|
||||
|
||||
-- 检查并更新字段注释
|
||||
COMMENT ON COLUMN consultation_request.consultation_id IS '会诊申请单号:CS+年月日时分秒+4位随机数';
|
||||
COMMENT ON COLUMN consultation_request.consultation_request_date IS '会诊申请时间';
|
||||
COMMENT ON COLUMN consultation_request.consultation_date IS '会诊时间';
|
||||
COMMENT ON COLUMN consultation_request.consultation_purpose IS '简要病史及会诊目的';
|
||||
COMMENT ON COLUMN consultation_request.consultation_opinion IS '会诊意见';
|
||||
COMMENT ON COLUMN consultation_request.consultation_status IS '会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消';
|
||||
COMMENT ON COLUMN consultation_request.consultation_urgency IS '是否紧急:一般/紧急';
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician IS '提交会诊的医生姓名';
|
||||
COMMENT ON COLUMN consultation_request.confirming_physician_id IS '提交会诊的医生ID';
|
||||
COMMENT ON COLUMN consultation_request.confirming_date IS '提交会诊日期';
|
||||
COMMENT ON COLUMN consultation_request.signature IS '结束会诊医生姓名/签名医生';
|
||||
COMMENT ON COLUMN consultation_request.signature_physician_id IS '结束会诊医生ID';
|
||||
COMMENT ON COLUMN consultation_request.signature_date IS '结束会诊日期/签名时间';
|
||||
COMMENT ON COLUMN consultation_request.cancel_nature_date IS '作废会诊日期';
|
||||
COMMENT ON COLUMN consultation_request.cancel_reason IS '作废原因';
|
||||
COMMENT ON COLUMN consultation_request.consultation_activity_id IS '会诊项目ID,关联wor_activity_definition表,用于确定会诊类型和价格';
|
||||
COMMENT ON COLUMN consultation_request.consultation_activity_name IS '会诊项目名称,如:院内会诊、远程会诊等';
|
||||
|
||||
END $$;
|
||||
|
||||
-- 为现有表添加必要的索引
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查并创建索引
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_status_date') THEN
|
||||
CREATE INDEX idx_consultation_request_status_date ON consultation_request (consultation_status, consultation_request_date);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_physician') THEN
|
||||
CREATE INDEX idx_consultation_request_physician ON consultation_request (requesting_physician_id);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'consultation_request' AND indexname = 'idx_consultation_request_invited_object') THEN
|
||||
CREATE INDEX idx_consultation_request_invited_object ON consultation_request (invited_object);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,109 +0,0 @@
|
||||
-- 会诊申请表
|
||||
CREATE TABLE IF NOT EXISTS consultation_request (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id VARCHAR(50) NOT NULL, -- 患者ID
|
||||
patient_name VARCHAR(100), -- 患者姓名
|
||||
gender VARCHAR(10), -- 患者性别
|
||||
age INTEGER, -- 患者年龄
|
||||
visit_id BIGINT, -- 门诊就诊流水号
|
||||
order_id BIGINT, -- 门诊医嘱表主键
|
||||
department_id BIGINT, -- 申请科室ID
|
||||
department_name VARCHAR(100), -- 申请科室名称
|
||||
attending_doctor_id BIGINT, -- 主管医生ID
|
||||
attending_doctor_name VARCHAR(100), -- 主管医生姓名
|
||||
consultation_purpose TEXT, -- 会诊目的
|
||||
provisional_diagnosis TEXT, -- 门诊诊断
|
||||
current_condition TEXT, -- 目前病情
|
||||
consultation_department_ids TEXT, -- 申请会诊科室ID列表,逗号分隔
|
||||
consultation_doctor_ids TEXT, -- 申请会诊医生ID列表,逗号分隔
|
||||
consultation_type INTEGER DEFAULT 1, -- 会诊类型:1-普通会诊,2-紧急会诊,3-多学科会诊
|
||||
priority_level INTEGER DEFAULT 1, -- 优先级:1-低,2-中,3-高
|
||||
consultation_status INTEGER DEFAULT 0, -- 会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician VARCHAR(100), -- 提交会诊的医生
|
||||
confirming_physician_id BIGINT, -- 提交会诊的医生ID
|
||||
confirming_date TIMESTAMP, -- 提交会诊时间
|
||||
consultation_urgency VARCHAR(20) DEFAULT '一般', -- 是否紧急
|
||||
application_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 申请日期
|
||||
consultation_request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 会诊申请时间
|
||||
confirmed_date TIMESTAMP, -- 确认日期
|
||||
scheduled_date TIMESTAMP, -- 计划会诊日期
|
||||
actual_date TIMESTAMP, -- 实际会诊日期
|
||||
location VARCHAR(200), -- 会诊地点
|
||||
confirming_physician_name VARCHAR(100), -- 确认会诊的医生姓名
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
confirming_department_name VARCHAR(100), -- 代表科室
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
creator_id BIGINT, -- 创建人ID
|
||||
creator_name VARCHAR(100), -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1 -- 租户ID
|
||||
);
|
||||
|
||||
-- 会诊确认表
|
||||
CREATE TABLE IF NOT EXISTS consultation_confirmation (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT NOT NULL, -- 会诊申请ID
|
||||
confirming_department_id BIGINT NOT NULL, -- 确认科室ID
|
||||
confirming_department_name VARCHAR(100) NOT NULL, -- 确认科室名称
|
||||
confirming_doctor_id BIGINT NOT NULL, -- 确认医生ID
|
||||
confirming_doctor_name VARCHAR(100) NOT NULL, -- 确认医生姓名
|
||||
confirmation_status INTEGER DEFAULT 1, -- 确认状态:1-待确认,2-同意,3-拒绝
|
||||
confirmation_reason TEXT, -- 确认/拒绝理由
|
||||
confirmation_date TIMESTAMP, -- 确认日期
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
confirming_physician_name VARCHAR(100), -- 所属医生
|
||||
confirming_department_name_field VARCHAR(100), -- 代表科室
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1, -- 租户ID
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id)
|
||||
);
|
||||
|
||||
-- 会诊记录表
|
||||
CREATE TABLE IF NOT EXISTS consultation_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT NOT NULL, -- 会诊申请ID
|
||||
participant_doctor_id BIGINT NOT NULL, -- 参与医生ID
|
||||
participant_doctor_name VARCHAR(100) NOT NULL, -- 参与医生姓名
|
||||
participant_department_id BIGINT NOT NULL, -- 参与科室ID
|
||||
participant_department_name VARCHAR(100) NOT NULL, -- 参与科室名称
|
||||
opinion TEXT, -- 会诊意见
|
||||
suggestion TEXT, -- 会诊建议
|
||||
record_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 记录日期
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1, -- 租户ID
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_patient_id ON consultation_request(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_dept_id ON consultation_request(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_status ON consultation_request(consultation_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_date ON consultation_request(application_date);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_req_id ON consultation_confirmation(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_doctor_id ON consultation_confirmation(confirming_doctor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_status ON consultation_confirmation(confirmation_status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_req_id ON consultation_record(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_doctor_id ON consultation_record(participant_doctor_id);
|
||||
@@ -1,135 +0,0 @@
|
||||
-- 会诊申请表
|
||||
CREATE TABLE IF NOT EXISTS consultation_request (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id VARCHAR(50) NOT NULL, -- 患者ID
|
||||
patient_name VARCHAR(100), -- 患者姓名
|
||||
gender VARCHAR(10), -- 患者性别
|
||||
age INTEGER, -- 患者年龄
|
||||
visit_id BIGINT, -- 门诊就诊流水号
|
||||
order_id BIGINT, -- 门诊医嘱表主键
|
||||
department_id BIGINT, -- 申请科室ID
|
||||
department_name VARCHAR(100), -- 申请科室名称
|
||||
attending_doctor_id BIGINT, -- 主管医生ID
|
||||
attending_doctor_name VARCHAR(100), -- 主管医生姓名
|
||||
consultation_purpose TEXT, -- 会诊目的
|
||||
provisional_diagnosis TEXT, -- 门诊诊断
|
||||
current_condition TEXT, -- 目前病情
|
||||
consultation_department_ids TEXT, -- 申请会诊科室ID列表,逗号分隔
|
||||
consultation_doctor_ids TEXT, -- 申请会诊医生ID列表,逗号分隔
|
||||
consultation_type INTEGER DEFAULT 1, -- 会诊类型:1-普通会诊,2-紧急会诊,3-多学科会诊
|
||||
priority_level INTEGER DEFAULT 1, -- 优先级:1-低,2-中,3-高
|
||||
consultation_status INTEGER DEFAULT 0, -- 会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician VARCHAR(100), -- 提交会诊的医生
|
||||
confirming_physician_id BIGINT, -- 提交会诊的医生ID
|
||||
confirming_date TIMESTAMP, -- 提交会诊时间
|
||||
consultation_urgency VARCHAR(20) DEFAULT '一般', -- 是否紧急
|
||||
application_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 申请日期
|
||||
consultation_request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 会诊申请时间
|
||||
confirmed_date TIMESTAMP, -- 确认日期
|
||||
scheduled_date TIMESTAMP, -- 计划会诊日期
|
||||
actual_date TIMESTAMP, -- 实际会诊日期
|
||||
location VARCHAR(200), -- 会诊地点
|
||||
confirming_physician_name VARCHAR(100), -- 确认会诊的医生姓名
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
confirming_department_name VARCHAR(100), -- 代表科室
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
creator_id BIGINT, -- 创建人ID
|
||||
creator_name VARCHAR(100), -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1 -- 租户ID
|
||||
);
|
||||
|
||||
-- 会诊确认表
|
||||
CREATE TABLE IF NOT EXISTS consultation_confirmation (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT, -- 会诊申请ID
|
||||
confirming_department_id BIGINT NOT NULL, -- 确认科室ID
|
||||
confirming_department_name VARCHAR(100) NOT NULL, -- 确认科室名称
|
||||
confirming_doctor_id BIGINT NOT NULL, -- 确认医生ID
|
||||
confirming_doctor_name VARCHAR(100) NOT NULL, -- 确认医生姓名
|
||||
confirmation_status INTEGER DEFAULT 1, -- 确认状态:1-待确认,2-同意,3-拒绝
|
||||
confirmation_reason TEXT, -- 确认/拒绝理由
|
||||
confirmation_date TIMESTAMP, -- 确认日期
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
confirming_physician_name VARCHAR(100), -- 所属医生
|
||||
confirming_department_name_field VARCHAR(100), -- 代表科室
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1 -- 租户ID
|
||||
);
|
||||
|
||||
-- 会诊记录表
|
||||
CREATE TABLE IF NOT EXISTS consultation_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT, -- 会诊申请ID
|
||||
participant_doctor_id BIGINT NOT NULL, -- 参与医生ID
|
||||
participant_doctor_name VARCHAR(100) NOT NULL, -- 参与医生姓名
|
||||
participant_department_id BIGINT NOT NULL, -- 参与科室ID
|
||||
participant_department_name VARCHAR(100) NOT NULL, -- 参与科室名称
|
||||
opinion TEXT, -- 会诊意见
|
||||
suggestion TEXT, -- 会诊建议
|
||||
record_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 记录日期
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1 -- 租户ID
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_patient_id ON consultation_request(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_dept_id ON consultation_request(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_status ON consultation_request(consultation_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_date ON consultation_request(application_date);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_req_id ON consultation_confirmation(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_doctor_id ON consultation_confirmation(confirming_doctor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_status ON consultation_confirmation(confirmation_status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_req_id ON consultation_record(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_doctor_id ON consultation_record(participant_doctor_id);
|
||||
|
||||
-- 添加外键约束
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 为consultation_confirmation表添加外键约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_consultation_confirmation_request'
|
||||
AND table_name = 'consultation_confirmation'
|
||||
) THEN
|
||||
ALTER TABLE consultation_confirmation
|
||||
ADD CONSTRAINT fk_consultation_confirmation_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id);
|
||||
END IF;
|
||||
|
||||
-- 为consultation_record表添加外键约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_consultation_record_request'
|
||||
AND table_name = 'consultation_record'
|
||||
) THEN
|
||||
ALTER TABLE consultation_record
|
||||
ADD CONSTRAINT fk_consultation_record_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,141 +0,0 @@
|
||||
-- 会诊申请表
|
||||
CREATE TABLE IF NOT EXISTS consultation_request (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
patient_id VARCHAR(50) NOT NULL, -- 患者ID
|
||||
patient_name VARCHAR(100), -- 患者姓名
|
||||
gender VARCHAR(10), -- 患者性别
|
||||
age INTEGER, -- 患者年龄
|
||||
visit_id BIGINT, -- 门诊就诊流水号
|
||||
order_id BIGINT, -- 门诊医嘱表主键
|
||||
department_id BIGINT, -- 申请科室ID
|
||||
department_name VARCHAR(100), -- 申请科室名称
|
||||
attending_doctor_id BIGINT, -- 主管医生ID
|
||||
attending_doctor_name VARCHAR(100), -- 主管医生姓名
|
||||
consultation_purpose TEXT, -- 会诊目的
|
||||
provisional_diagnosis TEXT, -- 门诊诊断
|
||||
current_condition TEXT, -- 目前病情
|
||||
consultation_department_ids TEXT, -- 申请会诊科室ID列表,逗号分隔
|
||||
consultation_doctor_ids TEXT, -- 申请会诊医生ID列表,逗号分隔
|
||||
consultation_type INTEGER DEFAULT 1, -- 会诊类型:1-普通会诊,2-紧急会诊,3-多学科会诊
|
||||
priority_level INTEGER DEFAULT 1, -- 优先级:1-低,2-中,3-高
|
||||
consultation_status INTEGER DEFAULT 0, -- 会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician VARCHAR(100), -- 提交会诊的医生
|
||||
confirming_physician_id BIGINT, -- 提交会诊的医生ID
|
||||
confirming_date TIMESTAMP, -- 提交会诊时间
|
||||
consultation_urgency VARCHAR(20) DEFAULT '一般', -- 是否紧急
|
||||
application_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 申请日期
|
||||
consultation_request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 会诊申请时间
|
||||
confirmed_date TIMESTAMP, -- 确认日期
|
||||
scheduled_date TIMESTAMP, -- 计划会诊日期
|
||||
actual_date TIMESTAMP, -- 实际会诊日期
|
||||
location VARCHAR(200), -- 会诊地点
|
||||
confirming_physician_name VARCHAR(100), -- 确认会诊的医生姓名
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
confirming_department_name VARCHAR(100), -- 代表科室
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
creator_id BIGINT, -- 创建人ID
|
||||
creator_name VARCHAR(100), -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1 -- 租户ID
|
||||
);
|
||||
|
||||
-- 会诊确认表
|
||||
CREATE TABLE IF NOT EXISTS consultation_confirmation (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT NOT NULL, -- 会诊申请ID
|
||||
confirming_department_id BIGINT NOT NULL, -- 确认科室ID
|
||||
confirming_department_name VARCHAR(100) NOT NULL, -- 确认科室名称
|
||||
confirming_doctor_id BIGINT NOT NULL, -- 确认医生ID
|
||||
confirming_doctor_name VARCHAR(100) NOT NULL, -- 确认医生姓名
|
||||
confirmation_status INTEGER DEFAULT 1, -- 确认状态:1-待确认,2-同意,3-拒绝
|
||||
confirmation_reason TEXT, -- 确认/拒绝理由
|
||||
confirmation_date TIMESTAMP, -- 确认日期
|
||||
consultation_opinion TEXT, -- 会诊意见
|
||||
confirming_physician_participation VARCHAR(100), -- 会诊确认参加医师
|
||||
confirming_physician_name VARCHAR(100), -- 所属医生
|
||||
confirming_department_name_field VARCHAR(100), -- 代表科室
|
||||
signature VARCHAR(100), -- 签名医生
|
||||
signature_date TIMESTAMP, -- 签名时间
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1, -- 租户ID
|
||||
-- 注意:外键约束将在所有表创建后再添加
|
||||
CONSTRAINT fk_consultation_confirmation_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id)
|
||||
);
|
||||
|
||||
-- 会诊记录表
|
||||
CREATE TABLE IF NOT EXISTS consultation_record (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
consultation_request_id BIGINT NOT NULL, -- 会诊申请ID
|
||||
participant_doctor_id BIGINT NOT NULL, -- 参与医生ID
|
||||
participant_doctor_name VARCHAR(100) NOT NULL, -- 参与医生姓名
|
||||
participant_department_id BIGINT NOT NULL, -- 参与科室ID
|
||||
participant_department_name VARCHAR(100) NOT NULL, -- 参与科室名称
|
||||
opinion TEXT, -- 会诊意见
|
||||
suggestion TEXT, -- 会诊建议
|
||||
record_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 记录日期
|
||||
creator_id BIGINT NOT NULL, -- 创建人ID
|
||||
creator_name VARCHAR(100) NOT NULL, -- 创建人姓名
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater_id BIGINT, -- 更新人ID
|
||||
updater_name VARCHAR(100), -- 更新人姓名
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
valid_flag INTEGER DEFAULT 1, -- 有效标志:1-有效,0-无效
|
||||
tenant_id INTEGER DEFAULT 1, -- 租户ID
|
||||
-- 注意:外键约束将在所有表创建后再添加
|
||||
CONSTRAINT fk_consultation_record_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_patient_id ON consultation_request(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_dept_id ON consultation_request(department_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_status ON consultation_request(consultation_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_request_date ON consultation_request(application_date);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_req_id ON consultation_confirmation(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_doctor_id ON consultation_confirmation(confirming_doctor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_confirmation_status ON consultation_confirmation(confirmation_status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_req_id ON consultation_record(consultation_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_consultation_record_doctor_id ON consultation_record(participant_doctor_id);
|
||||
|
||||
-- 添加外键约束(如果尚未添加)
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 为consultation_confirmation表添加外键约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_consultation_confirmation_request'
|
||||
AND table_name = 'consultation_confirmation'
|
||||
) THEN
|
||||
ALTER TABLE consultation_confirmation
|
||||
ADD CONSTRAINT fk_consultation_confirmation_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id);
|
||||
END IF;
|
||||
|
||||
-- 为consultation_record表添加外键约束
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_consultation_record_request'
|
||||
AND table_name = 'consultation_record'
|
||||
) THEN
|
||||
ALTER TABLE consultation_record
|
||||
ADD CONSTRAINT fk_consultation_record_request
|
||||
FOREIGN KEY (consultation_request_id) REFERENCES consultation_request(id);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -1,70 +0,0 @@
|
||||
-- 插入会诊管理模块的菜单项
|
||||
-- 首先插入主菜单项
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊管理', 0, 10, 'consultationmanagement', '', '', 'ConsultationManagement', 1, 0, 'M', '0', '0', '', 'operation', 'admin', NOW(), 'admin', NOW(), '会诊管理菜单');
|
||||
|
||||
-- 获取刚插入的菜单ID
|
||||
DO $$
|
||||
DECLARE
|
||||
consultation_menu_id BIGINT;
|
||||
consultation_request_menu_id BIGINT;
|
||||
consultation_confirmation_menu_id BIGINT;
|
||||
consultation_record_menu_id BIGINT;
|
||||
BEGIN
|
||||
-- 获取会诊管理菜单ID
|
||||
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
|
||||
|
||||
-- 插入会诊申请子菜单
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊申请', consultation_menu_id, 1, 'consultationrequest', 'clinicmanagement/consultationrequest/index', '', 'ConsultationRequest', 1, 0, 'C', '0', '0', 'consultation:request:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊申请菜单');
|
||||
|
||||
-- 获取会诊申请菜单ID
|
||||
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' LIMIT 1;
|
||||
|
||||
-- 插入会诊申请按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊申请查询', consultation_request_menu_id, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:request:query', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊申请新增', consultation_request_menu_id, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:request:add', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊申请修改', consultation_request_menu_id, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:request:edit', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊申请删除', consultation_request_menu_id, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:request:remove', '#', 'admin', NOW(), 'admin', NOW(), '');
|
||||
|
||||
-- 插入会诊确认子菜单
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊确认', consultation_menu_id, 2, 'consultationconfirmation', 'clinicmanagement/consultationconfirmation/index', '', 'ConsultationConfirmation', 1, 0, 'C', '0', '0', 'consultation:confirmation:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊确认菜单');
|
||||
|
||||
-- 获取会诊确认菜单ID
|
||||
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' LIMIT 1;
|
||||
|
||||
-- 插入会诊确认按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊确认查询', consultation_confirmation_menu_id, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:confirmation:query', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊确认新增', consultation_confirmation_menu_id, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:confirmation:add', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊确认修改', consultation_confirmation_menu_id, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:confirmation:edit', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊确认删除', consultation_confirmation_menu_id, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:confirmation:remove', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊确认操作', consultation_confirmation_menu_id, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:confirmation:confirm', '#', 'admin', NOW(), 'admin', NOW(), '');
|
||||
|
||||
-- 插入会诊记录子菜单
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊记录', consultation_menu_id, 3, 'consultationrecord', 'clinicmanagement/consultationrecord/index', '', 'ConsultationRecord', 1, 0, 'C', '0', '0', 'consultation:record:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊记录菜单');
|
||||
|
||||
-- 获取会诊记录菜单ID
|
||||
SELECT menu_id INTO consultation_record_menu_id FROM sys_menu WHERE menu_name = '会诊记录' LIMIT 1;
|
||||
|
||||
-- 插入会诊记录按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊记录查询', consultation_record_menu_id, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:record:query', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊记录新增', consultation_record_menu_id, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:record:add', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊记录修改', consultation_record_menu_id, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:record:edit', '#', 'admin', NOW(), 'admin', NOW(), ''),
|
||||
('会诊记录删除', consultation_record_menu_id, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'consultation:record:remove', '#', 'admin', NOW(), 'admin', NOW(), '');
|
||||
END $$;
|
||||
@@ -59,11 +59,6 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
|
||||
return R.fail(400, "备注长度不能超过500个字符");
|
||||
}
|
||||
|
||||
// 检查诊室名称在同卫生机构下是否已存在
|
||||
if (clinicRoomService.existsByOrgNameAndRoomName(clinicRoom.getOrgName(), clinicRoom.getRoomName())) {
|
||||
return R.fail(400, "当前卫生机构下已存在该诊室名称");
|
||||
}
|
||||
|
||||
// 新增诊室
|
||||
int result = clinicRoomService.insertClinicRoom(clinicRoom);
|
||||
if (result > 0) {
|
||||
@@ -98,11 +93,6 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
|
||||
return R.fail(404, "诊室不存在");
|
||||
}
|
||||
|
||||
// 检查诊室名称在同卫生机构下是否已存在(排除当前记录)
|
||||
if (clinicRoomService.existsByOrgNameAndRoomNameExcludeId(clinicRoom.getOrgName(), clinicRoom.getRoomName(), clinicRoom.getId())) {
|
||||
return R.fail(400, "当前卫生机构下已存在该诊室名称");
|
||||
}
|
||||
|
||||
// 更新诊室
|
||||
int result = clinicRoomService.updateClinicRoom(clinicRoom);
|
||||
if (result > 0) {
|
||||
|
||||
@@ -87,6 +87,30 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public R<?> getTodayMySchedule() {
|
||||
// 获取今天的日期
|
||||
LocalDate today = LocalDate.now();
|
||||
DayOfWeek dayOfWeek = today.getDayOfWeek();
|
||||
|
||||
// 将 Java 的 DayOfWeek 转换为字符串表示
|
||||
String weekdayStr = convertDayOfWeekToString(dayOfWeek);
|
||||
|
||||
// 获取当前登录医生的ID(需要从SecurityUtils获取)
|
||||
// 如果没有SecurityUtils,可以从参数传入或使用其他方式获取
|
||||
// Long currentDoctorId = SecurityUtils.getLoginUser().getPractitionerId();
|
||||
|
||||
// 查询当前医生今天的排班
|
||||
LambdaQueryWrapper<DoctorSchedule> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(DoctorSchedule::getWeekday, weekdayStr) // 根据星期几查询
|
||||
// .eq(DoctorSchedule::getDoctorId, currentDoctorId) // 只查询当前医生的排班
|
||||
.eq(DoctorSchedule::getIsStopped, false); // 只查询未停止的排班
|
||||
|
||||
List<DoctorSchedule> list = doctorScheduleService.list(queryWrapper);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 DayOfWeek 转换为字符串表示
|
||||
* @param dayOfWeek DayOfWeek枚举
|
||||
@@ -113,44 +137,6 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> getTodayMySchedule() {
|
||||
try {
|
||||
// 获取当前登录用户信息
|
||||
com.core.common.core.domain.model.LoginUser loginUser = com.core.common.utils.SecurityUtils.getLoginUser();
|
||||
Long doctorId = loginUser.getPractitionerId();
|
||||
|
||||
// 如果没有获取到医生ID,尝试使用用户ID
|
||||
if (doctorId == null || doctorId == 0) {
|
||||
doctorId = loginUser.getUserId();
|
||||
System.out.println("Using userId as doctorId: " + doctorId);
|
||||
} else {
|
||||
System.out.println("Using practitionerId as doctorId: " + doctorId);
|
||||
}
|
||||
|
||||
// 获取今天的日期
|
||||
LocalDate today = LocalDate.now();
|
||||
System.out.println("Querying schedule for date: " + today);
|
||||
|
||||
// 查询当前医生今天的排班信息(从SchedulePool表)
|
||||
LambdaQueryWrapper<SchedulePool> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SchedulePool::getScheduleDate, today) // 根据具体日期查询
|
||||
.eq(SchedulePool::getDoctorId, doctorId) // 根据医生ID查询
|
||||
.eq(SchedulePool::getStatus, 1); // 只查询可用的排班
|
||||
|
||||
List<SchedulePool> list = schedulePoolService.list(queryWrapper);
|
||||
System.out.println("Found " + list.size() + " schedules for doctorId: " + doctorId);
|
||||
|
||||
// 为了支持多种日程类型,我们可以在这里整合来自不同数据源的信息
|
||||
// 目前只返回排班信息,但为以后扩展预留空间
|
||||
return R.ok(list);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error getting today's schedule: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return R.fail("获取排班信息失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) {
|
||||
if (ObjectUtil.isEmpty(doctorSchedule)) {
|
||||
|
||||
@@ -71,39 +71,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 号源列表来自排班表(adm_doctor_schedule),需确保clinical_ticket中存在对应记录
|
||||
Ticket existingTicket = ticketService.selectTicketById(ticketId);
|
||||
if (existingTicket == null) {
|
||||
DoctorSchedule schedule = doctorScheduleMapper.selectById(slotId);
|
||||
if (schedule == null) {
|
||||
return R.fail("排班记录不存在");
|
||||
}
|
||||
Ticket newTicket = new Ticket();
|
||||
newTicket.setId(slotId);
|
||||
newTicket.setDoctor(schedule.getDoctor());
|
||||
newTicket.setDepartment(String.valueOf(schedule.getDeptId()));
|
||||
newTicket.setDoctorId(schedule.getDoctorId());
|
||||
newTicket.setDepartmentId(schedule.getDeptId());
|
||||
String registerItem = schedule.getRegisterItem();
|
||||
if (registerItem != null && registerItem.contains("专家")) {
|
||||
newTicket.setTicketType("expert");
|
||||
} else {
|
||||
newTicket.setTicketType("general");
|
||||
}
|
||||
newTicket.setStatus("unbooked");
|
||||
int totalFee = (schedule.getRegisterFee() != null ? schedule.getRegisterFee() : 0)
|
||||
+ (schedule.getDiagnosisFee() != null ? schedule.getDiagnosisFee() : 0);
|
||||
newTicket.setFee(String.valueOf(totalFee));
|
||||
String timeRange = schedule.getStartTime() + "-" + schedule.getEndTime();
|
||||
newTicket.setTime(LocalDate.now() + " " + timeRange);
|
||||
// 使用MyBatis-Plus的save方法,支持手动设置ID
|
||||
ticketService.save(newTicket);
|
||||
}
|
||||
|
||||
// 4. 执行预约逻辑
|
||||
// 3. 执行原有的预约逻辑
|
||||
int result = ticketService.bookTicket(params);
|
||||
if (result > 0) {
|
||||
// 5. 预约成功后,更新排班表状态
|
||||
// 4. 预约成功后,更新排班表状态
|
||||
DoctorSchedule schedule = new DoctorSchedule();
|
||||
schedule.setId(slotId); // 对应 XML 中的 WHERE id = #{id}
|
||||
schedule.setIsStopped(true); // 设置为已预约
|
||||
@@ -115,12 +86,14 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
||||
if (updateCount > 0) {
|
||||
return R.ok("预约成功并已更新排班状态");
|
||||
} else {
|
||||
// 如果更新失败,可能需要根据业务逻辑决定是否回滚预约
|
||||
return R.ok("预约成功,但排班状态更新失败");
|
||||
}
|
||||
} else {
|
||||
return R.fail("预约失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
return R.fail("系统异常:" + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -89,13 +89,4 @@ public class DoctorScheduleController {
|
||||
return R.ok(doctorScheduleAppService.getTodayDoctorScheduleList());
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取当前登录医生今日排班List
|
||||
*
|
||||
* */
|
||||
@GetMapping("/today-my-schedule")
|
||||
public R<?> getTodayMySchedule() {
|
||||
return doctorScheduleAppService.getTodayMySchedule();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ package com.openhis.web.clinicalmanage.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.core.common.core.domain.model.LoginUser;
|
||||
import com.core.common.utils.SecurityUtils;
|
||||
import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
|
||||
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
|
||||
import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.catalina.User;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -101,29 +98,4 @@ public class SurgicalScheduleController {
|
||||
surgicalScheduleAppService.exportSurgerySchedule(opScheduleDto, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping(value = "/checkPassword")
|
||||
public R<?> checkPassword(@RequestParam String password) {
|
||||
try {
|
||||
//获取当前登录用户信息
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return R.fail("用户未登录");
|
||||
}
|
||||
// 直接使用 SecurityUtils.matchesPassword 进行密码验证
|
||||
boolean isMatch = SecurityUtils.matchesPassword(password, loginUser.getPassword());
|
||||
if (isMatch) {
|
||||
return R.ok(true);
|
||||
} else {
|
||||
return R.fail("密码错误");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return R.fail("密码验证失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
package com.openhis.web.consultation.controller;
|
||||
|
||||
import com.core.common.core.controller.BaseController;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.annotation.Log;
|
||||
import com.core.common.enums.BusinessType;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.openhis.consultation.domain.ConsultationConfirmation;
|
||||
import com.openhis.consultation.service.IConsultationConfirmationService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会诊确认 Controller
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/consultation/confirmation")
|
||||
public class ConsultationConfirmationController extends BaseController {
|
||||
@Autowired
|
||||
private IConsultationConfirmationService consultationConfirmationService;
|
||||
|
||||
/**
|
||||
* 查询会诊确认列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:confirmation:list')")
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(ConsultationConfirmation consultationConfirmation,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
LambdaQueryWrapper<ConsultationConfirmation> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(consultationConfirmation.getConsultationRequestId() != null,
|
||||
ConsultationConfirmation::getConsultationRequestId,
|
||||
consultationConfirmation.getConsultationRequestId())
|
||||
.eq(consultationConfirmation.getConfirmationStatus() != null,
|
||||
ConsultationConfirmation::getConfirmationStatus,
|
||||
consultationConfirmation.getConfirmationStatus())
|
||||
.orderByDesc(ConsultationConfirmation::getCreateTime);
|
||||
|
||||
Page<ConsultationConfirmation> page = new Page<>(pageNum, pageSize);
|
||||
Page<ConsultationConfirmation> result = consultationConfirmationService.page(page, queryWrapper);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会诊确认详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:confirmation:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||
return AjaxResult.success(consultationConfirmationService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会诊确认
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:confirmation:add')")
|
||||
@Log(title = "会诊确认", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody ConsultationConfirmation consultationConfirmation) {
|
||||
consultationConfirmation.setCreatorId(getUserId());
|
||||
consultationConfirmation.setCreatorName(getUsername());
|
||||
return toAjax(consultationConfirmationService.save(consultationConfirmation));
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认会诊(同意或拒绝)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:confirmation:confirm')")
|
||||
@Log(title = "会诊确认", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/confirm")
|
||||
public AjaxResult confirm(@RequestBody ConsultationConfirmation consultationConfirmation) {
|
||||
// 设置确认日期
|
||||
consultationConfirmation.setConfirmationDate(LocalDateTime.now());
|
||||
consultationConfirmation.setUpdaterId(getUserId());
|
||||
consultationConfirmation.setUpdaterName(getUsername());
|
||||
return toAjax(consultationConfirmationService.updateById(consultationConfirmation));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会诊确认
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:confirmation:remove')")
|
||||
@Log(title = "会诊确认", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(consultationConfirmationService.removeByIds(java.util.Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,10 @@ public class ConsultationController {
|
||||
@ApiParam("就诊ID(可选,不传则查询当前医生的所有会诊申请)")
|
||||
@RequestParam(required = false) Long encounterId) {
|
||||
try {
|
||||
log.info("获取会诊列表,encounterId: {}", encounterId);
|
||||
List<ConsultationRequestDto> list = consultationAppService.getConsultationList(encounterId);
|
||||
log.info("获取会诊列表成功,共 {} 条记录", list != null ? list.size() : 0);
|
||||
return R.ok(list);
|
||||
} catch (Exception e) {
|
||||
log.error("获取会诊列表失败,encounterId: {}", encounterId, e);
|
||||
log.error("获取会诊列表失败", e);
|
||||
return R.fail("获取会诊列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.openhis.web.consultation.controller;
|
||||
|
||||
import com.core.common.core.controller.BaseController;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.annotation.Log;
|
||||
import com.core.common.enums.BusinessType;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.openhis.consultation.domain.ConsultationRecord;
|
||||
import com.openhis.consultation.service.IConsultationRecordService;
|
||||
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 his
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/consultation/record")
|
||||
public class ConsultationRecordController extends BaseController {
|
||||
@Autowired
|
||||
private IConsultationRecordService consultationRecordService;
|
||||
|
||||
/**
|
||||
* 查询会诊记录列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:record:list')")
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(ConsultationRecord consultationRecord,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
LambdaQueryWrapper<ConsultationRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(consultationRecord.getConsultationRequestId() != null,
|
||||
ConsultationRecord::getConsultationRequestId,
|
||||
consultationRecord.getConsultationRequestId())
|
||||
.eq(consultationRecord.getParticipantDoctorId() != null,
|
||||
ConsultationRecord::getParticipantDoctorId,
|
||||
consultationRecord.getParticipantDoctorId())
|
||||
.orderByDesc(ConsultationRecord::getRecordDate);
|
||||
|
||||
Page<ConsultationRecord> page = new Page<>(pageNum, pageSize);
|
||||
Page<ConsultationRecord> result = consultationRecordService.page(page, queryWrapper);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会诊记录详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:record:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||
return AjaxResult.success(consultationRecordService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会诊记录
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:record:add')")
|
||||
@Log(title = "会诊记录", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody ConsultationRecord consultationRecord) {
|
||||
consultationRecord.setCreatorId(getUserId());
|
||||
consultationRecord.setCreatorName(getUsername());
|
||||
return toAjax(consultationRecordService.save(consultationRecord));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会诊记录
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:record:edit')")
|
||||
@Log(title = "会诊记录", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody ConsultationRecord consultationRecord) {
|
||||
consultationRecord.setUpdaterId(getUserId());
|
||||
consultationRecord.setUpdaterName(getUsername());
|
||||
return toAjax(consultationRecordService.updateById(consultationRecord));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会诊记录
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:record:remove')")
|
||||
@Log(title = "会诊记录", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(consultationRecordService.removeByIds(java.util.Arrays.asList(ids)));
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package com.openhis.web.consultation.controller;
|
||||
|
||||
import com.core.common.core.controller.BaseController;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.annotation.Log;
|
||||
import com.core.common.enums.BusinessType;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.openhis.consultation.domain.ConsultationRequest;
|
||||
import com.openhis.consultation.service.IConsultationRequestService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会诊申请 Controller
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/consultation/request")
|
||||
public class ConsultationRequestController extends BaseController {
|
||||
@Autowired
|
||||
private IConsultationRequestService consultationRequestService;
|
||||
|
||||
/**
|
||||
* 查询会诊申请列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:list')")
|
||||
@GetMapping("/list")
|
||||
public AjaxResult list(ConsultationRequest consultationRequest,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
LambdaQueryWrapper<ConsultationRequest> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.like(consultationRequest.getPatientName() != null, ConsultationRequest::getPatientName, consultationRequest.getPatientName())
|
||||
.eq(consultationRequest.getConsultationStatus() != null, ConsultationRequest::getConsultationStatus, consultationRequest.getConsultationStatus())
|
||||
.eq(consultationRequest.getConsultationActivityId() != null, ConsultationRequest::getConsultationActivityId, consultationRequest.getConsultationActivityId())
|
||||
.orderByDesc(ConsultationRequest::getConsultationRequestDate);
|
||||
|
||||
Page<ConsultationRequest> page = new Page<>(pageNum, pageSize);
|
||||
Page<ConsultationRequest> result = consultationRequestService.page(page, queryWrapper);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会诊申请详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||
return AjaxResult.success(consultationRequestService.getById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:add')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody ConsultationRequest consultationRequest) {
|
||||
// 自动生成申请单号
|
||||
String consultationId = "CS" + LocalDateTime.now().toString().replaceAll("[^0-9]", "").substring(0, 14) + String.format("%04d", (int)(Math.random() * 10000));
|
||||
consultationRequest.setConsultationId(consultationId);
|
||||
|
||||
consultationRequest.setCreateBy(getUsername());
|
||||
consultationRequest.setConsultationRequestDate(LocalDateTime.now());
|
||||
consultationRequest.setConsultationStatus(0); // 新开状态
|
||||
return toAjax(consultationRequestService.save(consultationRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:edit')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody ConsultationRequest consultationRequest) {
|
||||
consultationRequest.setUpdateBy(getUsername());
|
||||
return toAjax(consultationRequestService.updateById(consultationRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:remove')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(consultationRequestService.removeByIds(java.util.Arrays.asList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:submit')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/submit/{id}")
|
||||
public AjaxResult submit(@PathVariable Long id) {
|
||||
int result = consultationRequestService.submitConsultation(id, getUserId(), getUsername());
|
||||
return result > 0 ? AjaxResult.success("提交成功") : AjaxResult.error("提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消提交会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:cancelSubmit')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/cancelSubmit/{id}")
|
||||
public AjaxResult cancelSubmit(@PathVariable Long id) {
|
||||
int result = consultationRequestService.cancelSubmitConsultation(id);
|
||||
return result > 0 ? AjaxResult.success("取消提交成功") : AjaxResult.error("取消提交失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:end')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/end/{id}")
|
||||
public AjaxResult end(@PathVariable Long id) {
|
||||
int result = consultationRequestService.endConsultation(id, getUserId(), getUsername());
|
||||
return result > 0 ? AjaxResult.success("结束成功") : AjaxResult.error("结束失败,请确保会诊已签名");
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废会诊申请
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:cancel')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/cancel/{id}")
|
||||
public AjaxResult cancel(@PathVariable Long id) {
|
||||
int result = consultationRequestService.cancelConsultation(id, getUserId(), getUsername());
|
||||
return result > 0 ? AjaxResult.success("作废成功") : AjaxResult.error("作废失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认会诊
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:confirm')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/confirm")
|
||||
public AjaxResult confirm(@RequestBody ConsultationRequest request) {
|
||||
int result = consultationRequestService.confirmConsultation(
|
||||
request.getId(),
|
||||
request.getConfirmingPhysicianName(),
|
||||
String.valueOf(request.getConfirmingPhysicianId()),
|
||||
request.getConsultationOpinion()
|
||||
);
|
||||
return result > 0 ? AjaxResult.success("确认成功") : AjaxResult.error("确认失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名会诊
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('consultation:request:sign')")
|
||||
@Log(title = "会诊申请", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/sign")
|
||||
public AjaxResult sign(@RequestBody ConsultationRequest request) {
|
||||
int result = consultationRequestService.signConsultation(
|
||||
request.getId(),
|
||||
request.getSignature(),
|
||||
getUserId(),
|
||||
getUsername()
|
||||
);
|
||||
return result > 0 ? AjaxResult.success("签名成功") : AjaxResult.error("签名失败,请确保会诊已确认");
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.openhis.web.consultation.domain;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.Alias;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
@@ -16,7 +15,6 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@TableName("consultation_request")
|
||||
@Alias("webConsultationRequest")
|
||||
public class ConsultationRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.openhis.consultation.mapper;
|
||||
package com.openhis.web.consultation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.consultation.domain.ConsultationRequest;
|
||||
import com.openhis.web.consultation.domain.ConsultationRequest;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 会诊申请 Mapper接口
|
||||
* 会诊申请Mapper接口
|
||||
*
|
||||
* @author his
|
||||
* @author system
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConsultationRequestMapper extends BaseMapper<ConsultationRequest> {
|
||||
|
||||
}
|
||||
|
||||
@@ -629,16 +629,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
|
||||
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
|
||||
if (adviceSaveDto.getGenerateSourceEnum() != null) {
|
||||
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
|
||||
} else {
|
||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||
}
|
||||
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
|
||||
if (adviceSaveDto.getSourceBillNo() != null) {
|
||||
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
|
||||
}
|
||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
||||
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
||||
@@ -768,16 +759,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
|
||||
// 保存耗材费用项
|
||||
chargeItem = new ChargeItem();
|
||||
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
|
||||
if (adviceSaveDto.getGenerateSourceEnum() != null) {
|
||||
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
|
||||
} else {
|
||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||
}
|
||||
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
|
||||
if (adviceSaveDto.getSourceBillNo() != null) {
|
||||
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
|
||||
}
|
||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
||||
@@ -919,16 +900,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
||||
// 保存时保存诊疗费用项
|
||||
if (is_save) {
|
||||
chargeItem = new ChargeItem();
|
||||
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
|
||||
if (adviceSaveDto.getGenerateSourceEnum() != null) {
|
||||
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
|
||||
} else {
|
||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
||||
}
|
||||
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
|
||||
if (adviceSaveDto.getSourceBillNo() != null) {
|
||||
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
|
||||
}
|
||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
package com.openhis.web.doctorstation.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.openhis.clinical.domain.InfectiousCard;
|
||||
import com.openhis.clinical.service.IInfectiousCardService;
|
||||
import com.core.common.utils.StringUtils;
|
||||
import com.core.common.utils.poi.ExcelUtil;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.page.PageDomain;
|
||||
import com.core.common.core.page.TableDataInfo;
|
||||
import com.core.common.core.page.TableSupport;
|
||||
import com.core.common.utils.PageUtils;
|
||||
import com.core.common.core.controller.BaseController;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.core.common.utils.PageUtils.startPage;
|
||||
|
||||
/**
|
||||
* 传染病报告卡 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/doctor-station/diagnosis")
|
||||
public class InfectiousDiseaseReportController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IInfectiousCardService infectiousCardService;
|
||||
|
||||
/**
|
||||
* 获取下一个卡片编号
|
||||
*/
|
||||
@GetMapping("/next-card-no")
|
||||
public AjaxResult getNextCardNo(@RequestParam String orgCode) {
|
||||
if (StringUtils.isEmpty(orgCode)) {
|
||||
return AjaxResult.error("机构编码不能为空");
|
||||
}
|
||||
try {
|
||||
String nextCardNo = infectiousCardService.generateNextCardNo(orgCode);
|
||||
return AjaxResult.success("获取成功", nextCardNo);
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("获取卡片编号失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存传染病报告卡
|
||||
*/
|
||||
@PostMapping("/save-infectious-disease-report")
|
||||
public AjaxResult saveInfectiousDiseaseReport(@RequestBody InfectiousCard infectiousCard) {
|
||||
// 验证必要参数
|
||||
if (infectiousCard == null) {
|
||||
return AjaxResult.error("请求数据不能为空");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(infectiousCard.getCardNo())) {
|
||||
return AjaxResult.error("卡片编号不能为空");
|
||||
}
|
||||
|
||||
if (infectiousCard.getPatId() == null) {
|
||||
return AjaxResult.error("患者ID不能为空");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(infectiousCard.getDiseaseCode())) {
|
||||
return AjaxResult.error("疾病编码不能为空");
|
||||
}
|
||||
|
||||
// 验证年龄和家长姓名的关系
|
||||
if (infectiousCard.getAge() != null && infectiousCard.getAgeUnit() != null &&
|
||||
infectiousCard.getAgeUnit().equals("1") && infectiousCard.getAge() <= 14) {
|
||||
if (StringUtils.isEmpty(infectiousCard.getParentName())) {
|
||||
return AjaxResult.error("14岁及以下患者必须填写家长姓名");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
boolean result = infectiousCardService.saveInfectiousCard(infectiousCard);
|
||||
if (result) {
|
||||
return AjaxResult.success("传染病报告卡保存成功");
|
||||
} else {
|
||||
return AjaxResult.error("传染病报告卡保存失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("保存过程中发生异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据卡片编号查询传染病报告卡
|
||||
*/
|
||||
@GetMapping("/get-infectious-card/{cardNo}")
|
||||
public AjaxResult getInfectiousCardByCardNo(@PathVariable String cardNo) {
|
||||
if (StringUtils.isEmpty(cardNo)) {
|
||||
return AjaxResult.error("卡片编号不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
InfectiousCard infectiousCard = infectiousCardService.getById(cardNo);
|
||||
if (infectiousCard != null) {
|
||||
return AjaxResult.success(infectiousCard);
|
||||
} else {
|
||||
return AjaxResult.error("未找到对应的传染病报告卡");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("查询过程中发生异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交传染病报告卡(更新状态为已提交)
|
||||
*/
|
||||
@PutMapping("/submit-infectious-card/{cardNo}")
|
||||
public AjaxResult submitInfectiousCard(@PathVariable String cardNo) {
|
||||
if (StringUtils.isEmpty(cardNo)) {
|
||||
return AjaxResult.error("卡片编号不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
InfectiousCard infectiousCard = infectiousCardService.getById(cardNo);
|
||||
if (infectiousCard == null) {
|
||||
return AjaxResult.error("未找到对应的传染病报告卡");
|
||||
}
|
||||
|
||||
// 只有暂存状态的卡片才能提交
|
||||
if (infectiousCard.getStatus() != 0) {
|
||||
return AjaxResult.error("只有暂存状态的卡片才能提交");
|
||||
}
|
||||
|
||||
infectiousCard.setStatus(1); // 设置为已提交状态
|
||||
boolean result = infectiousCardService.updateById(infectiousCard);
|
||||
if (result) {
|
||||
return AjaxResult.success("传染病报告卡提交成功");
|
||||
} else {
|
||||
return AjaxResult.error("传染病报告卡提交失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("提交过程中发生异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询传染病报告卡列表
|
||||
*/
|
||||
@GetMapping("/list-infectious-cards")
|
||||
public TableDataInfo listInfectiousCards(InfectiousCard infectiousCard) {
|
||||
startPage();
|
||||
List<InfectiousCard> list = infectiousCardService.list(new LambdaQueryWrapper<>(infectiousCard));
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出传染病报告卡列表
|
||||
*/
|
||||
@PostMapping("/export-infectious-cards")
|
||||
public AjaxResult exportInfectiousCards(InfectiousCard infectiousCard) {
|
||||
List<InfectiousCard> list = infectiousCardService.list(new LambdaQueryWrapper<>(infectiousCard));
|
||||
ExcelUtil<InfectiousCard> util = new ExcelUtil<>(InfectiousCard.class);
|
||||
return util.exportExcel(list, "传染病报告卡数据");
|
||||
}
|
||||
}
|
||||
@@ -190,15 +190,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
@Override
|
||||
public R<?> prePayment(PrePaymentDto prePaymentDto) {
|
||||
logger.info("预结算:参数:" + JSON.toJSONString(prePaymentDto));
|
||||
// 查收费项(支持手术计费)
|
||||
List<ChargeItem> chargeItemList;
|
||||
if (prePaymentDto.getGenerateSourceEnum() != null && prePaymentDto.getGenerateSourceEnum() == 2) {
|
||||
// 手术计费:根据generateSourceEnum和sourceBillNo过滤
|
||||
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds(), prePaymentDto.getGenerateSourceEnum(), prePaymentDto.getSourceBillNo());
|
||||
} else {
|
||||
// 普通门诊划价
|
||||
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
|
||||
}
|
||||
// 查收费项
|
||||
List<ChargeItem> chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
|
||||
if (chargeItemList.isEmpty()) {
|
||||
return R.fail("未选择收费项");
|
||||
}
|
||||
@@ -1906,13 +1899,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
||||
iAccountService.save(accountZf);
|
||||
}
|
||||
String accountContractNo = outpatientRegistrationAddParam.getAccountFormData().getContractNo();
|
||||
if (!CommonConstants.BusinessName.DEFAULT_CONTRACT_NO
|
||||
.equals(accountContractNo)
|
||||
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
|
||||
&& !CommonConstants.BusinessName.DEFAULT_STUDENT_CONTRACT_NO
|
||||
.equals(accountContractNo)
|
||||
&& accountContractNo.length() > 11
|
||||
&& accountContractNo.startsWith("STUDENT")) {
|
||||
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
|
||||
&& outpatientRegistrationAddParam.getAccountFormData().getContractNo().length() > 11) {
|
||||
// 建立学生自费ACCOUNT
|
||||
Account accountStudentZf = new Account();
|
||||
BeanUtils.copyProperties(accountFormData, accountStudentZf);
|
||||
@@ -1922,9 +1913,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
||||
iAccountService.save(accountStudentZf);
|
||||
|
||||
// 截取医保合同号时,先验证是否以"STUDENT"开头
|
||||
String ybContractNo = accountContractNo.substring("STUDENT".length());
|
||||
Contract contractYb = contractService.getContract(ybContractNo);
|
||||
Contract contractYb = contractService.getContract(
|
||||
outpatientRegistrationAddParam.getAccountFormData().getContractNo().substring("STUDENT".length()));
|
||||
if (contractYb != null && 1 == contractYb.getYbFlag()) {
|
||||
// 建立纯医保ACCOUNT
|
||||
Account accountYb = new Account();
|
||||
@@ -2103,24 +2093,6 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
||||
return chargeItemService.list(new LambdaQueryWrapper<ChargeItem>().in(ChargeItem::getId, chargeItemIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收费项集合(支持手术计费)
|
||||
*
|
||||
* @param chargeItemIds 收费项id集合
|
||||
* @param generateSourceEnum 账单生成来源(2:手术计费)
|
||||
* @param sourceBillNo 来源业务单据号(手术单号)
|
||||
* @return 收费项集合
|
||||
*/
|
||||
private List<ChargeItem> getChargeItems(List<Long> chargeItemIds, Integer generateSourceEnum, String sourceBillNo) {
|
||||
LambdaQueryWrapper<ChargeItem> wrapper = new LambdaQueryWrapper<ChargeItem>().in(ChargeItem::getId, chargeItemIds);
|
||||
// 如果是手术计费,需要额外过滤条件
|
||||
if (generateSourceEnum != null && generateSourceEnum == 2 && sourceBillNo != null) {
|
||||
wrapper.eq(ChargeItem::getGenerateSourceEnum, 2)
|
||||
.eq(ChargeItem::getSourceBillNo, sourceBillNo);
|
||||
}
|
||||
return chargeItemService.list(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型转换:收费项chargeItem转成PaymentedItemModel
|
||||
*
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.openhis.web.ybmanage.controller;
|
||||
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.domain.PageResult;
|
||||
import com.core.common.core.page.TableDataInfo;
|
||||
import com.core.common.core.controller.BaseController;
|
||||
import com.openhis.yb.domain.DayEndMedicalInsuranceSettlement;
|
||||
import com.openhis.yb.service.IDayEndMedicalInsuranceSettlementService;
|
||||
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-02-02
|
||||
*/
|
||||
@Api(tags = "日结医保结算")
|
||||
@RestController
|
||||
@RequestMapping("/ybmanage/dayEndMedicalInsuranceSettlement")
|
||||
public class DayEndMedicalInsuranceSettlementController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IDayEndMedicalInsuranceSettlementService dayEndMedicalInsuranceSettlementService;
|
||||
|
||||
/**
|
||||
* 查询日结医保结算列表
|
||||
*/
|
||||
@ApiOperation("查询日结医保结算列表")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(DayEndMedicalInsuranceSettlement dayEndMedicalInsuranceSettlement) {
|
||||
startPage();
|
||||
List<DayEndMedicalInsuranceSettlement> list = dayEndMedicalInsuranceSettlementService.selectDayEndMedicalInsuranceSettlementList(dayEndMedicalInsuranceSettlement);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询日结医保结算列表
|
||||
*/
|
||||
@ApiOperation("分页查询日结医保结算列表")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:list')")
|
||||
@GetMapping("/page")
|
||||
public PageResult<DayEndMedicalInsuranceSettlement> page(DayEndMedicalInsuranceSettlement dayEndMedicalInsuranceSettlement,
|
||||
@RequestParam(defaultValue = "1") int pageNum,
|
||||
@RequestParam(defaultValue = "10") int pageSize) {
|
||||
return dayEndMedicalInsuranceSettlementService.selectDayEndMedicalInsuranceSettlementPage(dayEndMedicalInsuranceSettlement, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日结医保结算详细信息
|
||||
*/
|
||||
@ApiOperation("获取日结医保结算详细信息")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:query')")
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||
return AjaxResult.success(dayEndMedicalInsuranceSettlementService.selectDayEndMedicalInsuranceSettlementById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增日结医保结算
|
||||
*/
|
||||
@ApiOperation("新增日结医保结算")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:add')")
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody DayEndMedicalInsuranceSettlement dayEndMedicalInsuranceSettlement) {
|
||||
return toAjax(dayEndMedicalInsuranceSettlementService.insertDayEndMedicalInsuranceSettlement(dayEndMedicalInsuranceSettlement));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日结医保结算
|
||||
*/
|
||||
@ApiOperation("修改日结医保结算")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:edit')")
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody DayEndMedicalInsuranceSettlement dayEndMedicalInsuranceSettlement) {
|
||||
return toAjax(dayEndMedicalInsuranceSettlementService.updateDayEndMedicalInsuranceSettlement(dayEndMedicalInsuranceSettlement));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日结医保结算
|
||||
*/
|
||||
@ApiOperation("删除日结医保结算")
|
||||
@PreAuthorize("@ss.hasPermi('ybmanage:dayEndMedicalInsuranceSettlement:remove')")
|
||||
@DeleteMapping("/{ids}")
|
||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||
return toAjax(dayEndMedicalInsuranceSettlementService.deleteDayEndMedicalInsuranceSettlementByIds(ids));
|
||||
}
|
||||
}
|
||||
@@ -91,14 +91,3 @@ server:
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /openhis
|
||||
|
||||
# 开发环境日志配置
|
||||
logging:
|
||||
level:
|
||||
com.openhis: info
|
||||
com.baomidou.mybatisplus: info
|
||||
com.openhis.mapper: info
|
||||
com.openhis.domain: info
|
||||
org.springframework.jdbc.core: info
|
||||
com.alibaba.druid: info
|
||||
com.alibaba.druid.sql: info
|
||||
@@ -32,17 +32,16 @@ server:
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.openhis: debug
|
||||
com.openhis: info
|
||||
org.springframework: warn
|
||||
# MyBatis和MyBatis-Plus日志
|
||||
com.baomidou.mybatisplus: debug
|
||||
com.openhis.mapper: debug
|
||||
com.openhis.domain: debug
|
||||
com.openhis.web.regdoctorstation.mapper: info
|
||||
# JDBC日志
|
||||
org.springframework.jdbc.core: debug
|
||||
org.springframework.jdbc.core: info
|
||||
# Druid SQL日志
|
||||
com.alibaba.druid: debug
|
||||
com.alibaba.druid.sql: debug
|
||||
com.alibaba.druid: info
|
||||
com.alibaba.druid.sql: info
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
@@ -92,9 +91,6 @@ mybatis-plus:
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 加载全局的配置文件
|
||||
configLocation: classpath:mybatis/mybatis-config.xml
|
||||
configuration:
|
||||
# 开启 SQL 日志输出
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: validFlag # 全局逻辑删除的实体字段名
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
cs.apply_dept_id,
|
||||
cs.apply_dept_name,
|
||||
cs.org_id,
|
||||
cs.encounter_id,
|
||||
o.name AS org_name,
|
||||
cs.main_surgeon_name AS surgeon_name
|
||||
FROM op_schedule os
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.openhis.consultation.mapper.ConsultationRequestMapper">
|
||||
<mapper namespace="com.openhis.web.consultation.mapper.ConsultationRequestMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.openhis.consultation.domain.ConsultationRequest">
|
||||
<resultMap id="BaseResultMap" type="com.openhis.web.consultation.domain.ConsultationRequest">
|
||||
<id column="id" property="id" />
|
||||
<result column="consultation_id" property="consultationId" />
|
||||
<result column="patient_id" property="patientId" />
|
||||
@@ -22,6 +22,8 @@
|
||||
<result column="requesting_physician_id" property="requestingPhysicianId" />
|
||||
<result column="consultation_request_date" property="consultationRequestDate" />
|
||||
<result column="invited_object" property="invitedObject" />
|
||||
<result column="invited_department_id" property="invitedDepartmentId" />
|
||||
<result column="invited_physician_id" property="invitedPhysicianId" />
|
||||
<result column="consultation_date" property="consultationDate" />
|
||||
<result column="consultation_purpose" property="consultationPurpose" />
|
||||
<result column="provisional_diagnosis" property="provisionalDiagnosis" />
|
||||
@@ -43,11 +45,6 @@
|
||||
<result column="tenant_id" property="tenantId" />
|
||||
<result column="is_deleted" property="isDeleted" />
|
||||
<result column="remark" property="remark" />
|
||||
<result column="consultation_activity_id" property="consultationActivityId" />
|
||||
<result column="consultation_activity_name" property="consultationActivityName" />
|
||||
<result column="confirming_physician_name" property="confirmingPhysicianName" />
|
||||
<result column="confirming_department_name" property="confirmingDepartmentName" />
|
||||
<result column="confirming_physician_participation" property="confirmingPhysicianParticipation" />
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
|
||||
outagedetection=true
|
||||
outagedetectioninterval=2000
|
||||
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
|
||||
@@ -39,12 +39,7 @@ public enum GenerateSource implements HisEnumInterface {
|
||||
/**
|
||||
* 自动滚费
|
||||
*/
|
||||
AUTO_ROLL_FEES(5, "5", "自动滚费"),
|
||||
|
||||
/**
|
||||
* 手术计费
|
||||
*/
|
||||
SURGERY_BILLING(6, "6", "手术计费");
|
||||
AUTO_ROLL_FEES(5, "5", "自动滚费");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
@@ -244,9 +244,4 @@ public class ChargeItem extends HisBaseEntity {
|
||||
*/
|
||||
private BigDecimal manualAdjustedPrice;
|
||||
|
||||
/**
|
||||
* 来源业务单据号(如手术申请单号)
|
||||
*/
|
||||
private String sourceBillNo;
|
||||
|
||||
}
|
||||
|
||||
@@ -51,23 +51,4 @@ public interface IClinicRoomService extends IService<ClinicRoom> {
|
||||
* @return 结果
|
||||
*/
|
||||
int deleteClinicRoomById(Long id);
|
||||
|
||||
/**
|
||||
* 检查指定卫生机构下是否已存在相同诊室名称(新增时使用)
|
||||
*
|
||||
* @param orgName 卫生机构名称
|
||||
* @param roomName 诊室名称
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByOrgNameAndRoomName(String orgName, String roomName);
|
||||
|
||||
/**
|
||||
* 检查指定卫生机构下是否已存在相同诊室名称(编辑时使用,排除当前记录)
|
||||
*
|
||||
* @param orgName 卫生机构名称
|
||||
* @param roomName 诊室名称
|
||||
* @param id 当前记录ID
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean existsByOrgNameAndRoomNameExcludeId(String orgName, String roomName, Long id);
|
||||
}
|
||||
|
||||
@@ -44,21 +44,4 @@ public class ClinicRoomServiceImpl extends ServiceImpl<ClinicRoomMapper, ClinicR
|
||||
public int deleteClinicRoomById(Long id) {
|
||||
return baseMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByOrgNameAndRoomName(String orgName, String roomName) {
|
||||
LambdaQueryWrapper<ClinicRoom> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ClinicRoom::getOrgName, orgName)
|
||||
.eq(ClinicRoom::getRoomName, roomName);
|
||||
return count(queryWrapper) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByOrgNameAndRoomNameExcludeId(String orgName, String roomName, Long id) {
|
||||
LambdaQueryWrapper<ClinicRoom> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ClinicRoom::getOrgName, orgName)
|
||||
.eq(ClinicRoom::getRoomName, roomName)
|
||||
.ne(ClinicRoom::getId, id);
|
||||
return count(queryWrapper) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,378 +0,0 @@
|
||||
package com.openhis.clinical.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 传染病报卡表
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "infectious_card")
|
||||
public class InfectiousCard implements Serializable {
|
||||
|
||||
/**
|
||||
* 卡片编号:机构代码+年月日+4位流水
|
||||
*/
|
||||
@TableId(value = "card_no")
|
||||
private String cardNo;
|
||||
|
||||
/**
|
||||
* 本次就诊ID (adm_encounter.id)
|
||||
*/
|
||||
@TableField(value = "visit_id")
|
||||
private Long visitId;
|
||||
|
||||
/**
|
||||
* 诊断记录唯一ID (adm_encounter_diagnosis.condition_id)
|
||||
*/
|
||||
@TableField(value = "diag_id")
|
||||
private Long diagId;
|
||||
|
||||
/**
|
||||
* 患者ID
|
||||
*/
|
||||
@TableField(value = "pat_id")
|
||||
private Long patId;
|
||||
|
||||
/**
|
||||
* 证件类型
|
||||
*/
|
||||
@TableField(value = "id_type")
|
||||
private Integer idType;
|
||||
|
||||
/**
|
||||
* 证件号码:18位校验
|
||||
*/
|
||||
@TableField(value = "id_no")
|
||||
private String idNo;
|
||||
|
||||
/**
|
||||
* 患者姓名
|
||||
*/
|
||||
@TableField(value = "pat_name")
|
||||
private String patName;
|
||||
|
||||
/**
|
||||
* 家长姓名:≤14岁必填
|
||||
*/
|
||||
@TableField(value = "parent_name")
|
||||
private String parentName;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
@TableField(value = "sex")
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
@TableField(value = "birthday")
|
||||
private LocalDate birthday;
|
||||
|
||||
/**
|
||||
* 年龄
|
||||
*/
|
||||
@TableField(value = "age")
|
||||
private Integer age;
|
||||
|
||||
/**
|
||||
* 年龄单位:1岁/2月/3天
|
||||
*/
|
||||
@TableField(value = "age_unit")
|
||||
private String ageUnit;
|
||||
|
||||
/**
|
||||
* 工作单位
|
||||
*/
|
||||
@TableField(value = "workplace")
|
||||
private String workplace;
|
||||
|
||||
/**
|
||||
* 电话
|
||||
*/
|
||||
@TableField(value = "phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@TableField(value = "contact_phone")
|
||||
private String contactPhone;
|
||||
|
||||
/**
|
||||
* 地址-省
|
||||
*/
|
||||
@TableField(value = "address_prov")
|
||||
private String addressProv;
|
||||
|
||||
/**
|
||||
* 地址-市
|
||||
*/
|
||||
@TableField(value = "address_city")
|
||||
private String addressCity;
|
||||
|
||||
/**
|
||||
* 地址-县/区
|
||||
*/
|
||||
@TableField(value = "address_county")
|
||||
private String addressCounty;
|
||||
|
||||
/**
|
||||
* 地址-乡镇/街道
|
||||
*/
|
||||
@TableField(value = "address_town")
|
||||
private String addressTown;
|
||||
|
||||
/**
|
||||
* 地址-村/居委会
|
||||
*/
|
||||
@TableField(value = "address_village")
|
||||
private String addressVillage;
|
||||
|
||||
/**
|
||||
* 地址-门牌号
|
||||
*/
|
||||
@TableField(value = "address_house")
|
||||
private String addressHouse;
|
||||
|
||||
/**
|
||||
* 患者归属:1本县区/2本市/3本省/4外省/5港澳台/6外籍
|
||||
*/
|
||||
@TableField(value = "patient_belong")
|
||||
private Integer patientBelong;
|
||||
|
||||
/**
|
||||
* 职业
|
||||
*/
|
||||
@TableField(value = "occupation")
|
||||
private String occupation;
|
||||
|
||||
/**
|
||||
* 疾病编码
|
||||
*/
|
||||
@TableField(value = "disease_code")
|
||||
private String diseaseCode;
|
||||
|
||||
/**
|
||||
* 疾病类型
|
||||
*/
|
||||
@TableField(value = "disease_type")
|
||||
private String diseaseType;
|
||||
|
||||
/**
|
||||
* 其他疾病
|
||||
*/
|
||||
@TableField(value = "other_disease")
|
||||
private String otherDisease;
|
||||
|
||||
/**
|
||||
* 病例分类:1疑似/2临床诊断/3确诊/4病原携带/5阳性
|
||||
*/
|
||||
@TableField(value = "case_class")
|
||||
private Integer caseClass;
|
||||
|
||||
/**
|
||||
* 发病日期
|
||||
*/
|
||||
@TableField(value = "onset_date")
|
||||
private LocalDate onsetDate;
|
||||
|
||||
/**
|
||||
* 诊断日期
|
||||
*/
|
||||
@TableField(value = "diag_date")
|
||||
private LocalDateTime diagDate;
|
||||
|
||||
/**
|
||||
* 死亡日期
|
||||
*/
|
||||
@TableField(value = "death_date")
|
||||
private LocalDate deathDate;
|
||||
|
||||
/**
|
||||
* 订正姓名
|
||||
*/
|
||||
@TableField(value = "correct_name")
|
||||
private String correctName;
|
||||
|
||||
/**
|
||||
* 撤销原因
|
||||
*/
|
||||
@TableField(value = "withdraw_reason")
|
||||
private String withdrawReason;
|
||||
|
||||
/**
|
||||
* 报告机构
|
||||
*/
|
||||
@TableField(value = "report_org")
|
||||
private String reportOrg;
|
||||
|
||||
/**
|
||||
* 报告机构电话
|
||||
*/
|
||||
@TableField(value = "report_org_phone")
|
||||
private String reportOrgPhone;
|
||||
|
||||
/**
|
||||
* 报告医生
|
||||
*/
|
||||
@TableField(value = "report_doc")
|
||||
private String reportDoc;
|
||||
|
||||
/**
|
||||
* 报告日期
|
||||
*/
|
||||
@TableField(value = "report_date")
|
||||
private LocalDate reportDate;
|
||||
|
||||
/**
|
||||
* 状态:0暂存 1已提交 2已审核 3已上报 4失败 5作废
|
||||
*/
|
||||
@TableField(value = "status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 失败信息
|
||||
*/
|
||||
@TableField(value = "fail_msg")
|
||||
private String failMsg;
|
||||
|
||||
/**
|
||||
* XML内容
|
||||
*/
|
||||
@TableField(value = "xml_content")
|
||||
private String xmlContent;
|
||||
|
||||
/**
|
||||
* 卡片名称代码:默认1-中华人民共和国传染病报告卡
|
||||
*/
|
||||
@TableField(value = "card_name_code")
|
||||
private Integer cardNameCode;
|
||||
|
||||
/**
|
||||
* 登记来源
|
||||
*/
|
||||
@TableField(value = "registration_source")
|
||||
private Integer registrationSource;
|
||||
|
||||
/**
|
||||
* 科室ID
|
||||
*/
|
||||
@TableField(value = "dept_id")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 医生ID
|
||||
*/
|
||||
@TableField(value = "doctor_id")
|
||||
private Long doctorId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public static final String COL_CARD_NO = "card_no";
|
||||
|
||||
public static final String COL_VISIT_ID = "visit_id";
|
||||
|
||||
public static final String COL_DIAG_ID = "diag_id";
|
||||
|
||||
public static final String COL_PAT_ID = "pat_id";
|
||||
|
||||
public static final String COL_ID_TYPE = "id_type";
|
||||
|
||||
public static final String COL_ID_NO = "id_no";
|
||||
|
||||
public static final String COL_PAT_NAME = "pat_name";
|
||||
|
||||
public static final String COL_PARENT_NAME = "parent_name";
|
||||
|
||||
public static final String COL_SEX = "sex";
|
||||
|
||||
public static final String COL_BIRTHDAY = "birthday";
|
||||
|
||||
public static final String COL_AGE = "age";
|
||||
|
||||
public static final String COL_AGE_UNIT = "age_unit";
|
||||
|
||||
public static final String COL_WORKPLACE = "workplace";
|
||||
|
||||
public static final String COL_PHONE = "phone";
|
||||
|
||||
public static final String COL_CONTACT_PHONE = "contact_phone";
|
||||
|
||||
public static final String COL_ADDRESS_PROV = "address_prov";
|
||||
|
||||
public static final String COL_ADDRESS_CITY = "address_city";
|
||||
|
||||
public static final String COL_ADDRESS_COUNTY = "address_county";
|
||||
|
||||
public static final String COL_ADDRESS_TOWN = "address_town";
|
||||
|
||||
public static final String COL_ADDRESS_VILLAGE = "address_village";
|
||||
|
||||
public static final String COL_ADDRESS_HOUSE = "address_house";
|
||||
|
||||
public static final String COL_PATIENT_BELONG = "patient_belong";
|
||||
|
||||
public static final String COL_OCCUPATION = "occupation";
|
||||
|
||||
public static final String COL_DISEASE_CODE = "disease_code";
|
||||
|
||||
public static final String COL_DISEASE_TYPE = "disease_type";
|
||||
|
||||
public static final String COL_OTHER_DISEASE = "other_disease";
|
||||
|
||||
public static final String COL_CASE_CLASS = "case_class";
|
||||
|
||||
public static final String COL_ONSET_DATE = "onset_date";
|
||||
|
||||
public static final String COL_DIAG_DATE = "diag_date";
|
||||
|
||||
public static final String COL_DEATH_DATE = "death_date";
|
||||
|
||||
public static final String COL_CORRECT_NAME = "correct_name";
|
||||
|
||||
public static final String COL_WITHDRAW_REASON = "withdraw_reason";
|
||||
|
||||
public static final String COL_REPORT_ORG = "report_org";
|
||||
|
||||
public static final String COL_REPORT_ORG_PHONE = "report_org_phone";
|
||||
|
||||
public static final String COL_REPORT_DOC = "report_doc";
|
||||
|
||||
public static final String COL_REPORT_DATE = "report_date";
|
||||
|
||||
public static final String COL_STATUS = "status";
|
||||
|
||||
public static final String COL_FAIL_MSG = "fail_msg";
|
||||
|
||||
public static final String COL_XML_CONTENT = "xml_content";
|
||||
|
||||
public static final String COL_CARD_NAME_CODE = "card_name_code";
|
||||
|
||||
public static final String COL_REGISTRATION_SOURCE = "registration_source";
|
||||
|
||||
public static final String COL_DEPT_ID = "dept_id";
|
||||
|
||||
public static final String COL_DOCTOR_ID = "doctor_id";
|
||||
|
||||
public static final String COL_CREATE_TIME = "create_time";
|
||||
|
||||
public static final String COL_UPDATE_TIME = "update_time";
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.openhis.clinical.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.clinical.domain.InfectiousCard;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 传染病报卡表 Mapper 接口
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfectiousCardMapper extends BaseMapper<InfectiousCard> {
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.openhis.clinical.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.clinical.domain.InfectiousCard;
|
||||
|
||||
/**
|
||||
* 传染病报卡表 Service接口
|
||||
*/
|
||||
public interface IInfectiousCardService extends IService<InfectiousCard> {
|
||||
|
||||
/**
|
||||
* 保存传染病报告卡
|
||||
* @param infectiousCard 传染病报告卡对象
|
||||
* @return 保存结果
|
||||
*/
|
||||
boolean saveInfectiousCard(InfectiousCard infectiousCard);
|
||||
|
||||
/**
|
||||
* 生成下一个卡片编号
|
||||
* @param orgCode 医疗机构编码
|
||||
* @return 下一个卡片编号(机构编码+YYYYMMDD+4位流水号)
|
||||
*/
|
||||
String generateNextCardNo(String orgCode);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.openhis.clinical.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.clinical.domain.InfectiousCard;
|
||||
import com.openhis.clinical.mapper.InfectiousCardMapper;
|
||||
import com.openhis.clinical.service.IInfectiousCardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 传染病报卡表 Service实现
|
||||
*/
|
||||
@Service
|
||||
public class InfectiousCardServiceImpl extends ServiceImpl<InfectiousCardMapper, InfectiousCard> implements IInfectiousCardService {
|
||||
|
||||
@Override
|
||||
public String generateNextCardNo(String orgCode) {
|
||||
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
String prefix = orgCode + dateStr;
|
||||
|
||||
// --- MyBatis-Plus Lambda 重构部分开始 ---
|
||||
InfectiousCard maxCardEntity = this.lambdaQuery()
|
||||
.select(InfectiousCard::getCardNo) // 只查询编号列
|
||||
.likeRight(InfectiousCard::getCardNo, prefix) // 匹配前缀 (prefix%)
|
||||
.orderByDesc(InfectiousCard::getCardNo) // 按编号降序
|
||||
.last("LIMIT 1") // 取最大的一条
|
||||
.one(); // 获取单条实体
|
||||
|
||||
String maxCardNo = (maxCardEntity != null) ? maxCardEntity.getCardNo() : null;
|
||||
// --- 重构部分结束 ---
|
||||
|
||||
int nextSerial = 1;
|
||||
if (maxCardNo != null && maxCardNo.length() >= 4) {
|
||||
String lastFour = maxCardNo.substring(maxCardNo.length() - 4);
|
||||
try {
|
||||
nextSerial = Integer.parseInt(lastFour) + 1;
|
||||
} catch (NumberFormatException e) {
|
||||
// 流水号解析失败,使用默认值1
|
||||
}
|
||||
}
|
||||
return prefix + String.format("%04d", nextSerial);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveInfectiousCard(InfectiousCard infectiousCard) {
|
||||
// 设置默认值
|
||||
if (infectiousCard.getStatus() == null) {
|
||||
infectiousCard.setStatus(0);
|
||||
}
|
||||
|
||||
if (infectiousCard.getReportDate() == null) {
|
||||
infectiousCard.setReportDate(LocalDate.now());
|
||||
}
|
||||
|
||||
if (infectiousCard.getCreateTime() == null) {
|
||||
infectiousCard.setCreateTime(LocalDateTime.now());
|
||||
} else {
|
||||
infectiousCard.setUpdateTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
return this.saveOrUpdate(infectiousCard);
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package com.openhis.consultation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 会诊确认表
|
||||
*/
|
||||
@TableName(value = "consultation_confirmation")
|
||||
@Data
|
||||
public class ConsultationConfirmation implements Serializable {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会诊申请ID
|
||||
*/
|
||||
@TableField(value = "consultation_request_id")
|
||||
private Long consultationRequestId;
|
||||
|
||||
/**
|
||||
* 确认科室ID
|
||||
*/
|
||||
@TableField(value = "confirming_department_id")
|
||||
private Long confirmingDepartmentId;
|
||||
|
||||
/**
|
||||
* 确认科室名称
|
||||
*/
|
||||
@TableField(value = "confirming_department_name")
|
||||
private String confirmingDepartmentName;
|
||||
|
||||
/**
|
||||
* 确认医生ID
|
||||
*/
|
||||
@TableField(value = "confirming_doctor_id")
|
||||
private Long confirmingDoctorId;
|
||||
|
||||
/**
|
||||
* 确认医生姓名
|
||||
*/
|
||||
@TableField(value = "confirming_doctor_name")
|
||||
private String confirmingDoctorName;
|
||||
|
||||
/**
|
||||
* 确认状态:1-待确认,2-同意,3-拒绝
|
||||
*/
|
||||
@TableField(value = "confirmation_status")
|
||||
private Integer confirmationStatus;
|
||||
|
||||
/**
|
||||
* 确认/拒绝理由
|
||||
*/
|
||||
@TableField(value = "confirmation_reason")
|
||||
private String confirmationReason;
|
||||
|
||||
/**
|
||||
* 确认日期
|
||||
*/
|
||||
@TableField(value = "confirmation_date")
|
||||
private LocalDateTime confirmationDate;
|
||||
|
||||
/**
|
||||
* 会诊意见
|
||||
*/
|
||||
@TableField(value = "consultation_opinion")
|
||||
private String consultationOpinion;
|
||||
|
||||
/**
|
||||
* 会诊确认参加医师
|
||||
*/
|
||||
@TableField(value = "confirming_physician_participation")
|
||||
private String confirmingPhysicianParticipation;
|
||||
|
||||
/**
|
||||
* 所属医生
|
||||
*/
|
||||
@TableField(value = "confirming_physician_name")
|
||||
private String confirmingPhysicianName;
|
||||
|
||||
/**
|
||||
* 代表科室
|
||||
*/
|
||||
@TableField(value = "confirming_department_name")
|
||||
private String confirmingDepartmentNameField;
|
||||
|
||||
/**
|
||||
* 签名医生
|
||||
*/
|
||||
@TableField(value = "signature")
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* 签名时间
|
||||
*/
|
||||
@TableField(value = "signature_date")
|
||||
private LocalDateTime signatureDate;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField(value = "creator_id")
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(value = "creator_name")
|
||||
private String creatorName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField(value = "updater_id")
|
||||
private Long updaterId;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
@TableField(value = "updater_name")
|
||||
private String updaterName;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 有效标志:1-有效,0-无效
|
||||
*/
|
||||
@TableField(value = "valid_flag")
|
||||
private Integer validFlag;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@TableField(value = "tenant_id")
|
||||
private Integer tenantId;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package com.openhis.consultation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 会诊记录表
|
||||
*/
|
||||
@TableName(value = "consultation_record")
|
||||
@Data
|
||||
public class ConsultationRecord implements Serializable {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会诊申请ID
|
||||
*/
|
||||
@TableField(value = "consultation_request_id")
|
||||
private Long consultationRequestId;
|
||||
|
||||
/**
|
||||
* 参与医生ID
|
||||
*/
|
||||
@TableField(value = "participant_doctor_id")
|
||||
private Long participantDoctorId;
|
||||
|
||||
/**
|
||||
* 参与医生姓名
|
||||
*/
|
||||
@TableField(value = "participant_doctor_name")
|
||||
private String participantDoctorName;
|
||||
|
||||
/**
|
||||
* 参与科室ID
|
||||
*/
|
||||
@TableField(value = "participant_department_id")
|
||||
private Long participantDepartmentId;
|
||||
|
||||
/**
|
||||
* 参与科室名称
|
||||
*/
|
||||
@TableField(value = "participant_department_name")
|
||||
private String participantDepartmentName;
|
||||
|
||||
/**
|
||||
* 会诊意见
|
||||
*/
|
||||
@TableField(value = "opinion")
|
||||
private String opinion;
|
||||
|
||||
/**
|
||||
* 会诊建议
|
||||
*/
|
||||
@TableField(value = "suggestion")
|
||||
private String suggestion;
|
||||
|
||||
/**
|
||||
* 记录日期
|
||||
*/
|
||||
@TableField(value = "record_date")
|
||||
private LocalDateTime recordDate;
|
||||
|
||||
/**
|
||||
* 创建人ID
|
||||
*/
|
||||
@TableField(value = "creator_id")
|
||||
private Long creatorId;
|
||||
|
||||
/**
|
||||
* 创建人姓名
|
||||
*/
|
||||
@TableField(value = "creator_name")
|
||||
private String creatorName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新人ID
|
||||
*/
|
||||
@TableField(value = "updater_id")
|
||||
private Long updaterId;
|
||||
|
||||
/**
|
||||
* 更新人姓名
|
||||
*/
|
||||
@TableField(value = "updater_name")
|
||||
private String updaterName;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 有效标志:1-有效,0-无效
|
||||
*/
|
||||
@TableField(value = "valid_flag")
|
||||
private Integer validFlag;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@TableField(value = "tenant_id")
|
||||
private Integer tenantId;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
package com.openhis.consultation.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.Alias;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会诊申请表
|
||||
*/
|
||||
@TableName(value = "consultation_request")
|
||||
@Alias("domainConsultationRequest")
|
||||
@Data
|
||||
public class ConsultationRequest implements Serializable {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会诊申请单号:CS+年月日时分秒+4位随机数
|
||||
*/
|
||||
@TableField(value = "consultation_id")
|
||||
private String consultationId;
|
||||
|
||||
/**
|
||||
* 患者ID(外键:adm_patient.id)
|
||||
*/
|
||||
@TableField(value = "patient_id")
|
||||
private Long patientId;
|
||||
|
||||
/**
|
||||
* 门诊就诊流水号(外键:adm_encounter.id)
|
||||
*/
|
||||
@TableField(value = "encounter_id")
|
||||
private Long encounterId;
|
||||
|
||||
/**
|
||||
* 关联的医嘱ID(外键:wor_service_request.id)
|
||||
*/
|
||||
@TableField(value = "order_id")
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 患者姓名
|
||||
*/
|
||||
@TableField(value = "patient_name")
|
||||
private String patientName;
|
||||
|
||||
/**
|
||||
* 患者病历号
|
||||
*/
|
||||
@TableField(value = "patient_bus_no")
|
||||
private String patientBusNo;
|
||||
|
||||
/**
|
||||
* 患者就诊卡号
|
||||
*/
|
||||
@TableField(value = "patient_identifier_no")
|
||||
private String patientIdentifierNo;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
@TableField(value = "gender_enum")
|
||||
private Integer genderEnum;
|
||||
|
||||
/**
|
||||
* 年龄
|
||||
*/
|
||||
@TableField(value = "age")
|
||||
private Integer age;
|
||||
|
||||
/**
|
||||
* 申请会诊的科室名称
|
||||
*/
|
||||
@TableField(value = "department")
|
||||
private String department;
|
||||
|
||||
/**
|
||||
* 申请科室ID
|
||||
*/
|
||||
@TableField(value = "department_id")
|
||||
private Long departmentId;
|
||||
|
||||
/**
|
||||
* 申请会诊的医生姓名
|
||||
*/
|
||||
@TableField(value = "requesting_physician")
|
||||
private String requestingPhysician;
|
||||
|
||||
/**
|
||||
* 申请医生ID
|
||||
*/
|
||||
@TableField(value = "requesting_physician_id")
|
||||
private Long requestingPhysicianId;
|
||||
|
||||
/**
|
||||
* 会诊申请时间
|
||||
*/
|
||||
@TableField(value = "consultation_request_date")
|
||||
private LocalDateTime consultationRequestDate;
|
||||
|
||||
/**
|
||||
* 会诊邀请对象
|
||||
*/
|
||||
@TableField(value = "invited_object")
|
||||
private String invitedObject;
|
||||
|
||||
/**
|
||||
* 会诊时间
|
||||
*/
|
||||
@TableField(value = "consultation_date")
|
||||
private LocalDateTime consultationDate;
|
||||
|
||||
/**
|
||||
* 简要病史及会诊目的
|
||||
*/
|
||||
@TableField(value = "consultation_purpose")
|
||||
private String consultationPurpose;
|
||||
|
||||
/**
|
||||
* 门诊诊断
|
||||
*/
|
||||
@TableField(value = "provisional_diagnosis")
|
||||
private String provisionalDiagnosis;
|
||||
|
||||
/**
|
||||
* 会诊意见
|
||||
*/
|
||||
@TableField(value = "consultation_opinion")
|
||||
private String consultationOpinion;
|
||||
|
||||
/**
|
||||
* 会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消
|
||||
*/
|
||||
@TableField(value = "consultation_status")
|
||||
private Integer consultationStatus;
|
||||
|
||||
/**
|
||||
* 是否紧急:一般/紧急
|
||||
*/
|
||||
@TableField(value = "consultation_urgency")
|
||||
private String consultationUrgency;
|
||||
|
||||
/**
|
||||
* 提交会诊的医生姓名
|
||||
*/
|
||||
@TableField(value = "confirming_physician")
|
||||
private String confirmingPhysician;
|
||||
|
||||
/**
|
||||
* 提交会诊的医生ID
|
||||
*/
|
||||
@TableField(value = "confirming_physician_id")
|
||||
private Long confirmingPhysicianId;
|
||||
|
||||
/**
|
||||
* 提交会诊日期
|
||||
*/
|
||||
@TableField(value = "confirming_date")
|
||||
private LocalDateTime confirmingDate;
|
||||
|
||||
/**
|
||||
* 结束会诊医生姓名/签名医生
|
||||
*/
|
||||
@TableField(value = "signature")
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* 结束会诊医生ID
|
||||
*/
|
||||
@TableField(value = "signature_physician_id")
|
||||
private Long signaturePhysicianId;
|
||||
|
||||
/**
|
||||
* 结束会诊日期/签名时间
|
||||
*/
|
||||
@TableField(value = "signature_date")
|
||||
private LocalDateTime signatureDate;
|
||||
|
||||
/**
|
||||
* 作废会诊日期
|
||||
*/
|
||||
@TableField(value = "cancel_nature_date")
|
||||
private LocalDateTime cancelNatureDate;
|
||||
|
||||
/**
|
||||
* 作废原因
|
||||
*/
|
||||
@TableField(value = "cancel_reason")
|
||||
private String cancelReason;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@TableField(value = "create_by")
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@TableField(value = "update_by")
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@TableField(value = "tenant_id")
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 逻辑删除标识:0-未删除,1-已删除
|
||||
*/
|
||||
@TableField(value = "is_deleted")
|
||||
private Integer isDeleted;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@TableField(value = "remark")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 会诊项目ID,关联wor_activity_definition表,用于确定会诊类型和价格
|
||||
*/
|
||||
@TableField(value = "consultation_activity_id")
|
||||
private Long consultationActivityId;
|
||||
|
||||
/**
|
||||
* 会诊项目名称,如:院内会诊、远程会诊等
|
||||
*/
|
||||
@TableField(value = "consultation_activity_name")
|
||||
private String consultationActivityName;
|
||||
|
||||
/**
|
||||
* 确认会诊的医生姓名
|
||||
*/
|
||||
@TableField(value = "confirming_physician_name")
|
||||
private String confirmingPhysicianName;
|
||||
|
||||
/**
|
||||
* 代表科室
|
||||
*/
|
||||
@TableField(value = "confirming_department_name")
|
||||
private String confirmingDepartmentName;
|
||||
|
||||
/**
|
||||
* 会诊确认参加医师
|
||||
*/
|
||||
@TableField(value = "confirming_physician_participation")
|
||||
private String confirmingPhysicianParticipation;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.openhis.consultation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.consultation.domain.ConsultationConfirmation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 会诊确认 Mapper接口
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConsultationConfirmationMapper extends BaseMapper<ConsultationConfirmation> {
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.openhis.consultation.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.openhis.consultation.domain.ConsultationRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 会诊记录 Mapper接口
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConsultationRecordMapper extends BaseMapper<ConsultationRecord> {
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.openhis.consultation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.consultation.domain.ConsultationConfirmation;
|
||||
|
||||
/**
|
||||
* 会诊确认 服务层
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
public interface IConsultationConfirmationService extends IService<ConsultationConfirmation> {
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.openhis.consultation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.consultation.domain.ConsultationRecord;
|
||||
|
||||
/**
|
||||
* 会诊记录 服务层
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
public interface IConsultationRecordService extends IService<ConsultationRecord> {
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.openhis.consultation.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.openhis.consultation.domain.ConsultationRequest;
|
||||
|
||||
/**
|
||||
* 会诊申请 服务层
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
public interface IConsultationRequestService extends IService<ConsultationRequest> {
|
||||
/**
|
||||
* 提交会诊申请
|
||||
*/
|
||||
int submitConsultation(Long id, Long userId, String userName);
|
||||
|
||||
/**
|
||||
* 取消提交会诊申请
|
||||
*/
|
||||
int cancelSubmitConsultation(Long id);
|
||||
|
||||
/**
|
||||
* 结束会诊申请
|
||||
*/
|
||||
int endConsultation(Long id, Long userId, String userName);
|
||||
|
||||
/**
|
||||
* 作废会诊申请
|
||||
*/
|
||||
int cancelConsultation(Long id, Long userId, String userName);
|
||||
|
||||
/**
|
||||
* 确认会诊
|
||||
*/
|
||||
int confirmConsultation(Long id, String physicianName, String physicianId, String opinion);
|
||||
|
||||
/**
|
||||
* 签名会诊
|
||||
*/
|
||||
int signConsultation(Long id, String signature, Long userId, String userName);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.openhis.consultation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.consultation.domain.ConsultationConfirmation;
|
||||
import com.openhis.consultation.mapper.ConsultationConfirmationMapper;
|
||||
import com.openhis.consultation.service.IConsultationConfirmationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 会诊确认 服务层实现
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@Service
|
||||
public class ConsultationConfirmationServiceImpl extends ServiceImpl<ConsultationConfirmationMapper, ConsultationConfirmation> implements IConsultationConfirmationService {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.openhis.consultation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.consultation.domain.ConsultationRecord;
|
||||
import com.openhis.consultation.mapper.ConsultationRecordMapper;
|
||||
import com.openhis.consultation.service.IConsultationRecordService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 会诊记录 服务层实现
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@Service
|
||||
public class ConsultationRecordServiceImpl extends ServiceImpl<ConsultationRecordMapper, ConsultationRecord> implements IConsultationRecordService {
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.openhis.consultation.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.openhis.consultation.domain.ConsultationRequest;
|
||||
import com.openhis.consultation.mapper.ConsultationRequestMapper;
|
||||
import com.openhis.consultation.service.IConsultationRequestService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会诊申请 服务层实现
|
||||
*
|
||||
* @author his
|
||||
*/
|
||||
@Service
|
||||
public class ConsultationRequestServiceImpl extends ServiceImpl<ConsultationRequestMapper, ConsultationRequest> implements IConsultationRequestService {
|
||||
|
||||
@Override
|
||||
public int submitConsultation(Long id, Long userId, String userName) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() == 0) { // 仅当状态为"新开"时可提交
|
||||
request.setConsultationStatus(10); // 设置为"已提交"
|
||||
request.setConfirmingPhysician(userName);
|
||||
request.setConfirmingPhysicianId(userId);
|
||||
request.setConfirmingDate(LocalDateTime.now());
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 提交失败
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelSubmitConsultation(Long id) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() == 10) { // 仅当状态为"已提交"时可取消提交
|
||||
request.setConsultationStatus(0); // 设置为"新开"
|
||||
request.setConfirmingPhysician(null);
|
||||
request.setConfirmingPhysicianId(null);
|
||||
request.setConfirmingDate(null);
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 取消提交失败
|
||||
}
|
||||
|
||||
@Override
|
||||
public int endConsultation(Long id, Long userId, String userName) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() == 30) { // 仅当状态为"已签名"时可结束
|
||||
request.setConsultationStatus(40); // 设置为"已完成"
|
||||
request.setSignature(userName);
|
||||
request.setSignatureDate(LocalDateTime.now());
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 结束失败
|
||||
}
|
||||
|
||||
@Override
|
||||
public int cancelConsultation(Long id, Long userId, String userName) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() != 40) { // 未完成的申请可作废
|
||||
request.setConsultationStatus(50); // 设置为"已取消"
|
||||
request.setSignature(userName);
|
||||
request.setSignatureDate(LocalDateTime.now());
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 作废失败
|
||||
}
|
||||
|
||||
@Override
|
||||
public int confirmConsultation(Long id, String physicianName, String physicianId, String opinion) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() != 40) { // 未完成的申请可确认
|
||||
request.setConsultationStatus(20); // 设置为"已确认"
|
||||
request.setConfirmingPhysicianName(physicianName);
|
||||
request.setConsultationOpinion(opinion);
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 确认失败
|
||||
}
|
||||
|
||||
@Override
|
||||
public int signConsultation(Long id, String signature, Long userId, String userName) {
|
||||
ConsultationRequest request = this.getById(id);
|
||||
if (request != null && request.getConsultationStatus() == 20) { // 仅当状态为"已确认"时可签名
|
||||
request.setConsultationStatus(30); // 设置为"已签名"
|
||||
request.setSignature(signature);
|
||||
request.setSignatureDate(LocalDateTime.now());
|
||||
return this.updateById(request) ? 1 : 0;
|
||||
}
|
||||
return 0; // 签名失败
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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.openhis.consultation.mapper.ConsultationConfirmationMapper">
|
||||
|
||||
<resultMap type="com.openhis.consultation.domain.ConsultationConfirmation" id="ConsultationConfirmationResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="consultationRequestId" column="consultation_request_id"/>
|
||||
<result property="confirmingDepartmentId" column="confirming_department_id"/>
|
||||
<result property="confirmingDepartmentName" column="confirming_department_name"/>
|
||||
<result property="confirmingDoctorId" column="confirming_doctor_id"/>
|
||||
<result property="confirmingDoctorName" column="confirming_doctor_name"/>
|
||||
<result property="confirmationStatus" column="confirmation_status"/>
|
||||
<result property="confirmationReason" column="confirmation_reason"/>
|
||||
<result property="confirmationDate" column="confirmation_date"/>
|
||||
<result property="creatorId" column="creator_id"/>
|
||||
<result property="creatorName" column="creator_name"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updaterId" column="updater_id"/>
|
||||
<result property="updaterName" column="updater_name"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="validFlag" column="valid_flag"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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.openhis.consultation.mapper.ConsultationRecordMapper">
|
||||
|
||||
<resultMap type="com.openhis.consultation.domain.ConsultationRecord" id="ConsultationRecordResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="consultationRequestId" column="consultation_request_id"/>
|
||||
<result property="participantDoctorId" column="participant_doctor_id"/>
|
||||
<result property="participantDoctorName" column="participant_doctor_name"/>
|
||||
<result property="participantDepartmentId" column="participant_department_id"/>
|
||||
<result property="participantDepartmentName" column="participant_department_name"/>
|
||||
<result property="opinion" column="opinion"/>
|
||||
<result property="suggestion" column="suggestion"/>
|
||||
<result property="recordDate" column="record_date"/>
|
||||
<result property="creatorId" column="creator_id"/>
|
||||
<result property="creatorName" column="creator_name"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updaterId" column="updater_id"/>
|
||||
<result property="updaterName" column="updater_name"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="validFlag" column="valid_flag"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?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.openhis.consultation.mapper.ConsultationRequestMapper">
|
||||
|
||||
<resultMap type="com.openhis.consultation.domain.ConsultationRequest" id="ConsultationRequestResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="consultationId" column="consultation_id"/>
|
||||
<result property="patientId" column="patient_id"/>
|
||||
<result property="encounterId" column="encounter_id"/>
|
||||
<result property="orderId" column="order_id"/>
|
||||
<result property="patientName" column="patient_name"/>
|
||||
<result property="patientBusNo" column="patient_bus_no"/>
|
||||
<result property="patientIdentifierNo" column="patient_identifier_no"/>
|
||||
<result property="genderEnum" column="gender_enum"/>
|
||||
<result property="age" column="age"/>
|
||||
<result property="department" column="department"/>
|
||||
<result property="departmentId" column="department_id"/>
|
||||
<result property="requestingPhysician" column="requesting_physician"/>
|
||||
<result property="requestingPhysicianId" column="requesting_physician_id"/>
|
||||
<result property="consultationRequestDate" column="consultation_request_date"/>
|
||||
<result property="invitedObject" column="invited_object"/>
|
||||
<result property="consultationDate" column="consultation_date"/>
|
||||
<result property="consultationPurpose" column="consultation_purpose"/>
|
||||
<result property="provisionalDiagnosis" column="provisional_diagnosis"/>
|
||||
<result property="consultationOpinion" column="consultation_opinion"/>
|
||||
<result property="consultationStatus" column="consultation_status"/>
|
||||
<result property="consultationUrgency" column="consultation_urgency"/>
|
||||
<result property="confirmingPhysician" column="confirming_physician"/>
|
||||
<result property="confirmingPhysicianId" column="confirming_physician_id"/>
|
||||
<result property="confirmingDate" column="confirming_date"/>
|
||||
<result property="signature" column="signature"/>
|
||||
<result property="signaturePhysicianId" column="signature_physician_id"/>
|
||||
<result property="signatureDate" column="signature_date"/>
|
||||
<result property="cancelNatureDate" column="cancel_nature_date"/>
|
||||
<result property="cancelReason" column="cancel_reason"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="isDeleted" column="is_deleted"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="consultationActivityId" column="consultation_activity_id"/>
|
||||
<result property="consultationActivityName" column="consultation_activity_name"/>
|
||||
<result property="confirmingPhysicianName" column="confirming_physician_name"/>
|
||||
<result property="confirmingDepartmentName" column="confirming_department_name"/>
|
||||
<result property="confirmingPhysicianParticipation" column="confirming_physician_participation"/>
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
@@ -1,80 +0,0 @@
|
||||
-- 优化会诊申请表结构以满足需求文档 (PostgreSQL格式)
|
||||
-- 添加缺失的字段
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查并添加字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'visit_id') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN visit_id BIGINT;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'order_id') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN order_id BIGINT;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'patient_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN patient_name VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'gender') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN gender VARCHAR(10);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'age') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN age INTEGER;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'provisional_diagnosis') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN provisional_diagnosis TEXT;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'consultation_opinion') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN consultation_opinion TEXT;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_id') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_id BIGINT;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_date') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_date TIMESTAMP;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'consultation_urgency') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN consultation_urgency VARCHAR(20) DEFAULT '一般';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'consultation_status') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN consultation_status INTEGER DEFAULT 0;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'consultation_request_date') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN consultation_request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_name VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'signature') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN signature VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'signature_date') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN signature_date TIMESTAMP;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_department_name') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_department_name VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'consultation_request' AND column_name = 'confirming_physician_participation') THEN
|
||||
ALTER TABLE consultation_request ADD COLUMN confirming_physician_participation VARCHAR(100);
|
||||
END IF;
|
||||
|
||||
-- 更新注释
|
||||
COMMENT ON COLUMN consultation_request.consultation_status IS '会诊状态:0-新开,10-已提交,20-已确认,30-已签名,40-已完成,50-已取消';
|
||||
END $$;
|
||||
@@ -1,12 +0,0 @@
|
||||
-- 在adm_charge_item表添加来源业务单据字段
|
||||
-- 用于追溯费用是来源于手术计费
|
||||
-- SourceBillNo = 手术申请单号
|
||||
|
||||
-- 添加SourceBillNo字段
|
||||
ALTER TABLE adm_charge_item ADD COLUMN IF NOT EXISTS source_bill_no VARCHAR(64);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON COLUMN adm_charge_item.source_bill_no IS '来源业务单据号(如手术申请单号)';
|
||||
|
||||
-- 创建索引以提高查询性能
|
||||
CREATE INDEX IF NOT EXISTS idx_source_bill_no ON adm_charge_item(source_bill_no);
|
||||
@@ -1,71 +0,0 @@
|
||||
-- 创建日结医保结算表
|
||||
CREATE TABLE IF NOT EXISTS yb_day_end_settlement (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
settlement_no VARCHAR(64) NOT NULL,
|
||||
settlement_date DATE NOT NULL,
|
||||
settlement_type VARCHAR(20) DEFAULT 'daily',
|
||||
insurance_type VARCHAR(50),
|
||||
total_visits INTEGER DEFAULT 0,
|
||||
total_amount NUMERIC(15,2) DEFAULT 0.00,
|
||||
insurance_pay_amount NUMERIC(15,2) DEFAULT 0.00,
|
||||
account_pay_amount NUMERIC(15,2) DEFAULT 0.00,
|
||||
personal_pay_amount NUMERIC(15,2) DEFAULT 0.00,
|
||||
fund_pay_sum_amount NUMERIC(15,2) DEFAULT 0.00,
|
||||
status CHAR(1) DEFAULT '0',
|
||||
operator VARCHAR(50),
|
||||
remark VARCHAR(500),
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE yb_day_end_settlement IS '日结医保结算表';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.settlement_no IS '结算单号';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.settlement_date IS '结算日期';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.settlement_type IS '结算类型 (daily, weekly, monthly)';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.insurance_type IS '医保类型 (urbanEmployee, urbanRuralResident, newRuralCooperative, selfPaid)';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.total_visits IS '总人次';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.total_amount IS '总金额';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.insurance_pay_amount IS '医保统筹支付金额';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.account_pay_amount IS '个人账户支付金额';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.personal_pay_amount IS '个人自付金额';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.fund_pay_sum_amount IS '医保基金支付总额';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.status IS '状态 (0正常 1停用)';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.operator IS '操作员';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.remark IS '备注';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.create_by IS '创建者';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.update_by IS '更新者';
|
||||
COMMENT ON COLUMN yb_day_end_settlement.update_time IS '更新时间';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_settlement_date ON yb_day_end_settlement(settlement_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_settlement_no ON yb_day_end_settlement(settlement_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_settlement_type ON yb_day_end_settlement(settlement_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_insurance_type ON yb_day_end_settlement(insurance_type);
|
||||
|
||||
-- 创建更新时间函数(如果不存在)
|
||||
CREATE OR REPLACE FUNCTION update_modified_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.update_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- 创建更新时间触发器
|
||||
DO $$
|
||||
BEGIN
|
||||
DROP TRIGGER IF EXISTS update_yb_day_end_settlement_modtime ON yb_day_end_settlement;
|
||||
CREATE TRIGGER update_yb_day_end_settlement_modtime
|
||||
BEFORE UPDATE ON yb_day_end_settlement
|
||||
FOR EACH ROW EXECUTE FUNCTION update_modified_column();
|
||||
END $$;
|
||||
|
||||
-- 插入示例数据
|
||||
INSERT INTO yb_day_end_settlement (settlement_no, settlement_date, settlement_type, insurance_type, total_visits, total_amount, insurance_pay_amount, account_pay_amount, personal_pay_amount, fund_pay_sum_amount, status, operator, remark, create_by) VALUES
|
||||
('YBDS20260202001', '2026-02-02', 'daily', 'urbanEmployee', 150, 150000.00, 120000.00, 15000.00, 15000.00, 135000.00, '0', 'admin', '2026年2月2日城镇职工医保日结', 'admin'),
|
||||
('YBDS20260202002', '2026-02-02', 'daily', 'urbanRuralResident', 80, 80000.00, 64000.00, 8000.00, 8000.00, 72000.00, '0', 'admin', '2026年2月2日城乡居民医保日结', 'admin'),
|
||||
('YBDS20260202003', '2026-02-02', 'daily', 'newRuralCooperative', 60, 60000.00, 48000.00, 6000.00, 6000.00, 54000.00, '0', 'admin', '2026年2月2日新农合医保日结', 'admin');
|
||||
@@ -1,27 +0,0 @@
|
||||
-- 添加日结医保结算菜单项到系统菜单表
|
||||
-- 假设医保管理模块的父级菜单ID为某个值,这里我们先查询医保管理相关的菜单ID
|
||||
|
||||
-- 首先查找医保相关的父菜单ID(如果没有找到,需要手动指定一个合适的父菜单ID)
|
||||
-- 偌设我们使用一个常见的父级ID,或者创建一个新的医保管理顶级菜单
|
||||
|
||||
-- 添加医保管理顶级菜单(如果不存在的话)
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '医保管理', 0, 7, 'ybmanagement', '', NULL, 'YbManagement', '1', '0', 'M', '0', '0', '', 'medication', 'admin', NOW(), 'admin', NOW(), '医保管理菜单'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu WHERE menu_name = '医保管理' AND menu_type = 'M'
|
||||
);
|
||||
|
||||
-- 获取医保管理菜单ID
|
||||
DO $$
|
||||
DECLARE
|
||||
yb_management_menu_id BIGINT;
|
||||
BEGIN
|
||||
SELECT menu_id INTO yb_management_menu_id FROM sys_menu WHERE menu_name = '医保管理' LIMIT 1;
|
||||
|
||||
-- 添加日结医保结算子菜单
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('日结医保结算', yb_management_menu_id, 1, 'dayEndMedicalInsuranceSettlement', 'ybmanagement/dayEndMedicalInsuranceSettlement/index', NULL, 'DayEndMedicalInsuranceSettlement', '1', '0', 'C', '0', '0', 'ybmanage:dayEndMedicalInsuranceSettlement:view', 'document', 'admin', NOW(), 'admin', NOW(), '日结医保结算菜单');
|
||||
END $$;
|
||||
@@ -1,118 +0,0 @@
|
||||
-- 更新会诊管理模块的菜单项,以满足需求文档要求 (PostgreSQL格式)
|
||||
DO $$
|
||||
DECLARE
|
||||
consultation_menu_id BIGINT;
|
||||
consultation_request_menu_id BIGINT;
|
||||
consultation_confirmation_menu_id BIGINT;
|
||||
consultation_record_menu_id BIGINT;
|
||||
BEGIN
|
||||
-- 获取会诊管理菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊管理', 0, 10, 'consultationmanagement', '', '', 'ConsultationManagement', '1', '0', 'M', '0', '0', '', 'operation', 'admin', NOW(), 'admin', NOW(), '会诊管理菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 获取会诊申请菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊申请', consultation_menu_id, 1, 'consultationrequest', 'clinicmanagement/consultationrequest/index', '', 'ConsultationRequest', '1', '0', 'C', '0', '0', 'consultation:request:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊申请菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊申请按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请查询', consultation_request_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:query' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请新增', consultation_request_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:add' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请修改', consultation_request_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:edit' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请删除', consultation_request_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:remove' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请提交', consultation_request_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:submit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:submit' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请结束', consultation_request_menu_id, 6, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:end', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:end' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请作废', consultation_request_menu_id, 7, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:cancel', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:cancel' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
-- 获取会诊确认菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊确认', consultation_menu_id, 2, 'consultationconfirmation', 'clinicmanagement/consultationconfirmation/index', '', 'ConsultationConfirmation', '1', '0', 'C', '0', '0', 'consultation:confirmation:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊确认菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊确认按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认查询', consultation_confirmation_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:query' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认新增', consultation_confirmation_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:add' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认修改', consultation_confirmation_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:edit' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认删除', consultation_confirmation_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:remove' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认操作', consultation_confirmation_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:confirm', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:confirm' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
-- 获取会诊记录菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_record_menu_id FROM sys_menu WHERE menu_name = '会诊记录' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊记录', consultation_menu_id, 3, 'consultationrecord', 'clinicmanagement/consultationrecord/index', '', 'ConsultationRecord', '1', '0', 'C', '0', '0', 'consultation:record:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊记录菜单');
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊记录按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录查询', consultation_record_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:query' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录新增', consultation_record_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:add' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录修改', consultation_record_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:edit' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录删除', consultation_record_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:remove' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
END $$;
|
||||
@@ -1,118 +0,0 @@
|
||||
-- 更新会诊管理模块的菜单项,以满足需求文档要求 (PostgreSQL格式)
|
||||
DO $$
|
||||
DECLARE
|
||||
consultation_menu_id BIGINT;
|
||||
consultation_request_menu_id BIGINT;
|
||||
consultation_confirmation_menu_id BIGINT;
|
||||
consultation_record_menu_id BIGINT;
|
||||
BEGIN
|
||||
-- 获取会诊管理菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊管理', 0, 10, 'consultationmanagement', '', '', 'ConsultationManagement', '1', '0', 'M', '0', '0', '', 'operation', 'admin', NOW(), 'admin', NOW(), '会诊管理菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 获取会诊申请菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊申请', consultation_menu_id, 1, 'consultationrequest', 'clinicmanagement/consultationrequest/index', '', 'ConsultationRequest', '1', '0', 'C', '0', '0', 'consultation:request:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊申请菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊申请按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请查询', consultation_request_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:query' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请新增', consultation_request_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:add' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请修改', consultation_request_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:edit' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请删除', consultation_request_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:remove' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请提交', consultation_request_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:submit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:submit' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请结束', consultation_request_menu_id, 6, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:end', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:end' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊申请作废', consultation_request_menu_id, 7, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:cancel', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:cancel' AND parent_id = consultation_request_menu_id);
|
||||
|
||||
-- 获取会诊确认菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊确认', consultation_menu_id, 2, 'consultationconfirmation', 'clinicmanagement/consultationconfirmation/index', '', 'ConsultationConfirmation', '1', '0', 'C', '0', '0', 'consultation:confirmation:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊确认菜单');
|
||||
|
||||
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊确认按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认查询', consultation_confirmation_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:query' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认新增', consultation_confirmation_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:add' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认修改', consultation_confirmation_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:edit' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认删除', consultation_confirmation_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:remove' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊确认操作', consultation_confirmation_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:confirm', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:confirm' AND parent_id = consultation_confirmation_menu_id);
|
||||
|
||||
-- 获取会诊记录菜单ID,如果不存在则创建
|
||||
SELECT menu_id INTO consultation_record_menu_id FROM sys_menu WHERE menu_name = '会诊记录' AND parent_id = consultation_menu_id LIMIT 1;
|
||||
IF NOT FOUND THEN
|
||||
INSERT INTO sys_menu
|
||||
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES
|
||||
('会诊记录', consultation_menu_id, 3, 'consultationrecord', 'clinicmanagement/consultationrecord/index', '', 'ConsultationRecord', '1', '0', 'C', '0', '0', 'consultation:record:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊记录菜单');
|
||||
END IF;
|
||||
|
||||
-- 更新或插入会诊记录按钮权限
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录查询', consultation_record_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:query', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:query' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录新增', consultation_record_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:add', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:add' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录修改', consultation_record_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:edit' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
SELECT '会诊记录删除', consultation_record_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:remove' AND parent_id = consultation_record_menu_id);
|
||||
|
||||
END $$;
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user