577 lines
17 KiB
Markdown
577 lines
17 KiB
Markdown
# QWEN.md - 医院绩效考核管理系统
|
||
|
||
Context for AI coding agents working in this repository.
|
||
|
||
---
|
||
|
||
## Project Overview
|
||
|
||
A **Hospital Performance Management System** (医院绩效考核管理系统) designed for a county-level TCM (Traditional Chinese Medicine) hospital. The system provides comprehensive performance assessment management including:
|
||
|
||
- **Department Management** - Hierarchical department structure
|
||
- **Staff Management** - Employee information and tracking
|
||
- **Assessment Indicators** - KPI definition and management
|
||
- **Performance Evaluation** - Assessment workflow with review process
|
||
- **Data Analytics** - Reports, statistics, and trend analysis
|
||
- **Salary Calculation** - Performance-based payroll processing
|
||
|
||
### Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|-------|------------|
|
||
| **Backend** | FastAPI 0.115+ · SQLAlchemy 2.0 (async) · PostgreSQL · Alembic · Pydantic v2 |
|
||
| **Frontend** | Vue 3 (Composition API) · Element Plus · Pinia · Vite · ECharts |
|
||
| **Auth** | JWT (python-jose) · bcrypt password hashing |
|
||
| **Database** | **PostgreSQL 14+** (asyncpg driver) |
|
||
|
||
---
|
||
|
||
## Quick Start
|
||
|
||
### Prerequisites
|
||
- Python 3.10+
|
||
- Node.js 18+
|
||
- PostgreSQL 14+
|
||
|
||
### Backend Setup
|
||
|
||
```bash
|
||
cd backend
|
||
|
||
# Create virtual environment
|
||
python -m venv venv
|
||
venv\Scripts\activate # Windows
|
||
|
||
# Install dependencies
|
||
pip install -r requirements.txt
|
||
|
||
# Configure environment
|
||
copy .env.example .env
|
||
# Edit .env with your database credentials
|
||
|
||
# Create database
|
||
createdb hospital_performance
|
||
|
||
# Run migrations
|
||
alembic upgrade head
|
||
|
||
# Initialize default data (optional)
|
||
python init_db.py
|
||
|
||
# Start server
|
||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||
```
|
||
|
||
### Frontend Setup
|
||
|
||
```bash
|
||
cd frontend
|
||
|
||
# Install dependencies
|
||
npm install
|
||
|
||
# Start dev server (http://localhost:5173)
|
||
npm run dev
|
||
|
||
# Build for production
|
||
npm run build
|
||
```
|
||
|
||
### Access the Application
|
||
- **Frontend**: http://localhost:5173
|
||
- **API Docs**: http://localhost:8000/api/v1/docs
|
||
- **Health Check**: http://localhost:8000/health
|
||
|
||
### Default Credentials
|
||
- **Username**: `admin`
|
||
- **Password**: `admin123`
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
hospital-performance/
|
||
├── backend/ # Backend service
|
||
│ ├── app/
|
||
│ │ ├── api/v1/ # API routes
|
||
│ │ │ ├── auth.py # Authentication
|
||
│ │ │ ├── departments.py # Department CRUD
|
||
│ │ │ ├── staff.py # Staff CRUD
|
||
│ │ │ ├── indicators.py # Indicator CRUD
|
||
│ │ │ ├── assessments.py # Assessment workflow
|
||
│ │ │ ├── salary.py # Salary calculation
|
||
│ │ │ ├── stats.py # Statistics & reports
|
||
│ │ │ └── finance.py # Finance management
|
||
│ │ ├── core/ # Core modules
|
||
│ │ │ ├── config.py # Settings management
|
||
│ │ │ ├── database.py # DB connection & session
|
||
│ │ │ ├── security.py # JWT & password hashing
|
||
│ │ │ └── init_db.py # DB initialization
|
||
│ │ ├── models/
|
||
│ │ │ └── models.py # SQLAlchemy ORM models
|
||
│ │ ├── schemas/
|
||
│ │ │ └── schemas.py # Pydantic v2 schemas
|
||
│ │ ├── services/ # Business logic layer
|
||
│ │ │ ├── staff_service.py
|
||
│ │ │ ├── department_service.py
|
||
│ │ │ ├── indicator_service.py
|
||
│ │ │ ├── assessment_service.py
|
||
│ │ │ ├── salary_service.py
|
||
│ │ │ └── stats_service.py
|
||
│ │ ├── utils/ # Utility functions
|
||
│ │ └── main.py # FastAPI app factory
|
||
│ ├── alembic/ # Database migrations
|
||
│ ├── tests/ # Test files (empty)
|
||
│ ├── requirements.txt
|
||
│ └── .env.example
|
||
│
|
||
├── frontend/ # Frontend application
|
||
│ ├── src/
|
||
│ │ ├── api/ # API client (Axios)
|
||
│ │ │ ├── request.js # Axios instance + interceptors
|
||
│ │ │ ├── auth.js
|
||
│ │ │ ├── staff.js
|
||
│ │ │ ├── department.js
|
||
│ │ │ ├── indicator.js
|
||
│ │ │ ├── assessment.js
|
||
│ │ │ ├── salary.js
|
||
│ │ │ └── stats.js
|
||
│ │ ├── stores/ # Pinia stores
|
||
│ │ │ ├── user.js # User state & auth
|
||
│ │ │ └── app.js # App-wide state
|
||
│ │ ├── router/
|
||
│ │ │ └── index.js # Vue Router config
|
||
│ │ ├── views/ # Page components
|
||
│ │ │ ├── Login.vue
|
||
│ │ │ ├── Layout.vue # Main layout with sidebar
|
||
│ │ │ ├── Dashboard.vue
|
||
│ │ │ ├── basic/ # Basic data management
|
||
│ │ │ │ ├── Departments.vue
|
||
│ │ │ │ ├── Staff.vue
|
||
│ │ │ │ └── Indicators.vue
|
||
│ │ │ ├── assessment/ # Assessment management
|
||
│ │ │ │ ├── Assessments.vue
|
||
│ │ │ │ └── AssessmentDetail.vue
|
||
│ │ │ ├── salary/ # Salary management
|
||
│ │ │ │ └── Salary.vue
|
||
│ │ │ ├── reports/ # Reports & analytics
|
||
│ │ │ │ └── Reports.vue
|
||
│ │ │ └── finance/ # Finance management
|
||
│ │ │ └── Finance.vue
|
||
│ │ ├── components/ # Reusable components
|
||
│ │ ├── assets/ # Static assets (SCSS)
|
||
│ │ ├── App.vue
|
||
│ │ └── main.js
|
||
│ ├── dist/ # Production build
|
||
│ ├── package.json
|
||
│ └── vite.config.js
|
||
│
|
||
├── docs/ # Documentation
|
||
│ ├── index.md
|
||
│ ├── architecture.md
|
||
│ ├── database.md
|
||
│ ├── api.md
|
||
│ ├── backend.md
|
||
│ └── frontend.md
|
||
│
|
||
├── AGENTS.md # AI agent guidelines
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## Database Schema
|
||
|
||
**Database**: PostgreSQL 14+
|
||
|
||
### Core Tables
|
||
|
||
| Table | Description |
|
||
|-------|-------------|
|
||
| `departments` | Department hierarchy (tree structure with parent_id) |
|
||
| `staff` | Employee information |
|
||
| `indicators` | Assessment indicators/KPIs |
|
||
| `assessments` | Assessment records (header) |
|
||
| `assessment_details` | Assessment line items (scores per indicator) |
|
||
| `salary_records` | Monthly salary calculations |
|
||
| `users` | System users for authentication |
|
||
|
||
### Key Enums
|
||
|
||
```python
|
||
# Department types
|
||
DeptType: CLINICAL, MEDICAL_TECH, MEDICAL_AUXILIARY, ADMIN, LOGISTICS
|
||
|
||
# Staff status
|
||
StaffStatus: ACTIVE, LEAVE, RESIGNED, RETIRED
|
||
|
||
# Assessment workflow status
|
||
AssessmentStatus: DRAFT → SUBMITTED → REVIEWED → FINALIZED (or REJECTED)
|
||
|
||
# Indicator types
|
||
IndicatorType: QUALITY, QUANTITY, EFFICIENCY, SERVICE, COST
|
||
```
|
||
|
||
### Assessment Workflow
|
||
|
||
```
|
||
┌─────────┐ ┌───────────┐ ┌──────────┐ ┌────────────┐
|
||
│ DRAFT │ → │ SUBMITTED │ → │ REVIEWED │ → │ FINALIZED │
|
||
│ 草稿 │ │ 已提交 │ │ 已审核 │ │ 已确认 │
|
||
└─────────┘ └───────────┘ └──────────┘ └────────────┘
|
||
↓
|
||
┌──────────┐
|
||
│ REJECTED │
|
||
│ 已驳回 │
|
||
└──────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Development Conventions
|
||
|
||
### Backend (Python/FastAPI)
|
||
|
||
#### Import Order
|
||
```python
|
||
# 1. Standard library
|
||
from datetime import datetime
|
||
from typing import Optional, List
|
||
|
||
# 2. Third-party
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
# 3. Local imports (absolute paths)
|
||
from app.core.database import get_db
|
||
from app.schemas.schemas import StaffCreate
|
||
from app.services.staff_service import StaffService
|
||
```
|
||
|
||
#### Naming Conventions
|
||
- **Files**: `snake_case.py` (e.g., `staff_service.py`)
|
||
- **Classes**: `PascalCase` (e.g., `StaffService`, `StaffCreate`)
|
||
- **Functions/Variables**: `snake_case` (e.g., `get_staff_list`)
|
||
- **Constants**: `UPPER_SNAKE_CASE`
|
||
|
||
#### Pydantic v2 Pattern
|
||
```python
|
||
class StaffResponse(StaffBase):
|
||
"""员工响应"""
|
||
model_config = ConfigDict(from_attributes=True) # Required for ORM mode
|
||
id: int
|
||
status: StaffStatus
|
||
created_at: datetime
|
||
```
|
||
|
||
#### Async Database Pattern
|
||
```python
|
||
async def get_staff_list(db: AsyncSession, page: int = 1, page_size: int = 20):
|
||
offset = (page - 1) * page_size
|
||
result = await db.execute(select(Staff).offset(offset).limit(page_size))
|
||
staff_list = result.scalars().all()
|
||
|
||
count_result = await db.execute(select(func.count()).select_from(Staff))
|
||
total = count_result.scalar()
|
||
|
||
return staff_list, total
|
||
```
|
||
|
||
#### API Response Format
|
||
```python
|
||
# Standard response
|
||
return {
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": result,
|
||
"total": total, # For paginated responses
|
||
"page": page,
|
||
"page_size": page_size
|
||
}
|
||
|
||
# Error response
|
||
raise HTTPException(status_code=404, detail="员工不存在")
|
||
```
|
||
|
||
#### Service Layer Pattern
|
||
```python
|
||
# API route delegates to service
|
||
@router.get("/staff")
|
||
async def get_staff_list(
|
||
db: AsyncSession = Depends(get_db),
|
||
page: int = 1,
|
||
page_size: int = 20
|
||
):
|
||
staff_list, total = await StaffService.get_list(db, page, page_size)
|
||
return {"data": staff_list, "total": total, "page": page, "page_size": page_size}
|
||
```
|
||
|
||
### Frontend (Vue 3/JavaScript)
|
||
|
||
#### Import Order
|
||
```javascript
|
||
// 1. Vue
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
|
||
// 2. Third-party
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
|
||
// 3. Local (use @ alias)
|
||
import { getStaffList, createStaff } from '@/api/staff'
|
||
import { useUserStore } from '@/stores/user'
|
||
```
|
||
|
||
#### Component Pattern (`<script setup>`)
|
||
```vue
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { getStaffList } from '@/api/staff'
|
||
|
||
// Reactive state
|
||
const loading = ref(false)
|
||
const tableData = ref([])
|
||
const form = reactive({ name: '', status: 'active' })
|
||
|
||
// Functions
|
||
async function loadData() {
|
||
loading.value = true
|
||
try {
|
||
const res = await getStaffList(form)
|
||
tableData.value = res.data || []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadData()
|
||
})
|
||
</script>
|
||
```
|
||
|
||
#### Pinia Store Pattern
|
||
```javascript
|
||
// stores/user.js
|
||
import { defineStore } from 'pinia'
|
||
|
||
export const useUserStore = defineStore('user', () => {
|
||
const token = ref('')
|
||
const userInfo = ref(null)
|
||
|
||
async function login(username, password) {
|
||
const res = await loginApi({ username, password })
|
||
token.value = res.access_token
|
||
localStorage.setItem('token', res.access_token)
|
||
}
|
||
|
||
function logout() {
|
||
token.value = ''
|
||
userInfo.value = null
|
||
localStorage.removeItem('token')
|
||
}
|
||
|
||
return { token, userInfo, login, logout }
|
||
})
|
||
```
|
||
|
||
#### API Layer Pattern
|
||
```javascript
|
||
// api/staff.js
|
||
import request from './request'
|
||
|
||
export function getStaffList(params) {
|
||
return request.get('/staff', { params })
|
||
}
|
||
|
||
export function createStaff(data) {
|
||
return request.post('/staff', data)
|
||
}
|
||
```
|
||
|
||
#### Error Handling
|
||
```javascript
|
||
// User confirmation
|
||
await ElMessageBox.confirm('确定要删除吗?', '提示', { type: 'warning' })
|
||
|
||
// Feedback
|
||
ElMessage.success('操作成功')
|
||
ElMessage.error('操作失败')
|
||
|
||
// Loading state pattern
|
||
try {
|
||
await createStaff(form)
|
||
ElMessage.success('创建成功')
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Key API Endpoints
|
||
|
||
| Method | Endpoint | Description |
|
||
|--------|----------|-------------|
|
||
| POST | `/auth/login` | User login |
|
||
| GET | `/auth/me` | Get current user |
|
||
| GET | `/departments` | List departments |
|
||
| POST | `/departments` | Create department |
|
||
| GET | `/staff` | List staff (paginated) |
|
||
| POST | `/staff` | Create staff |
|
||
| GET | `/indicators` | List indicators |
|
||
| POST | `/assessments` | Create assessment |
|
||
| POST | `/assessments/batch` | Batch create assessments |
|
||
| POST | `/assessments/{id}/submit` | Submit assessment |
|
||
| POST | `/assessments/{id}/review` | Review assessment |
|
||
| POST | `/salary/generate` | Generate salary records |
|
||
| GET | `/stats/department` | Department statistics |
|
||
| GET | `/stats/trend` | Trend analysis |
|
||
|
||
---
|
||
|
||
## Common Tasks
|
||
|
||
### Add a New API Endpoint
|
||
|
||
**Backend:**
|
||
1. Add route in `backend/app/api/v1/<module>.py`
|
||
2. Add service method in `backend/app/services/<module>_service.py`
|
||
3. Add Pydantic schemas in `backend/app/schemas/schemas.py`
|
||
4. Create Alembic migration if DB changes needed
|
||
|
||
**Frontend:**
|
||
1. Add API function in `frontend/src/api/<module>.js`
|
||
2. Create/update view component in `frontend/src/views/`
|
||
3. Add route in `frontend/src/router/index.js`
|
||
|
||
### Database Migration
|
||
|
||
```bash
|
||
cd backend
|
||
|
||
# Generate new migration
|
||
alembic revision --autogenerate -m "Description of changes"
|
||
|
||
# Apply migrations
|
||
alembic upgrade head
|
||
|
||
# Rollback one migration
|
||
alembic downgrade -1
|
||
```
|
||
|
||
### Run Tests (when available)
|
||
|
||
```bash
|
||
cd backend
|
||
pytest
|
||
pytest tests/test_specific.py -v
|
||
pytest -k "test_name" -v
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture Notes
|
||
|
||
### Layered Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Frontend (Vue 3) │
|
||
│ Views → Components → Stores → API │
|
||
└───────────────────┬─────────────────────┘
|
||
│ HTTP/JSON
|
||
▼
|
||
┌─────────────────────────────────────────┐
|
||
│ Backend (FastAPI) │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ API Layer (routes) │ │
|
||
│ └──────────────┬──────────────────┘ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ Service Layer (business logic) │ │
|
||
│ └──────────────┬──────────────────┘ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────┐ │
|
||
│ │ ORM Layer (SQLAlchemy) │ │
|
||
│ └──────────────┬──────────────────┘ │
|
||
└───────────────────┼─────────────────────┘
|
||
▼
|
||
┌─────────────────────────────────────────┐
|
||
│ Database (PostgreSQL) │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
### Authentication Flow
|
||
|
||
1. Client sends credentials to `/auth/login`
|
||
2. Server validates and returns JWT token
|
||
3. Client stores token in `localStorage`
|
||
4. Client includes `Authorization: Bearer {token}` in subsequent requests
|
||
5. Server validates token via `get_current_user` dependency
|
||
6. 401 responses trigger redirect to login page
|
||
|
||
### Salary Calculation Logic
|
||
|
||
```
|
||
Assessment (FINALIZED status)
|
||
↓
|
||
Read performance score
|
||
↓
|
||
Apply performance ratio (from staff.perf_ratio)
|
||
↓
|
||
Calculate: performance_bonus = base_salary × (score/100) × perf_ratio
|
||
↓
|
||
Generate SalaryRecord with total_salary = base + bonus - deduction + allowance
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Backend Issues
|
||
|
||
**Database connection error:**
|
||
- Ensure PostgreSQL is running
|
||
- Check `DATABASE_URL` in `.env`
|
||
- Verify database exists: `createdb hospital_performance`
|
||
- Check PostgreSQL credentials (username, password, port)
|
||
|
||
**Migration issues:**
|
||
```bash
|
||
# Check migration status
|
||
alembic current
|
||
|
||
# Show pending migrations
|
||
alembic history
|
||
|
||
# Clear and re-migrate (dev only)
|
||
alembic downgrade base
|
||
alembic upgrade head
|
||
```
|
||
|
||
### Frontend Issues
|
||
|
||
**API requests failing:**
|
||
- Ensure backend is running on port 8000
|
||
- Check proxy config in `vite.config.js`
|
||
- Verify token in localStorage is valid
|
||
|
||
**Build errors:**
|
||
```bash
|
||
# Clean and reinstall
|
||
rm -rf node_modules package-lock.json
|
||
npm install
|
||
```
|
||
|
||
---
|
||
|
||
## Related Documentation
|
||
|
||
- [Architecture](docs/architecture.md) - System architecture overview
|
||
- [Database](docs/database.md) - ER diagrams and table structures
|
||
- [API](docs/api.md) - Detailed API documentation
|
||
- [Backend](docs/backend.md) - Backend development guide
|
||
- [Frontend](docs/frontend.md) - Frontend development guide
|