96-门诊医生站会诊申请确认界面和97-门诊会诊申请管理界面全部功能。

This commit is contained in:
weixin_45799331
2026-02-11 14:16:30 +08:00
parent 3ab7ea1898
commit 1747291f41
67 changed files with 213 additions and 6087 deletions

View File

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

View File

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

View File

@@ -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"
]
}

View File

@@ -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*

View File

@@ -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*

View File

@@ -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())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,11 +59,6 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
return R.fail(400, "备注长度不能超过500个字符");
}
// 检查诊室名称在同卫生机构下是否已存在
if (clinicRoomService.existsByOrgNameAndRoomName(clinicRoom.getOrgName(), clinicRoom.getRoomName())) {
return R.fail(400, "当前卫生机构下已存在该诊室名称");
}
// 新增诊室
int result = clinicRoomService.insertClinicRoom(clinicRoom);
if (result > 0) {
@@ -98,11 +93,6 @@ public class ClinicRoomAppServiceImpl implements IClinicRoomAppService {
return R.fail(404, "诊室不存在");
}
// 检查诊室名称在同卫生机构下是否已存在(排除当前记录)
if (clinicRoomService.existsByOrgNameAndRoomNameExcludeId(clinicRoom.getOrgName(), clinicRoom.getRoomName(), clinicRoom.getId())) {
return R.fail(400, "当前卫生机构下已存在该诊室名称");
}
// 更新诊室
int result = clinicRoomService.updateClinicRoom(clinicRoom);
if (result > 0) {

View File

@@ -87,6 +87,30 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
return R.ok(list);
}
@Transactional
@Override
public R<?> getTodayMySchedule() {
// 获取今天的日期
LocalDate today = LocalDate.now();
DayOfWeek dayOfWeek = today.getDayOfWeek();
// 将 Java 的 DayOfWeek 转换为字符串表示
String weekdayStr = convertDayOfWeekToString(dayOfWeek);
// 获取当前登录医生的ID需要从SecurityUtils获取
// 如果没有SecurityUtils可以从参数传入或使用其他方式获取
// Long currentDoctorId = SecurityUtils.getLoginUser().getPractitionerId();
// 查询当前医生今天的排班
LambdaQueryWrapper<DoctorSchedule> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DoctorSchedule::getWeekday, weekdayStr) // 根据星期几查询
// .eq(DoctorSchedule::getDoctorId, currentDoctorId) // 只查询当前医生的排班
.eq(DoctorSchedule::getIsStopped, false); // 只查询未停止的排班
List<DoctorSchedule> list = doctorScheduleService.list(queryWrapper);
return R.ok(list);
}
/**
* 将 DayOfWeek 转换为字符串表示
* @param dayOfWeek DayOfWeek枚举
@@ -113,44 +137,6 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
}
}
@Override
public R<?> getTodayMySchedule() {
try {
// 获取当前登录用户信息
com.core.common.core.domain.model.LoginUser loginUser = com.core.common.utils.SecurityUtils.getLoginUser();
Long doctorId = loginUser.getPractitionerId();
// 如果没有获取到医生ID尝试使用用户ID
if (doctorId == null || doctorId == 0) {
doctorId = loginUser.getUserId();
System.out.println("Using userId as doctorId: " + doctorId);
} else {
System.out.println("Using practitionerId as doctorId: " + doctorId);
}
// 获取今天的日期
LocalDate today = LocalDate.now();
System.out.println("Querying schedule for date: " + today);
// 查询当前医生今天的排班信息从SchedulePool表
LambdaQueryWrapper<SchedulePool> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SchedulePool::getScheduleDate, today) // 根据具体日期查询
.eq(SchedulePool::getDoctorId, doctorId) // 根据医生ID查询
.eq(SchedulePool::getStatus, 1); // 只查询可用的排班
List<SchedulePool> list = schedulePoolService.list(queryWrapper);
System.out.println("Found " + list.size() + " schedules for doctorId: " + doctorId);
// 为了支持多种日程类型,我们可以在这里整合来自不同数据源的信息
// 目前只返回排班信息,但为以后扩展预留空间
return R.ok(list);
} catch (Exception e) {
System.err.println("Error getting today's schedule: " + e.getMessage());
e.printStackTrace();
return R.fail("获取排班信息失败:" + e.getMessage());
}
}
@Override
public R<?> addDoctorSchedule(DoctorSchedule doctorSchedule) {
if (ObjectUtil.isEmpty(doctorSchedule)) {

View File

@@ -71,39 +71,10 @@ public class TicketAppServiceImpl implements ITicketAppService {
}
try {
// 3. 号源列表来自排班表(adm_doctor_schedule)需确保clinical_ticket中存在对应记录
Ticket existingTicket = ticketService.selectTicketById(ticketId);
if (existingTicket == null) {
DoctorSchedule schedule = doctorScheduleMapper.selectById(slotId);
if (schedule == null) {
return R.fail("排班记录不存在");
}
Ticket newTicket = new Ticket();
newTicket.setId(slotId);
newTicket.setDoctor(schedule.getDoctor());
newTicket.setDepartment(String.valueOf(schedule.getDeptId()));
newTicket.setDoctorId(schedule.getDoctorId());
newTicket.setDepartmentId(schedule.getDeptId());
String registerItem = schedule.getRegisterItem();
if (registerItem != null && registerItem.contains("专家")) {
newTicket.setTicketType("expert");
} else {
newTicket.setTicketType("general");
}
newTicket.setStatus("unbooked");
int totalFee = (schedule.getRegisterFee() != null ? schedule.getRegisterFee() : 0)
+ (schedule.getDiagnosisFee() != null ? schedule.getDiagnosisFee() : 0);
newTicket.setFee(String.valueOf(totalFee));
String timeRange = schedule.getStartTime() + "-" + schedule.getEndTime();
newTicket.setTime(LocalDate.now() + " " + timeRange);
// 使用MyBatis-Plus的save方法支持手动设置ID
ticketService.save(newTicket);
}
// 4. 执行预约逻辑
// 3. 执行原有的预约逻辑
int result = ticketService.bookTicket(params);
if (result > 0) {
// 5. 预约成功后,更新排班表状态
// 4. 预约成功后,更新排班表状态
DoctorSchedule schedule = new DoctorSchedule();
schedule.setId(slotId); // 对应 XML 中的 WHERE id = #{id}
schedule.setIsStopped(true); // 设置为已预约
@@ -115,12 +86,14 @@ public class TicketAppServiceImpl implements ITicketAppService {
if (updateCount > 0) {
return R.ok("预约成功并已更新排班状态");
} else {
// 如果更新失败,可能需要根据业务逻辑决定是否回滚预约
return R.ok("预约成功,但排班状态更新失败");
}
} else {
return R.fail("预约失败");
}
} catch (Exception e) {
// e.printStackTrace();
log.error(e.getMessage());
return R.fail("系统异常:" + e.getMessage());
}

View File

@@ -89,13 +89,4 @@ public class DoctorScheduleController {
return R.ok(doctorScheduleAppService.getTodayDoctorScheduleList());
}
/*
* 获取当前登录医生今日排班List
*
* */
@GetMapping("/today-my-schedule")
public R<?> getTodayMySchedule() {
return doctorScheduleAppService.getTodayMySchedule();
}
}

