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个字符");
|
return R.fail(400, "备注长度不能超过500个字符");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查诊室名称在同卫生机构下是否已存在
|
|
||||||
if (clinicRoomService.existsByOrgNameAndRoomName(clinicRoom.getOrgName(), clinicRoom.getRoomName())) {
|
|
||||||
return R.fail(400, "当前卫生机构下已存在该诊室名称");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增诊室
|
// 新增诊室
|
||||||
int result = clinicRoomService.insertClinicRoom(clinicRoom);
|
int result = clinicRoomService.insertClinicRoom(clinicRoom);
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
@@ -98,11 +93,6 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
|
|||||||
return R.fail(404, "诊室不存在");
|
return R.fail(404, "诊室不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查诊室名称在同卫生机构下是否已存在(排除当前记录)
|
|
||||||
if (clinicRoomService.existsByOrgNameAndRoomNameExcludeId(clinicRoom.getOrgName(), clinicRoom.getRoomName(), clinicRoom.getId())) {
|
|
||||||
return R.fail(400, "当前卫生机构下已存在该诊室名称");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新诊室
|
// 更新诊室
|
||||||
int result = clinicRoomService.updateClinicRoom(clinicRoom);
|
int result = clinicRoomService.updateClinicRoom(clinicRoom);
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
|
|||||||
@@ -87,6 +87,30 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
return R.ok(list);
|
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 转换为字符串表示
|
* 将 DayOfWeek 转换为字符串表示
|
||||||
* @param dayOfWeek 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
|
@Override
|
||||||
public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) {
|
public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) {
|
||||||
if (ObjectUtil.isEmpty(doctorSchedule)) {
|
if (ObjectUtil.isEmpty(doctorSchedule)) {
|
||||||
|
|||||||
@@ -71,39 +71,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 3. 号源列表来自排班表(adm_doctor_schedule),需确保clinical_ticket中存在对应记录
|
// 3. 执行原有的预约逻辑
|
||||||
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. 执行预约逻辑
|
|
||||||
int result = ticketService.bookTicket(params);
|
int result = ticketService.bookTicket(params);
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
// 5. 预约成功后,更新排班表状态
|
// 4. 预约成功后,更新排班表状态
|
||||||
DoctorSchedule schedule = new DoctorSchedule();
|
DoctorSchedule schedule = new DoctorSchedule();
|
||||||
schedule.setId(slotId); // 对应 XML 中的 WHERE id = #{id}
|
schedule.setId(slotId); // 对应 XML 中的 WHERE id = #{id}
|
||||||
schedule.setIsStopped(true); // 设置为已预约
|
schedule.setIsStopped(true); // 设置为已预约
|
||||||
@@ -115,12 +86,14 @@ public class TicketAppServiceImpl implements ITicketAppService {
|
|||||||
if (updateCount > 0) {
|
if (updateCount > 0) {
|
||||||
return R.ok("预约成功并已更新排班状态");
|
return R.ok("预约成功并已更新排班状态");
|
||||||
} else {
|
} else {
|
||||||
|
// 如果更新失败,可能需要根据业务逻辑决定是否回滚预约
|
||||||
return R.ok("预约成功,但排班状态更新失败");
|
return R.ok("预约成功,但排班状态更新失败");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return R.fail("预约失败");
|
return R.fail("预约失败");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
// e.printStackTrace();
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
return R.fail("系统异常:" + e.getMessage());
|
return R.fail("系统异常:" + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,13 +89,4 @@ public class DoctorScheduleController {
|
|||||||
return R.ok(doctorScheduleAppService.getTodayDoctorScheduleList());
|
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.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.core.common.core.domain.R;
|
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.appservice.ISurgicalScheduleAppService;
|
||||||
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
|
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
|
||||||
import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
|
import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.catalina.User;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@@ -101,29 +98,4 @@ public class SurgicalScheduleController {
|
|||||||
surgicalScheduleAppService.exportSurgerySchedule(opScheduleDto, response);
|
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(可选,不传则查询当前医生的所有会诊申请)")
|
@ApiParam("就诊ID(可选,不传则查询当前医生的所有会诊申请)")
|
||||||
@RequestParam(required = false) Long encounterId) {
|
@RequestParam(required = false) Long encounterId) {
|
||||||
try {
|
try {
|
||||||
log.info("获取会诊列表,encounterId: {}", encounterId);
|
|
||||||
List<ConsultationRequestDto> list = consultationAppService.getConsultationList(encounterId);
|
List<ConsultationRequestDto> list = consultationAppService.getConsultationList(encounterId);
|
||||||
log.info("获取会诊列表成功,共 {} 条记录", list != null ? list.size() : 0);
|
|
||||||
return R.ok(list);
|
return R.ok(list);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取会诊列表失败,encounterId: {}", encounterId, e);
|
log.error("获取会诊列表失败", e);
|
||||||
return R.fail("获取会诊列表失败: " + e.getMessage());
|
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.baomidou.mybatisplus.annotation.*;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.ibatis.type.Alias;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -16,7 +15,6 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("consultation_request")
|
@TableName("consultation_request")
|
||||||
@Alias("webConsultationRequest")
|
|
||||||
public class ConsultationRequest implements Serializable {
|
public class ConsultationRequest implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
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.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.openhis.consultation.domain.ConsultationRequest;
|
import com.openhis.web.consultation.domain.ConsultationRequest;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会诊申请 Mapper接口
|
* 会诊申请Mapper接口
|
||||||
*
|
*
|
||||||
* @author his
|
* @author system
|
||||||
|
* @date 2026-01-29
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ConsultationRequestMapper extends BaseMapper<ConsultationRequest> {
|
public interface ConsultationRequestMapper extends BaseMapper<ConsultationRequest> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,16 +629,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
|
||||||
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
|
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
|
||||||
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
|
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
|
||||||
if (adviceSaveDto.getGenerateSourceEnum() != null) {
|
|
||||||
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
|
|
||||||
} else {
|
|
||||||
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
|
|
||||||
}
|
|
||||||
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
|
|
||||||
if (adviceSaveDto.getSourceBillNo() != null) {
|
|
||||||
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
|
|
||||||
}
|
|
||||||
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
|
||||||
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
|
||||||
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
|
||||||
@@ -768,16 +759,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
|
|
||||||
// 保存耗材费用项
|
// 保存耗材费用项
|
||||||
chargeItem = new ChargeItem();
|
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.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
chargeItem.setTenantId(tenantId); // 补全租户ID
|
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||||
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
||||||
@@ -919,16 +900,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
|
|||||||
// 保存时保存诊疗费用项
|
// 保存时保存诊疗费用项
|
||||||
if (is_save) {
|
if (is_save) {
|
||||||
chargeItem = new ChargeItem();
|
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.setId(adviceSaveDto.getChargeItemId()); // 费用项id
|
||||||
chargeItem.setTenantId(tenantId); // 补全租户ID
|
chargeItem.setTenantId(tenantId); // 补全租户ID
|
||||||
chargeItem.setCreateBy(currentUsername); // 补全创建人
|
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
|
@Override
|
||||||
public R<?> prePayment(PrePaymentDto prePaymentDto) {
|
public R<?> prePayment(PrePaymentDto prePaymentDto) {
|
||||||
logger.info("预结算:参数:" + JSON.toJSONString(prePaymentDto));
|
logger.info("预结算:参数:" + JSON.toJSONString(prePaymentDto));
|
||||||
// 查收费项(支持手术计费)
|
// 查收费项
|
||||||
List<ChargeItem> chargeItemList;
|
List<ChargeItem> chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
|
||||||
if (prePaymentDto.getGenerateSourceEnum() != null && prePaymentDto.getGenerateSourceEnum() == 2) {
|
|
||||||
// 手术计费:根据generateSourceEnum和sourceBillNo过滤
|
|
||||||
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds(), prePaymentDto.getGenerateSourceEnum(), prePaymentDto.getSourceBillNo());
|
|
||||||
} else {
|
|
||||||
// 普通门诊划价
|
|
||||||
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
|
|
||||||
}
|
|
||||||
if (chargeItemList.isEmpty()) {
|
if (chargeItemList.isEmpty()) {
|
||||||
return R.fail("未选择收费项");
|
return R.fail("未选择收费项");
|
||||||
}
|
}
|
||||||
@@ -1906,13 +1899,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
||||||
iAccountService.save(accountZf);
|
iAccountService.save(accountZf);
|
||||||
}
|
}
|
||||||
String accountContractNo = outpatientRegistrationAddParam.getAccountFormData().getContractNo();
|
|
||||||
if (!CommonConstants.BusinessName.DEFAULT_CONTRACT_NO
|
if (!CommonConstants.BusinessName.DEFAULT_CONTRACT_NO
|
||||||
.equals(accountContractNo)
|
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
|
||||||
&& !CommonConstants.BusinessName.DEFAULT_STUDENT_CONTRACT_NO
|
&& !CommonConstants.BusinessName.DEFAULT_STUDENT_CONTRACT_NO
|
||||||
.equals(accountContractNo)
|
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
|
||||||
&& accountContractNo.length() > 11
|
&& outpatientRegistrationAddParam.getAccountFormData().getContractNo().length() > 11) {
|
||||||
&& accountContractNo.startsWith("STUDENT")) {
|
|
||||||
// 建立学生自费ACCOUNT
|
// 建立学生自费ACCOUNT
|
||||||
Account accountStudentZf = new Account();
|
Account accountStudentZf = new Account();
|
||||||
BeanUtils.copyProperties(accountFormData, accountStudentZf);
|
BeanUtils.copyProperties(accountFormData, accountStudentZf);
|
||||||
@@ -1922,9 +1913,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
: outpatientRegistrationAddParam.getYbMdtrtCertType());
|
||||||
iAccountService.save(accountStudentZf);
|
iAccountService.save(accountStudentZf);
|
||||||
|
|
||||||
// 截取医保合同号时,先验证是否以"STUDENT"开头
|
Contract contractYb = contractService.getContract(
|
||||||
String ybContractNo = accountContractNo.substring("STUDENT".length());
|
outpatientRegistrationAddParam.getAccountFormData().getContractNo().substring("STUDENT".length()));
|
||||||
Contract contractYb = contractService.getContract(ybContractNo);
|
|
||||||
if (contractYb != null && 1 == contractYb.getYbFlag()) {
|
if (contractYb != null && 1 == contractYb.getYbFlag()) {
|
||||||
// 建立纯医保ACCOUNT
|
// 建立纯医保ACCOUNT
|
||||||
Account accountYb = new Account();
|
Account accountYb = new Account();
|
||||||
@@ -2103,24 +2093,6 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
|
|||||||
return chargeItemService.list(new LambdaQueryWrapper<ChargeItem>().in(ChargeItem::getId, chargeItemIds));
|
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
|
* 类型转换:收费项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:
|
servlet:
|
||||||
# 应用的访问路径
|
# 应用的访问路径
|
||||||
context-path: /openhis
|
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:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.openhis: debug
|
com.openhis: info
|
||||||
org.springframework: warn
|
org.springframework: warn
|
||||||
# MyBatis和MyBatis-Plus日志
|
# MyBatis和MyBatis-Plus日志
|
||||||
com.baomidou.mybatisplus: debug
|
com.baomidou.mybatisplus: debug
|
||||||
com.openhis.mapper: debug
|
com.openhis.web.regdoctorstation.mapper: info
|
||||||
com.openhis.domain: debug
|
|
||||||
# JDBC日志
|
# JDBC日志
|
||||||
org.springframework.jdbc.core: debug
|
org.springframework.jdbc.core: info
|
||||||
# Druid SQL日志
|
# Druid SQL日志
|
||||||
com.alibaba.druid: debug
|
com.alibaba.druid: info
|
||||||
com.alibaba.druid.sql: debug
|
com.alibaba.druid.sql: info
|
||||||
|
|
||||||
# 用户配置
|
# 用户配置
|
||||||
user:
|
user:
|
||||||
@@ -92,9 +91,6 @@ mybatis-plus:
|
|||||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||||
# 加载全局的配置文件
|
# 加载全局的配置文件
|
||||||
configLocation: classpath:mybatis/mybatis-config.xml
|
configLocation: classpath:mybatis/mybatis-config.xml
|
||||||
configuration:
|
|
||||||
# 开启 SQL 日志输出
|
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
|
||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
logic-delete-field: validFlag # 全局逻辑删除的实体字段名
|
logic-delete-field: validFlag # 全局逻辑删除的实体字段名
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
cs.apply_dept_id,
|
cs.apply_dept_id,
|
||||||
cs.apply_dept_name,
|
cs.apply_dept_name,
|
||||||
cs.org_id,
|
cs.org_id,
|
||||||
cs.encounter_id,
|
|
||||||
o.name AS org_name,
|
o.name AS org_name,
|
||||||
cs.main_surgeon_name AS surgeon_name
|
cs.main_surgeon_name AS surgeon_name
|
||||||
FROM op_schedule os
|
FROM op_schedule os
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<!DOCTYPE mapper
|
<!DOCTYPE mapper
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"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" />
|
<id column="id" property="id" />
|
||||||
<result column="consultation_id" property="consultationId" />
|
<result column="consultation_id" property="consultationId" />
|
||||||
<result column="patient_id" property="patientId" />
|
<result column="patient_id" property="patientId" />
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
<result column="requesting_physician_id" property="requestingPhysicianId" />
|
<result column="requesting_physician_id" property="requestingPhysicianId" />
|
||||||
<result column="consultation_request_date" property="consultationRequestDate" />
|
<result column="consultation_request_date" property="consultationRequestDate" />
|
||||||
<result column="invited_object" property="invitedObject" />
|
<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_date" property="consultationDate" />
|
||||||
<result column="consultation_purpose" property="consultationPurpose" />
|
<result column="consultation_purpose" property="consultationPurpose" />
|
||||||
<result column="provisional_diagnosis" property="provisionalDiagnosis" />
|
<result column="provisional_diagnosis" property="provisionalDiagnosis" />
|
||||||
@@ -43,11 +45,6 @@
|
|||||||
<result column="tenant_id" property="tenantId" />
|
<result column="tenant_id" property="tenantId" />
|
||||||
<result column="is_deleted" property="isDeleted" />
|
<result column="is_deleted" property="isDeleted" />
|
||||||
<result column="remark" property="remark" />
|
<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>
|
</resultMap>
|
||||||
|
|
||||||
</mapper>
|
</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", "自动滚费"),
|
AUTO_ROLL_FEES(5, "5", "自动滚费");
|
||||||
|
|
||||||
/**
|
|
||||||
* 手术计费
|
|
||||||
*/
|
|
||||||
SURGERY_BILLING(6, "6", "手术计费");
|
|
||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
private final String code;
|
private final String code;
|
||||||
|
|||||||
@@ -244,9 +244,4 @@ public class ChargeItem extends HisBaseEntity {
|
|||||||
*/
|
*/
|
||||||
private BigDecimal manualAdjustedPrice;
|
private BigDecimal manualAdjustedPrice;
|
||||||
|
|
||||||
/**
|
|
||||||
* 来源业务单据号(如手术申请单号)
|
|
||||||
*/
|
|
||||||
private String sourceBillNo;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,23 +51,4 @@ public interface IClinicRoomService extends IService<ClinicRoom> {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
int deleteClinicRoomById(Long id);
|
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) {
|
public int deleteClinicRoomById(Long id) {
|
||||||
return baseMapper.deleteById(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 $$;
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<sidebar-item
|
<sidebar-item
|
||||||
v-for="(child, index) in item.children"
|
v-for="(child, index) in item.children"
|
||||||
:key="child.path + index"
|
:key="child.path + index"
|
||||||
:is-nest="true"
|
:is-nest="true"
|
||||||
:item="child"
|
:item="child"
|
||||||
:base-path="resolvePath(child.path)"
|
:base-path="resolvePath(child.path)"
|
||||||
class="nest-menu"
|
class="nest-menu"
|
||||||
/>
|
/>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user