View File

@@ -2,14 +2,11 @@ package com.openhis.web.clinicalmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.SecurityUtils;
import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.User;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
@@ -101,29 +98,4 @@ public class SurgicalScheduleController {
surgicalScheduleAppService.exportSurgerySchedule(opScheduleDto, response);
}
/**
* 验证密码
* @param password 密码
* @return 结果
*/
@PostMapping(value = "/checkPassword")
public R<?> checkPassword(@RequestParam String password) {
try {
//获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return R.fail("用户未登录");
}
// 直接使用 SecurityUtils.matchesPassword 进行密码验证
boolean isMatch = SecurityUtils.matchesPassword(password, loginUser.getPassword());
if (isMatch) {
return R.ok(true);
} else {
return R.fail("密码错误");
}
} catch (Exception e) {
return R.fail("密码验证失败:" + e.getMessage());
}
}
}

View File

@@ -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)));
}
}

View File

@@ -41,12 +41,10 @@ public class ConsultationController {
@ApiParam("就诊ID可选不传则查询当前医生的所有会诊申请")
@RequestParam(required = false) Long encounterId) {
try {
log.info("获取会诊列表encounterId: {}", encounterId);
List<ConsultationRequestDto> list = consultationAppService.getConsultationList(encounterId);
log.info("获取会诊列表成功,共 {} 条记录", list != null ? list.size() : 0);
return R.ok(list);
} catch (Exception e) {
log.error("获取会诊列表失败encounterId: {}", encounterId, e);
log.error("获取会诊列表失败", e);
return R.fail("获取会诊列表失败: " + e.getMessage());
}
}

View File

@@ -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)));
}
}

View File

@@ -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("签名失败,请确保会诊已确认");
}
}

View File

@@ -3,7 +3,6 @@ package com.openhis.web.consultation.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.apache.ibatis.type.Alias;
import java.io.Serializable;
import java.util.Date;
@@ -16,7 +15,6 @@ import java.util.Date;
*/
@Data
@TableName("consultation_request")
@Alias("webConsultationRequest")
public class ConsultationRequest implements Serializable {
private static final long serialVersionUID = 1L;

View File

@@ -1,14 +1,17 @@
package com.openhis.consultation.mapper;
package com.openhis.web.consultation.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.openhis.consultation.domain.ConsultationRequest;
import com.openhis.web.consultation.domain.ConsultationRequest;
import org.apache.ibatis.annotations.Mapper;
/**
* 会诊申请 Mapper接口
* 会诊申请Mapper接口
*
* @author his
* @author system
* @date 2026-01-29
*/
@Mapper
public interface ConsultationRequestMapper extends BaseMapper<ConsultationRequest> {
}

View File

@@ -629,16 +629,7 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setStatusEnum(ChargeItemStatus.DRAFT.getValue()); // 收费状态
chargeItem.setBusNo(AssignSeqEnum.CHARGE_ITEM_NO.getPrefix().concat(medicationRequest.getBusNo()));
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
if (adviceSaveDto.getGenerateSourceEnum() != null) {
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue()); // 生成来源
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
@@ -768,16 +759,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 保存耗材费用项
chargeItem = new ChargeItem();
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
if (adviceSaveDto.getGenerateSourceEnum() != null) {
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setTenantId(tenantId); // 补全租户ID
chargeItem.setCreateBy(currentUsername); // 补全创建人
@@ -919,16 +900,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 保存时保存诊疗费用项
if (is_save) {
chargeItem = new ChargeItem();
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
if (adviceSaveDto.getGenerateSourceEnum() != null) {
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setTenantId(tenantId); // 补全租户ID
chargeItem.setCreateBy(currentUsername); // 补全创建人

View File

@@ -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, "传染病报告卡数据");
}
}

View File

@@ -190,15 +190,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
@Override
public R<?> prePayment(PrePaymentDto prePaymentDto) {
logger.info("预结算:参数:" + JSON.toJSONString(prePaymentDto));
// 查收费项(支持手术计费)
List<ChargeItem> chargeItemList;
if (prePaymentDto.getGenerateSourceEnum() != null && prePaymentDto.getGenerateSourceEnum() == 2) {
// 手术计费根据generateSourceEnum和sourceBillNo过滤
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds(), prePaymentDto.getGenerateSourceEnum(), prePaymentDto.getSourceBillNo());
} else {
// 普通门诊划价
chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
}
// 查收费项
List<ChargeItem> chargeItemList = getChargeItems(prePaymentDto.getChargeItemIds());
if (chargeItemList.isEmpty()) {
return R.fail("未选择收费项");
}
@@ -1906,13 +1899,11 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
: outpatientRegistrationAddParam.getYbMdtrtCertType());
iAccountService.save(accountZf);
}
String accountContractNo = outpatientRegistrationAddParam.getAccountFormData().getContractNo();
if (!CommonConstants.BusinessName.DEFAULT_CONTRACT_NO
.equals(accountContractNo)
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
&& !CommonConstants.BusinessName.DEFAULT_STUDENT_CONTRACT_NO
.equals(accountContractNo)
&& accountContractNo.length() > 11
&& accountContractNo.startsWith("STUDENT")) {
.equals(outpatientRegistrationAddParam.getAccountFormData().getContractNo())
&& outpatientRegistrationAddParam.getAccountFormData().getContractNo().length() > 11) {
// 建立学生自费ACCOUNT
Account accountStudentZf = new Account();
BeanUtils.copyProperties(accountFormData, accountStudentZf);
@@ -1922,9 +1913,8 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
: outpatientRegistrationAddParam.getYbMdtrtCertType());
iAccountService.save(accountStudentZf);
// 截取医保合同号时,先验证是否以"STUDENT"开头
String ybContractNo = accountContractNo.substring("STUDENT".length());
Contract contractYb = contractService.getContract(ybContractNo);
Contract contractYb = contractService.getContract(
outpatientRegistrationAddParam.getAccountFormData().getContractNo().substring("STUDENT".length()));
if (contractYb != null && 1 == contractYb.getYbFlag()) {
// 建立纯医保ACCOUNT
Account accountYb = new Account();
@@ -2103,24 +2093,6 @@ public class PaymentRecServiceImpl implements IPaymentRecService {
return chargeItemService.list(new LambdaQueryWrapper<ChargeItem>().in(ChargeItem::getId, chargeItemIds));
}
/**
* 获取收费项集合(支持手术计费)
*
* @param chargeItemIds 收费项id集合
* @param generateSourceEnum 账单生成来源2手术计费
* @param sourceBillNo 来源业务单据号(手术单号)
* @return 收费项集合
*/
private List<ChargeItem> getChargeItems(List<Long> chargeItemIds, Integer generateSourceEnum, String sourceBillNo) {
LambdaQueryWrapper<ChargeItem> wrapper = new LambdaQueryWrapper<ChargeItem>().in(ChargeItem::getId, chargeItemIds);
// 如果是手术计费,需要额外过滤条件
if (generateSourceEnum != null && generateSourceEnum == 2 && sourceBillNo != null) {
wrapper.eq(ChargeItem::getGenerateSourceEnum, 2)
.eq(ChargeItem::getSourceBillNo, sourceBillNo);
}
return chargeItemService.list(wrapper);
}
/**
* 类型转换收费项chargeItem转成PaymentedItemModel
*

View File

@@ -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));
}
}

View File

@@ -91,14 +91,3 @@ server:
servlet:
# 应用的访问路径
context-path: /openhis
# 开发环境日志配置
logging:
level:
com.openhis: info
com.baomidou.mybatisplus: info
com.openhis.mapper: info
com.openhis.domain: info
org.springframework.jdbc.core: info
com.alibaba.druid: info
com.alibaba.druid.sql: info

View File

@@ -32,17 +32,16 @@ server:
# 日志配置
logging:
level:
com.openhis: debug
com.openhis: info
org.springframework: warn
# MyBatis和MyBatis-Plus日志
com.baomidou.mybatisplus: debug
com.openhis.mapper: debug
com.openhis.domain: debug
com.openhis.web.regdoctorstation.mapper: info
# JDBC日志
org.springframework.jdbc.core: debug
org.springframework.jdbc.core: info
# Druid SQL日志
com.alibaba.druid: debug
com.alibaba.druid.sql: debug
com.alibaba.druid: info
com.alibaba.druid.sql: info
# 用户配置
user:
@@ -92,9 +91,6 @@ mybatis-plus:
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
configuration:
# 开启 SQL 日志输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: validFlag # 全局逻辑删除的实体字段名

View File

@@ -29,7 +29,6 @@
cs.apply_dept_id,
cs.apply_dept_name,
cs.org_id,
cs.encounter_id,
o.name AS org_name,
cs.main_surgeon_name AS surgeon_name
FROM op_schedule os

View File

@@ -2,10 +2,10 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.openhis.consultation.mapper.ConsultationRequestMapper">
<mapper namespace="com.openhis.web.consultation.mapper.ConsultationRequestMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.openhis.consultation.domain.ConsultationRequest">
<resultMap id="BaseResultMap" type="com.openhis.web.consultation.domain.ConsultationRequest">
<id column="id" property="id" />
<result column="consultation_id" property="consultationId" />
<result column="patient_id" property="patientId" />
@@ -22,6 +22,8 @@
<result column="requesting_physician_id" property="requestingPhysicianId" />
<result column="consultation_request_date" property="consultationRequestDate" />
<result column="invited_object" property="invitedObject" />
<result column="invited_department_id" property="invitedDepartmentId" />
<result column="invited_physician_id" property="invitedPhysicianId" />
<result column="consultation_date" property="consultationDate" />
<result column="consultation_purpose" property="consultationPurpose" />
<result column="provisional_diagnosis" property="provisionalDiagnosis" />
@@ -43,11 +45,6 @@
<result column="tenant_id" property="tenantId" />
<result column="is_deleted" property="isDeleted" />
<result column="remark" property="remark" />
<result column="consultation_activity_id" property="consultationActivityId" />
<result column="consultation_activity_name" property="consultationActivityName" />
<result column="confirming_physician_name" property="confirmingPhysicianName" />
<result column="confirming_department_name" property="confirmingDepartmentName" />
<result column="confirming_physician_participation" property="confirmingPhysicianParticipation" />
</resultMap>
</mapper>

View File

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

View File

@@ -39,12 +39,7 @@ public enum GenerateSource implements HisEnumInterface {
/**
* 自动滚费
*/
AUTO_ROLL_FEES(5, "5", "自动滚费"),
/**
* 手术计费
*/
SURGERY_BILLING(6, "6", "手术计费");
AUTO_ROLL_FEES(5, "5", "自动滚费");
private final Integer value;
private final String code;

View File

@@ -244,9 +244,4 @@ public class ChargeItem extends HisBaseEntity {
*/
private BigDecimal manualAdjustedPrice;
/**
* 来源业务单据号(如手术申请单号)
*/
private String sourceBillNo;
}

View File

@@ -51,23 +51,4 @@ public interface IClinicRoomService extends IService<ClinicRoom> {
* @return 结果
*/
int deleteClinicRoomById(Long id);
/**
* 检查指定卫生机构下是否已存在相同诊室名称(新增时使用)
*
* @param orgName 卫生机构名称
* @param roomName 诊室名称
* @return 是否存在
*/
boolean existsByOrgNameAndRoomName(String orgName, String roomName);
/**
* 检查指定卫生机构下是否已存在相同诊室名称(编辑时使用,排除当前记录)
*
* @param orgName 卫生机构名称
* @param roomName 诊室名称
* @param id 当前记录ID
* @return 是否存在
*/
boolean existsByOrgNameAndRoomNameExcludeId(String orgName, String roomName, Long id);
}

View File

@@ -44,21 +44,4 @@ public class ClinicRoomServiceImpl extends ServiceImpl<ClinicRoomMapper, ClinicR
public int deleteClinicRoomById(Long id) {
return baseMapper.deleteById(id);
}
@Override
public boolean existsByOrgNameAndRoomName(String orgName, String roomName) {
LambdaQueryWrapper<ClinicRoom> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ClinicRoom::getOrgName, orgName)
.eq(ClinicRoom::getRoomName, roomName);
return count(queryWrapper) > 0;
}
@Override
public boolean existsByOrgNameAndRoomNameExcludeId(String orgName, String roomName, Long id) {
LambdaQueryWrapper<ClinicRoom> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ClinicRoom::getOrgName, orgName)
.eq(ClinicRoom::getRoomName, roomName)
.ne(ClinicRoom::getId, id);
return count(queryWrapper) > 0;
}
}

View File

@@ -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";
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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);
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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; // 签名失败
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,118 +0,0 @@
-- 更新会诊管理模块的菜单项,以满足需求文档要求 (PostgreSQL格式)
DO $$
DECLARE
consultation_menu_id BIGINT;
consultation_request_menu_id BIGINT;
consultation_confirmation_menu_id BIGINT;
consultation_record_menu_id BIGINT;
BEGIN
-- 获取会诊管理菜单ID如果不存在则创建
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
IF NOT FOUND THEN
INSERT INTO sys_menu
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('会诊管理', 0, 10, 'consultationmanagement', '', '', 'ConsultationManagement', '1', '0', 'M', '0', '0', '', 'operation', 'admin', NOW(), 'admin', NOW(), '会诊管理菜单');
SELECT menu_id INTO consultation_menu_id FROM sys_menu WHERE menu_name = '会诊管理' LIMIT 1;
END IF;
-- 获取会诊申请菜单ID如果不存在则创建
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
IF NOT FOUND THEN
INSERT INTO sys_menu
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('会诊申请', consultation_menu_id, 1, 'consultationrequest', 'clinicmanagement/consultationrequest/index', '', 'ConsultationRequest', '1', '0', 'C', '0', '0', 'consultation:request:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊申请菜单');
SELECT menu_id INTO consultation_request_menu_id FROM sys_menu WHERE menu_name = '会诊申请' AND parent_id = consultation_menu_id LIMIT 1;
END IF;
-- 更新或插入会诊申请按钮权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请查询', consultation_request_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:query', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:query' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请新增', consultation_request_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:add', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:add' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请修改', consultation_request_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:edit' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请删除', consultation_request_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:remove' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请提交', consultation_request_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:submit', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:submit' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请结束', consultation_request_menu_id, 6, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:end', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:end' AND parent_id = consultation_request_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊申请作废', consultation_request_menu_id, 7, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:request:cancel', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:request:cancel' AND parent_id = consultation_request_menu_id);
-- 获取会诊确认菜单ID如果不存在则创建
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
IF NOT FOUND THEN
INSERT INTO sys_menu
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('会诊确认', consultation_menu_id, 2, 'consultationconfirmation', 'clinicmanagement/consultationconfirmation/index', '', 'ConsultationConfirmation', '1', '0', 'C', '0', '0', 'consultation:confirmation:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊确认菜单');
SELECT menu_id INTO consultation_confirmation_menu_id FROM sys_menu WHERE menu_name = '会诊确认' AND parent_id = consultation_menu_id LIMIT 1;
END IF;
-- 更新或插入会诊确认按钮权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊确认查询', consultation_confirmation_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:query', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:query' AND parent_id = consultation_confirmation_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊确认新增', consultation_confirmation_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:add', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:add' AND parent_id = consultation_confirmation_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊确认修改', consultation_confirmation_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:edit' AND parent_id = consultation_confirmation_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊确认删除', consultation_confirmation_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:remove' AND parent_id = consultation_confirmation_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊确认操作', consultation_confirmation_menu_id, 5, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:confirmation:confirm', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:confirmation:confirm' AND parent_id = consultation_confirmation_menu_id);
-- 获取会诊记录菜单ID如果不存在则创建
SELECT menu_id INTO consultation_record_menu_id FROM sys_menu WHERE menu_name = '会诊记录' AND parent_id = consultation_menu_id LIMIT 1;
IF NOT FOUND THEN
INSERT INTO sys_menu
(menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
VALUES
('会诊记录', consultation_menu_id, 3, 'consultationrecord', 'clinicmanagement/consultationrecord/index', '', 'ConsultationRecord', '1', '0', 'C', '0', '0', 'consultation:record:view', 'form', 'admin', NOW(), 'admin', NOW(), '会诊记录菜单');
END IF;
-- 更新或插入会诊记录按钮权限
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊记录查询', consultation_record_menu_id, 1, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:query', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:query' AND parent_id = consultation_record_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊记录新增', consultation_record_menu_id, 2, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:add', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:add' AND parent_id = consultation_record_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊记录修改', consultation_record_menu_id, 3, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:edit', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:edit' AND parent_id = consultation_record_menu_id);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
SELECT '会诊记录删除', consultation_record_menu_id, 4, '', '', '', '', '1', '0', 'F', '0', '0', 'consultation:record:remove', '#', 'admin', NOW(), 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'consultation:record:remove' AND parent_id = consultation_record_menu_id);
END $$;

File diff suppressed because one or more lines are too long