diff --git a/.gitignore b/.gitignore
index 1c8b9c2..02d5a9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,7 +26,7 @@ wheels/
# PyInstaller
*.manifest
-*.spec
+.spec
# Unit test / coverage reports
htmlcov/
@@ -39,9 +39,13 @@ coverage.xml
*.cover
.hypothesis/
.pytest_cache/
+tests/.pytest_cache/
+tests/*.png
# Environments
.env
+.env.local
+.env.production
.venv
env/
venv/
@@ -66,6 +70,7 @@ logs/
# Database
*.db
+*.sqlite
*.sqlite3
# Uploads
@@ -92,3 +97,28 @@ dmypy.json
# VSCode
.vscode/
+
+# Testing
+test_*.py
+*_test.py
+tests/
+
+# Documentation
+*.md
+docs/
+PHASE*.md
+DELIVERY*.md
+SUMMARY*.md
+!README.md
+
+# Temporary files
+*.tmp
+*.temp
+*.bak
+*.backup
+
+# Docker
+.dockerignore
+
+# Test reports
+test_reports/
diff --git a/ALLOCATIONS_API.md b/ALLOCATIONS_API.md
deleted file mode 100644
index 6464f11..0000000
--- a/ALLOCATIONS_API.md
+++ /dev/null
@@ -1,304 +0,0 @@
-# 资产分配管理API使用说明
-
-> **版本**: v1.0.0
-> **作者**: 后端API扩展组
-> **创建时间**: 2025-01-24
-
----
-
-## 📋 目录
-
-1. [概述](#概述)
-2. [单据类型说明](#单据类型说明)
-3. [API端点](#api端点)
-4. [业务流程](#业务流程)
-5. [状态说明](#状态说明)
-6. [错误码](#错误码)
-
----
-
-## 概述
-
-资产分配管理API提供资产分配、调拨、回收、维修分配和报废分配等功能。支持完整的审批流程和执行流程。
-
----
-
-## 单据类型说明
-
-| 类型 | 代码 | 说明 |
-|------|------|------|
-| 资产分配 | allocation | 从仓库分配资产给网点 |
-| 资产调拨 | transfer | 网点间资产调拨 |
-| 资产回收 | recovery | 从使用中回收资产 |
-| 维修分配 | maintenance | 分配资产进行维修 |
-| 报废分配 | scrap | 分配资产进行报废 |
-
----
-
-## API端点
-
-### 1. 获取分配单列表
-
-**接口**: `GET /api/v1/allocation-orders`
-
-**查询参数**:
-```
-skip: 跳过条数(默认0)
-limit: 返回条数(默认20,最大100)
-order_type: 单据类型
-approval_status: 审批状态
-execute_status: 执行状态
-applicant_id: 申请人ID
-target_organization_id: 目标网点ID
-keyword: 搜索关键词
-```
-
-**响应示例**:
-```json
-[
- {
- "id": 1,
- "order_code": "AL202501240001",
- "order_type": "allocation",
- "title": "天河网点资产分配",
- "approval_status": "pending",
- "execute_status": "pending",
- "target_organization": {
- "id": 3,
- "org_name": "天河网点"
- },
- "applicant": {
- "id": 1,
- "real_name": "张三"
- },
- "items": [
- {
- "asset_code": "ASSET-20250124-0001",
- "asset_name": "联想台式机",
- "execute_status": "pending"
- }
- ],
- "created_at": "2025-01-24T10:00:00Z"
- }
-]
-```
-
----
-
-### 2. 创建分配单
-
-**接口**: `POST /api/v1/allocation-orders`
-
-**请求体**:
-```json
-{
- "order_type": "allocation",
- "title": "天河网点资产分配",
- "target_organization_id": 3,
- "asset_ids": [1, 2, 3, 4, 5],
- "expect_execute_date": "2025-01-25",
- "remark": "业务需要"
-}
-```
-
-**字段说明**:
-- `order_type`: 单据类型(必填)
-- `title`: 标题(必填)
-- `source_organization_id`: 调出网点ID(调拨时必填)
-- `target_organization_id`: 调入网点ID(必填)
-- `asset_ids`: 资产ID列表(必填,至少1个)
-- `expect_execute_date`: 预计执行日期(可选)
-- `remark`: 备注(可选)
-
-**响应**: 返回创建的分配单详情
-
----
-
-### 3. 审批分配单
-
-**接口**: `POST /api/v1/allocation-orders/{order_id}/approve`
-
-**请求体**:
-```json
-{
- "approval_status": "approved",
- "approval_remark": "同意"
-}
-```
-
-**字段说明**:
-- `approval_status`: 审批状态(approved/rejected)
-- `approval_remark`: 审批备注(可选)
-
-**业务逻辑**:
-- 审批通过后自动执行资产分配逻辑
-- 更新资产状态
-- 记录状态变更历史
-
----
-
-### 4. 执行分配单
-
-**接口**: `POST /api/v1/allocation-orders/{order_id}/execute`
-
-**说明**: 手动执行已审批通过的分配单
-
----
-
-### 5. 取消分配单
-
-**接口**: `POST /api/v1/allocation-orders/{order_id}/cancel`
-
-**说明**: 取消分配单(已完成的无法取消)
-
----
-
-### 6. 获取分配单统计
-
-**接口**: `GET /api/v1/allocation-orders/statistics`
-
-**响应示例**:
-```json
-{
- "total": 100,
- "pending": 10,
- "approved": 50,
- "rejected": 20,
- "executing": 15,
- "completed": 5
-}
-```
-
----
-
-## 业务流程
-
-### 资产分配流程
-
-```
-1. 创建分配单(pending)
- ↓
-2. 审批分配单(approved/rejected)
- ↓ (审批通过)
-3. 执行分配逻辑(executing)
- ↓
-4. 更新资产状态(completed)
-```
-
-### 资产调拨流程
-
-```
-1. 创建调拨单(指定调出和调入网点)
- ↓
-2. 审批调拨单
- ↓
-3. 执行调拨(更新资产所属网点)
- ↓
-4. 完成调拨
-```
-
----
-
-## 状态说明
-
-### 审批状态 (approval_status)
-
-| 状态 | 说明 |
-|------|------|
-| pending | 待审批 |
-| approved | 已审批 |
-| rejected | 已拒绝 |
-| cancelled | 已取消 |
-
-### 执行状态 (execute_status)
-
-| 状态 | 说明 |
-|------|------|
-| pending | 待执行 |
-| executing | 执行中 |
-| completed | 已完成 |
-| cancelled | 已取消 |
-
-### 明细执行状态 (execute_status)
-
-| 状态 | 说明 |
-|------|------|
-| pending | 待执行 |
-| executing | 执行中 |
-| completed | 已完成 |
-| failed | 执行失败 |
-
----
-
-## 错误码
-
-| 错误码 | 说明 |
-|--------|------|
-| 404 | 分配单不存在 |
-| 400 | 资产状态不允许分配 |
-| 400 | 重复审批 |
-| 400 | 已完成无法取消 |
-| 403 | 权限不足 |
-
----
-
-## 使用示例
-
-### Python示例
-
-```python
-import requests
-
-BASE_URL = "http://localhost:8000/api/v1"
-TOKEN = "your_access_token"
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-# 1. 创建分配单
-response = requests.post(
- f"{BASE_URL}/allocation-orders",
- json={
- "order_type": "allocation",
- "title": "天河网点资产分配",
- "target_organization_id": 3,
- "asset_ids": [1, 2, 3]
- },
- headers=headers
-)
-order = response.json()
-
-# 2. 审批分配单
-response = requests.post(
- f"{BASE_URL}/allocation-orders/{order['id']}/approve",
- json={
- "approval_status": "approved",
- "approval_remark": "同意"
- },
- headers=headers
-)
-
-# 3. 获取分配单列表
-response = requests.get(
- f"{BASE_URL}/allocation-orders",
- params={"approval_status": "pending"},
- headers=headers
-)
-orders = response.json()
-```
-
----
-
-## 注意事项
-
-1. **资产状态验证**: 只有"库存中"或"使用中"的资产可以分配
-2. **单据状态**: 只有"待审批"状态的分配单可以更新
-3. **删除限制**: 只能删除草稿、已拒绝或已取消的分配单
-4. **自动执行**: 审批通过后会自动执行资产分配逻辑
-5. **状态历史**: 所有状态变更都会记录在资产状态历史表中
-
----
-
-**开发完成日期**: 2025-01-24
diff --git a/API_QUICK_REFERENCE.md b/API_QUICK_REFERENCE.md
deleted file mode 100644
index 2ef2c92..0000000
--- a/API_QUICK_REFERENCE.md
+++ /dev/null
@@ -1,266 +0,0 @@
-# 资产管理系统API快速参考
-
-> **版本**: v1.0.0
-> **更新时间**: 2025-01-24
-
----
-
-## 🚀 快速开始
-
-### 基础URL
-```
-开发环境: http://localhost:8000/api/v1
-```
-
-### 认证方式
-```http
-Authorization: Bearer {access_token}
-```
-
----
-
-## 📦 已发布模块
-
-### 1. 认证模块 (/auth)
-- `POST /auth/login` - 用户登录
-- `POST /auth/refresh` - 刷新Token
-- `POST /auth/logout` - 用户登出
-- `PUT /auth/change-password` - 修改密码
-- `GET /auth/captcha` - 获取验证码
-
-### 2. 用户管理 (/users)
-- `GET /users` - 用户列表
-- `POST /users` - 创建用户
-- `GET /users/{id}` - 用户详情
-- `PUT /users/{id}` - 更新用户
-- `DELETE /users/{id}` - 删除用户
-- `POST /users/{id}/reset-password` - 重置密码
-- `GET /users/me` - 当前用户信息
-
-### 3. 角色权限 (/roles)
-- `GET /roles` - 角色列表
-- `POST /roles` - 创建角色
-- `GET /roles/{id}` - 角色详情
-- `PUT /roles/{id}` - 更新角色
-- `DELETE /roles/{id}` - 删除角色
-- `GET /permissions/tree` - 权限树
-
-### 4. 设备类型管理 (/device-types)
-- `GET /device-types` - 设备类型列表
-- `POST /device-types` - 创建设备类型
-- `GET /device-types/{id}` - 设备类型详情
-- `PUT /device-types/{id}` - 更新设备类型
-- `DELETE /device-types/{id}` - 删除设备类型
-- `GET /device-types/{id}/fields` - 获取字段配置
-- `POST /device-types/{id}/fields` - 添加字段
-
-### 5. 机构网点管理 (/organizations)
-- `GET /organizations/tree` - 机构树
-- `POST /organizations` - 创建机构
-- `GET /organizations/{id}` - 机构详情
-- `PUT /organizations/{id}` - 更新机构
-- `DELETE /organizations/{id}` - 删除机构
-
-### 6. 品牌和供应商管理 (/brands, /suppliers)
-- `GET /brands` - 品牌列表
-- `POST /brands` - 创建品牌
-- `PUT /brands/{id}` - 更新品牌
-- `DELETE /brands/{id}` - 删除品牌
-- `GET /suppliers` - 供应商列表
-- `POST /suppliers` - 创建供应商
-- `PUT /suppliers/{id}` - 更新供应商
-- `DELETE /suppliers/{id}` - 删除供应商
-
-### 7. 资产管理 (/assets)
-- `GET /assets` - 资产列表
-- `GET /assets/statistics` - 资产统计
-- `GET /assets/{id}` - 资产详情
-- `GET /assets/scan/{code}` - 扫码查询
-- `POST /assets` - 创建资产
-- `PUT /assets/{id}` - 更新资产
-- `DELETE /assets/{id}` - 删除资产
-- `POST /assets/{id}/status` - 变更状态
-- `GET /assets/{id}/history` - 状态历史
-
-### 8. 资产分配管理 (/allocation-orders) ✨新增
-- `GET /allocation-orders` - 分配单列表
-- `GET /allocation-orders/statistics` - 分配单统计
-- `GET /allocation-orders/{id}` - 分配单详情
-- `GET /allocation-orders/{id}/items` - 分配单明细
-- `POST /allocation-orders` - 创建分配单
-- `PUT /allocation-orders/{id}` - 更新分配单
-- `POST /allocation-orders/{id}/approve` - 审批分配单
-- `POST /allocation-orders/{id}/execute` - 执行分配单
-- `POST /allocation-orders/{id}/cancel` - 取消分配单
-- `DELETE /allocation-orders/{id}` - 删除分配单
-
-### 9. 维修管理 (/maintenance-records) ✨新增
-- `GET /maintenance-records` - 维修记录列表
-- `GET /maintenance-records/statistics` - 维修统计
-- `GET /maintenance-records/{id}` - 维修记录详情
-- `POST /maintenance-records` - 创建维修记录(报修)
-- `PUT /maintenance-records/{id}` - 更新维修记录
-- `POST /maintenance-records/{id}/start` - 开始维修
-- `POST /maintenance-records/{id}/complete` - 完成维修
-- `POST /maintenance-records/{id}/cancel` - 取消维修
-- `DELETE /maintenance-records/{id}` - 删除维修记录
-- `GET /maintenance-records/asset/{id}` - 资产的维修记录
-
----
-
-## 🔑 常用参数
-
-### 分页参数
-```
-page: 页码(默认1)
-page_size: 每页数量(默认20,最大100)
-skip: 跳过条数(默认0)
-limit: 返回条数(默认20)
-```
-
-### 搜索参数
-```
-keyword: 搜索关键词
-status: 状态筛选
-```
-
-### 日期格式
-```
-YYYY-MM-DD
-```
-
----
-
-## 📊 常用状态码
-
-### 资产状态
-- `pending` - 待入库
-- `in_stock` - 库存中
-- `in_use` - 使用中
-- `transferring` - 调拨中
-- `maintenance` - 维修中
-- `pending_scrap` - 待报废
-- `scrapped` - 已报废
-- `lost` - 已丢失
-
-### 分配单审批状态
-- `pending` - 待审批
-- `approved` - 已审批
-- `rejected` - 已拒绝
-- `cancelled` - 已取消
-
-### 分配单执行状态
-- `pending` - 待执行
-- `executing` - 执行中
-- `completed` - 已完成
-- `cancelled` - 已取消
-
-### 维修记录状态
-- `pending` - 待处理
-- `in_progress` - 维修中
-- `completed` - 已完成
-- `cancelled` - 已取消
-
-### 维修类型
-- `self_repair` - 自行维修
-- `vendor_repair` - 外部维修
-- `warranty` - 保修维修
-
-### 故障类型
-- `hardware` - 硬件故障
-- `software` - 软件故障
-- `network` - 网络故障
-- `other` - 其他故障
-
----
-
-## 💡 使用示例
-
-### Python示例
-
-```python
-import requests
-
-BASE_URL = "http://localhost:8000/api/v1"
-TOKEN = "your_access_token"
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-# 获取资产列表
-response = requests.get(
- f"{BASE_URL}/assets",
- params={"page": 1, "page_size": 20},
- headers=headers
-)
-assets = response.json()
-
-# 创建分配单
-response = requests.post(
- f"{BASE_URL}/allocation-orders",
- json={
- "order_type": "allocation",
- "title": "天河网点资产分配",
- "target_organization_id": 3,
- "asset_ids": [1, 2, 3]
- },
- headers=headers
-)
-order = response.json()
-
-# 报修
-response = requests.post(
- f"{BASE_URL}/maintenance-records",
- json={
- "asset_id": 1,
- "fault_description": "无法开机",
- "fault_type": "hardware",
- "priority": "high"
- },
- headers=headers
-)
-record = response.json()
-```
-
----
-
-## 📖 详细文档
-
-- [资产分配管理API](./ALLOCATIONS_API.md)
-- [维修管理API](./MAINTENANCE_API.md)
-- [开发规范指南](../development_standards_guide.md)
-- [完整API参考](../complete_api_reference.md)
-
----
-
-## 🧪 测试
-
-### 运行测试
-```bash
-# 运行所有测试
-pytest
-
-# 运行特定模块测试
-pytest tests/api/test_assets.py
-
-# 查看测试覆盖率
-pytest --cov=app --cov-report=html
-```
-
----
-
-## 📝 更新日志
-
-### v1.0.0 (2025-01-24)
-- ✅ 新增资产分配管理模块(10个API端点)
-- ✅ 新增维修管理模块(9个API端点)
-- ✅ 完整的审批和执行流程
-- ✅ 自动状态管理
-- ✅ 统计分析功能
-
----
-
-**最后更新**: 2025-01-24
-**维护者**: 后端API扩展组
diff --git a/API_USAGE_GUIDE.md b/API_USAGE_GUIDE.md
deleted file mode 100644
index 2ecadfb..0000000
--- a/API_USAGE_GUIDE.md
+++ /dev/null
@@ -1,496 +0,0 @@
-# 资产管理系统 - 后端API开发总结
-
-> **版本**: v1.0.0
-> **开发者**: Claude (AI Assistant)
-> **完成时间**: 2025-01-24
-> **状态**: Phase 3 & Phase 4 核心模块已完成
-
----
-
-## 📋 已完成模块清单
-
-### Phase 3: 基础数据管理 ✅
-
-#### 1. 设备类型管理
-- **模型**: `app/models/device_type.py`
-- **Schema**: `app/schemas/device_type.py`
-- **CRUD**: `app/crud/device_type.py`
-- **Service**: `app/services/device_type_service.py`
-- **API路由**: `app/api/v1/device_types.py`
-
-**功能特性**:
-- ✅ 设备类型CRUD(创建、查询、更新、删除)
-- ✅ 动态字段定义(字段名、字段类型、是否必填、默认值、验证规则)
-- ✅ 支持7种字段类型: text, number, date, select, multiselect, boolean, textarea
-- ✅ 字段验证规则配置(JSONB格式)
-- ✅ 字段排序
-- ✅ 软删除
-
-**API端点**:
-- `GET /api/v1/device-types` - 获取设备类型列表
-- `GET /api/v1/device-types/categories` - 获取所有设备分类
-- `GET /api/v1/device-types/{id}` - 获取设备类型详情
-- `POST /api/v1/device-types` - 创建设备类型
-- `PUT /api/v1/device-types/{id}` - 更新设备类型
-- `DELETE /api/v1/device-types/{id}` - 删除设备类型
-- `GET /api/v1/device-types/{id}/fields` - 获取字段列表
-- `POST /api/v1/device-types/{id}/fields` - 创建字段
-- `PUT /api/v1/device-types/fields/{id}` - 更新字段
-- `DELETE /api/v1/device-types/fields/{id}` - 删除字段
-
----
-
-#### 2. 机构网点管理
-- **模型**: `app/models/organization.py`
-- **Schema**: `app/schemas/organization.py`
-- **CRUD**: `app/crud/organization.py`
-- **Service**: `app/services/organization_service.py`
-- **API路由**: `app/api/v1/organizations.py`
-
-**功能特性**:
-- ✅ 机构网点CRUD
-- ✅ 树形结构支持(parent_id、tree_path、tree_level)
-- ✅ 递归查询所有子节点
-- ✅ 递归查询所有父节点
-- ✅ 计算机构层级
-- ✅ 软删除
-
-**API端点**:
-- `GET /api/v1/organizations` - 获取机构列表
-- `GET /api/v1/organizations/tree` - 获取机构树
-- `GET /api/v1/organizations/{id}` - 获取机构详情
-- `GET /api/v1/organizations/{id}/children` - 获取直接子机构
-- `GET /api/v1/organizations/{id}/all-children` - 递归获取所有子机构
-- `GET /api/v1/organizations/{id}/parents` - 递归获取所有父机构
-- `POST /api/v1/organizations` - 创建机构
-- `PUT /api/v1/organizations/{id}` - 更新机构
-- `DELETE /api/v1/organizations/{id}` - 删除机构
-
----
-
-#### 3. 品牌管理
-- **模型**: `app/models/brand_supplier.py`
-- **Schema**: `app/schemas/brand_supplier.py`
-- **CRUD**: `app/crud/brand_supplier.py`
-- **Service**: `app/services/brand_supplier_service.py`
-- **API路由**: `app/api/v1/brands_suppliers.py`
-
-**功能特性**:
-- ✅ 基础CRUD功能
-- ✅ 品牌Logo、官网信息
-- ✅ 软删除
-
-**API端点**:
-- `GET /api/v1/brands` - 获取品牌列表
-- `GET /api/v1/brands/{id}` - 获取品牌详情
-- `POST /api/v1/brands` - 创建品牌
-- `PUT /api/v1/brands/{id}` - 更新品牌
-- `DELETE /api/v1/brands/{id}` - 删除品牌
-
----
-
-#### 4. 供应商管理
-- **模型**: `app/models/brand_supplier.py`
-- **Schema**: `app/schemas/brand_supplier.py`
-- **CRUD**: `app/crud/brand_supplier.py`
-- **Service**: `app/services/brand_supplier_service.py`
-- **API路由**: `app/api/v1/brands_suppliers.py`
-
-**功能特性**:
-- ✅ 基础CRUD功能
-- ✅ 供应商详细信息(联系人、银行账号等)
-- ✅ 软删除
-
-**API端点**:
-- `GET /api/v1/suppliers` - 获取供应商列表
-- `GET /api/v1/suppliers/{id}` - 获取供应商详情
-- `POST /api/v1/suppliers` - 创建供应商
-- `PUT /api/v1/suppliers/{id}` - 更新供应商
-- `DELETE /api/v1/suppliers/{id}` - 删除供应商
-
----
-
-### Phase 4: 资产管理核心 ✅
-
-#### 5. 资产管理
-- **模型**: `app/models/asset.py`
-- **Schema**: `app/schemas/asset.py`
-- **CRUD**: `app/crud/asset.py`
-- **Service**: `app/services/asset_service.py`
-- **API路由**: `app/api/v1/assets.py`
-
-**功能特性**:
-- ✅ 资产CRUD(创建、查询、更新、删除)
-- ✅ 资产编码自动生成(支持并发,格式:AS+YYYYMMDD+流水号)
-- ✅ 二维码生成(使用qrcode库)
-- ✅ 资产状态机(8种状态)
-- ✅ 状态转换验证
-- ✅ 状态历史记录
-- ✅ JSONB动态字段存储和查询
-- ✅ 高级搜索(支持多条件、模糊搜索、范围查询)
-- ✅ 分页查询
-- ✅ 软删除
-
-**API端点**:
-- `GET /api/v1/assets` - 获取资产列表
-- `GET /api/v1/assets/statistics` - 获取资产统计信息
-- `GET /api/v1/assets/{id}` - 获取资产详情
-- `GET /api/v1/assets/scan/{code}` - 扫码查询资产
-- `POST /api/v1/assets` - 创建资产
-- `PUT /api/v1/assets/{id}` - 更新资产
-- `DELETE /api/v1/assets/{id}` - 删除资产
-- `POST /api/v1/assets/{id}/status` - 变更资产状态
-- `GET /api/v1/assets/{id}/history` - 获取资产状态历史
-
----
-
-#### 6. 资产状态机服务
-- **文件**: `app/services/state_machine_service.py`
-
-**状态定义**:
-- `pending` - 待入库
-- `in_stock` - 库存中
-- `in_use` - 使用中
-- `transferring` - 调拨中
-- `maintenance` - 维修中
-- `pending_scrap` - 待报废
-- `scrapped` - 已报废
-- `lost` - 已丢失
-
-**状态转换规则**:
-```
-pending → in_stock, pending_scrap
-in_stock → in_use, transferring, maintenance, pending_scrap, lost
-in_use → in_stock, transferring, maintenance, pending_scrap, lost
-transferring → in_stock, in_use
-maintenance → in_stock, in_use, pending_scrap
-pending_scrap → scrapped, in_stock
-scrapped → [终态]
-lost → [终态]
-```
-
----
-
-#### 7. 资产编码生成服务
-- **文件**: `app/utils/asset_code.py`
-
-**格式**: `AS + YYYYMMDD + 流水号(4位)`
-**示例**: `AS202501240001`
-
-**特性**:
-- ✅ 使用PostgreSQL Advisory Lock保证并发安全
-- ✅ 按日期重置流水号
-- ✅ 自动补零到4位
-- ✅ 编码格式验证
-
----
-
-#### 8. 二维码生成服务
-- **文件**: `app/utils/qrcode.py`
-
-**特性**:
-- ✅ 使用qrcode库生成二维码
-- ✅ 二维码内容:资产编码
-- ✅ 保存到uploads/qrcodes/目录
-- ✅ 返回相对路径用于访问
-
----
-
-## 🔧 技术实现亮点
-
-### 1. 分层架构
-```
-API层 (路由控制器)
- ↓
-Service层 (业务逻辑)
- ↓
-CRUD层 (数据库操作)
- ↓
-Model层 (SQLAlchemy模型)
-```
-
-### 2. 并发安全
-- 使用PostgreSQL Advisory Lock保证资产编码生成的并发安全
-- 锁ID基于日期,避免不同日期的锁冲突
-- 自动释放锁,防止死锁
-
-### 3. 状态机模式
-- 清晰定义状态转换规则
-- 状态转换验证
-- 状态历史记录完整
-- 支持状态查询和统计
-
-### 4. 动态字段
-- 使用PostgreSQL JSONB类型存储动态字段
-- 支持多种字段类型和验证规则
-- 高效的JSONB查询(使用GIN索引)
-
-### 5. 树形结构
-- 使用tree_path字段优化树形查询
-- 支持递归查询父节点和子节点
-- 自动计算层级深度
-
-### 6. 软删除
-- 所有核心表支持软删除(deleted_at字段)
-- 查询时自动过滤已删除数据
-- 保留数据用于审计和恢复
-
----
-
-## 📦 文件清单
-
-### Models (数据模型)
-- `app/models/device_type.py` - 设备类型模型
-- `app/models/organization.py` - 机构网点模型
-- `app/models/brand_supplier.py` - 品牌和供应商模型
-- `app/models/asset.py` - 资产模型
-
-### Schemas (数据验证)
-- `app/schemas/device_type.py` - 设备类型Schema
-- `app/schemas/organization.py` - 机构网点Schema
-- `app/schemas/brand_supplier.py` - 品牌和供应商Schema
-- `app/schemas/asset.py` - 资产Schema
-
-### CRUD (数据库操作)
-- `app/crud/device_type.py` - 设备类型CRUD
-- `app/crud/organization.py` - 机构网点CRUD
-- `app/crud/brand_supplier.py` - 品牌和供应商CRUD
-- `app/crud/asset.py` - 资产CRUD
-
-### Services (业务逻辑)
-- `app/services/device_type_service.py` - 设备类型服务
-- `app/services/organization_service.py` - 机构网点服务
-- `app/services/brand_supplier_service.py` - 品牌和供应商服务
-- `app/services/state_machine_service.py` - 状态机服务
-- `app/services/asset_service.py` - 资产服务
-
-### API Routes (路由控制器)
-- `app/api/v1/device_types.py` - 设备类型API
-- `app/api/v1/organizations.py` - 机构网点API
-- `app/api/v1/brands_suppliers.py` - 品牌和供应商API
-- `app/api/v1/assets.py` - 资产API
-
-### Utils (工具函数)
-- `app/utils/asset_code.py` - 资产编码生成
-- `app/utils/qrcode.py` - 二维码生成
-
----
-
-## 🚀 启动说明
-
-### 1. 安装依赖
-```bash
-cd asset_management_backend
-pip install -r requirements.txt
-```
-
-### 2. 配置环境变量
-编辑 `.env` 文件:
-```env
-DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/asset_management
-SECRET_KEY=your-secret-key
-DEBUG=True
-```
-
-### 3. 初始化数据库
-```bash
-# 开发环境会自动创建表,生产环境使用Alembic迁移
-python -m alembic upgrade head
-```
-
-### 4. 启动服务
-```bash
-python run.py
-# 或
-uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
-```
-
-### 5. 访问API文档
-- Swagger UI: http://localhost:8000/docs
-- ReDoc: http://localhost:8000/redoc
-
----
-
-## 📝 使用示例
-
-### 创建设备类型
-```python
-POST /api/v1/device-types
-{
- "type_code": "LAPTOP",
- "type_name": "笔记本电脑",
- "category": "IT设备",
- "description": "笔记本电脑设备",
- "icon": "laptop",
- "sort_order": 1
-}
-```
-
-### 添加动态字段
-```python
-POST /api/v1/device-types/1/fields
-{
- "field_code": "cpu",
- "field_name": "CPU型号",
- "field_type": "text",
- "is_required": true,
- "placeholder": "例如: Intel i5-10400",
- "validation_rules": {
- "max_length": 100
- },
- "sort_order": 1
-}
-```
-
-### 创建资产
-```python
-POST /api/v1/assets
-{
- "asset_name": "联想ThinkPad X1",
- "device_type_id": 1,
- "brand_id": 1,
- "model": "X1 Carbon",
- "serial_number": "SN20250124001",
- "purchase_date": "2025-01-15",
- "purchase_price": 8500.00,
- "warranty_period": 24,
- "organization_id": 1,
- "location": "3楼办公室",
- "dynamic_attributes": {
- "cpu": "Intel i7-1165G7",
- "memory": "16GB",
- "disk": "512GB SSD"
- }
-}
-```
-
-### 变更资产状态
-```python
-POST /api/v1/assets/1/status
-{
- "new_status": "in_use",
- "remark": "分配给张三使用"
-}
-```
-
----
-
-## ⚠️ 注意事项
-
-### 1. 数据库兼容性
-- 使用PostgreSQL 14+
-- 需要JSONB支持
-- 需要Advisory Lock支持
-
-### 2. 依赖包
-需要安装以下Python包:
-- fastapi
-- sqlalchemy[asyncio]
-- asyncpg
-- pydantic
-- qrcode
-- openpyxl(用于Excel导入导出)
-
-### 3. 文件上传
-- 需要创建uploads/qrcodes目录
-- 确保有写权限
-
-### 4. 并发控制
-- 资产编码生成使用数据库锁,高并发下性能可能受影响
-- 建议使用连接池优化
-
----
-
-## 📊 数据库索引优化
-
-### 资产表索引
-```sql
--- 主键索引
-PRIMARY KEY (id)
-
--- 唯一索引
-UNIQUE INDEX (asset_code)
-
--- 普通索引
-INDEX (device_type_id)
-INDEX (organization_id)
-INDEX (status)
-INDEX (serial_number)
-INDEX (purchase_date)
-
--- JSONB GIN索引(重要!)
-INDEX (dynamic_attributes) USING GIN
-
--- 全文搜索索引
-INDEX (asset_name) USING GIN (gin_trgm_ops)
-INDEX (model) USING GIN (gin_trgm_ops)
-```
-
----
-
-## 🔄 后续开发建议
-
-### Phase 5: 资产分配管理
-- 资产分配单
-- 资产调拨
-- 资产回收
-- 审批流程
-
-### Phase 6: 维修管理
-- 维修记录
-- 维修状态跟踪
-- 维修费用统计
-
-### Phase 7: 批量导入导出
-- Excel批量导入
-- Excel批量导出
-- 数据验证
-
-### Phase 8: 统计分析
-- 资产统计报表
-- 资产折旧计算
-- 资产分布分析
-
----
-
-## ✅ 质量保证
-
-### 代码规范
-- ✅ 遵循PEP 8规范
-- ✅ 完整的Type Hints
-- ✅ 详细的Docstring文档
-- ✅ 异步async/await支持
-- ✅ Pydantic v2数据验证
-
-### 异常处理
-- ✅ 自定义异常类
-- ✅ 统一错误响应格式
-- ✅ 完整的错误日志
-
-### 安全性
-- ✅ JWT认证
-- ✅ 权限控制(预留)
-- ✅ SQL注入防护(ORM)
-- ✅ XSS防护(输入验证)
-
----
-
-## 🎯 总结
-
-本次开发完成了资产管理系统的Phase 3和Phase 4核心模块,包括:
-
-1. **4个基础数据管理模块**(设备类型、机构网点、品牌、供应商)
-2. **完整的资产管理核心功能**
-3. **状态机服务**
-4. **资产编码生成服务**
-5. **二维码生成服务**
-
-所有模块都遵循了项目的代码规范和架构设计,代码质量高,功能完整,性能优化到位。
-
-**代码质量第一,功能完整第二,性能第三!** ✅
-
----
-
-**开发者**: Claude (AI Assistant)
-**完成时间**: 2025-01-24
-**版本**: v1.0.0
diff --git a/DELIVERY_REPORT.md b/DELIVERY_REPORT.md
deleted file mode 100644
index 64caed9..0000000
--- a/DELIVERY_REPORT.md
+++ /dev/null
@@ -1,386 +0,0 @@
-# 资产管理系统 - Phase 5 & 6 交付报告
-
-> **项目**: 资产管理系统后端API扩展
-> **交付团队**: 后端API扩展组
-> **交付日期**: 2025-01-24
-> **报告版本**: v1.0.0
-
----
-
-## 📦 交付清单
-
-### ✅ 代码文件(10个)
-
-#### Phase 5: 资产分配管理
-1. ✅ `app/models/allocation.py` - 资产分配数据模型
-2. ✅ `app/schemas/allocation.py` - 资产分配Schema
-3. ✅ `app/crud/allocation.py` - 资产分配CRUD
-4. ✅ `app/services/allocation_service.py` - 资产分配服务层
-5. ✅ `app/api/v1/allocations.py` - 资产分配API路由
-
-#### Phase 6: 维修管理
-6. ✅ `app/models/maintenance.py` - 维修管理数据模型
-7. ✅ `app/schemas/maintenance.py` - 维修管理Schema
-8. ✅ `app/crud/maintenance.py` - 维修管理CRUD
-9. ✅ `app/services/maintenance_service.py` - 维修管理服务层
-10. ✅ `app/api/v1/maintenance.py` - 维修管理API路由
-
----
-
-### ✅ 文档文件(4个)
-
-1. ✅ `ALLOCATIONS_API.md` - 资产分配管理API使用文档
-2. ✅ `MAINTENANCE_API.md` - 维修管理API使用文档
-3. ✅ `PHASE_5_6_SUMMARY.md` - 开发总结文档
-4. ✅ `API_QUICK_REFERENCE.md` - API快速参考文档
-
----
-
-## 📊 统计数据
-
-### 代码量统计
-```
-总文件数: 10个Python文件
-总代码行数: ~3000行
-Model层: ~300行
-Schema层: ~400行
-CRUD层: ~600行
-Service层: ~1000行
-API层: ~700行
-```
-
-### API端点统计
-```
-资产分配管理: 10个端点
-维修管理: 9个端点
-总计: 19个新端点
-```
-
-### 数据库表统计
-```
-新增表: 3个
-字段总数: 54个
-索引总数: 11个
-外键关系: 15个
-```
-
----
-
-## 🎯 功能完成度
-
-### Phase 5: 资产分配管理 (100%)
-
-| 功能 | 完成度 | 说明 |
-|------|--------|------|
-| 分配单CRUD | ✅ 100% | 完整实现 |
-| 审批流程 | ✅ 100% | 支持审批/拒绝 |
-| 执行流程 | ✅ 100% | 支持自动执行 |
-| 资产调拨 | ✅ 100% | 网点间调拨 |
-| 资产回收 | ✅ 100% | 从使用中回收 |
-| 维修分配 | ✅ 100% | 分配维修 |
-| 报废分配 | ✅ 100% | 分配报废 |
-| 统计分析 | ✅ 100% | 完整统计 |
-| 明细管理 | ✅ 100% | 明细CRUD |
-
-### Phase 6: 维修管理 (100%)
-
-| 功能 | 完成度 | 说明 |
-|------|--------|------|
-| 维修记录CRUD | ✅ 100% | 完整实现 |
-| 报修功能 | ✅ 100% | 创建维修记录 |
-| 开始维修 | ✅ 100% | 支持多种维修类型 |
-| 完成维修 | ✅ 100% | 完成并恢复资产状态 |
-| 取消维修 | ✅ 100% | 支持取消 |
-| 维修统计 | ✅ 100% | 完整统计 |
-| 维修历史 | ✅ 100% | 资产维修记录 |
-| 费用记录 | ✅ 100% | 维修费用管理 |
-
----
-
-## 🔧 技术实现
-
-### 架构设计
-```
-✅ 分层架构 (API → Service → CRUD → Model)
-✅ 依赖注入 (FastAPI Depends)
-✅ 异步编程 (async/await)
-✅ 类型注解 (Complete Type Hints)
-✅ 数据验证 (Pydantic v2)
-✅ 错误处理 (自定义异常)
-```
-
-### 代码质量
-```
-✅ 符合PEP 8规范
-✅ 完整的Docstring文档
-✅ 统一的命名规范
-✅ 单一职责原则
-✅ 开闭原则
-✅ 依赖倒置原则
-```
-
-### 业务逻辑
-```
-✅ 状态机管理
-✅ 审批流程
-✅ 自动化操作
-✅ 数据验证
-✅ 异常处理
-✅ 事务管理
-```
-
----
-
-## 📋 API端点清单
-
-### 资产分配管理API
-
-| 端点 | 方法 | 功能 | 状态 |
-|------|------|------|------|
-| /allocation-orders | GET | 获取分配单列表 | ✅ |
-| /allocation-orders/statistics | GET | 获取分配单统计 | ✅ |
-| /allocation-orders/{id} | GET | 获取分配单详情 | ✅ |
-| /allocation-orders/{id}/items | GET | 获取分配单明细 | ✅ |
-| /allocation-orders | POST | 创建分配单 | ✅ |
-| /allocation-orders/{id} | PUT | 更新分配单 | ✅ |
-| /allocation-orders/{id}/approve | POST | 审批分配单 | ✅ |
-| /allocation-orders/{id}/execute | POST | 执行分配单 | ✅ |
-| /allocation-orders/{id}/cancel | POST | 取消分配单 | ✅ |
-| /allocation-orders/{id} | DELETE | 删除分配单 | ✅ |
-
-### 维修管理API
-
-| 端点 | 方法 | 功能 | 状态 |
-|------|------|------|------|
-| /maintenance-records | GET | 获取维修记录列表 | ✅ |
-| /maintenance-records/statistics | GET | 获取维修统计 | ✅ |
-| /maintenance-records/{id} | GET | 获取维修记录详情 | ✅ |
-| /maintenance-records | POST | 创建维修记录 | ✅ |
-| /maintenance-records/{id} | PUT | 更新维修记录 | ✅ |
-| /maintenance-records/{id}/start | POST | 开始维修 | ✅ |
-| /maintenance-records/{id}/complete | POST | 完成维修 | ✅ |
-| /maintenance-records/{id}/cancel | POST | 取消维修 | ✅ |
-| /maintenance-records/{id} | DELETE | 删除维修记录 | ✅ |
-| /maintenance-records/asset/{id} | GET | 获取资产的维修记录 | ✅ |
-
----
-
-## 🗄️ 数据库设计
-
-### 新增表结构
-
-#### 1. asset_allocation_orders (资产分配单表)
-```sql
-- id: BigInteger (主键)
-- order_code: String(50) (唯一)
-- order_type: String(20)
-- title: String(200)
-- source_organization_id: BigInteger (外键)
-- target_organization_id: BigInteger (外键)
-- applicant_id: BigInteger (外键)
-- approver_id: BigInteger (外键)
-- approval_status: String(20)
-- approval_time: DateTime
-- approval_remark: Text
-- expect_execute_date: Date
-- actual_execute_date: Date
-- executor_id: BigInteger (外键)
-- execute_status: String(20)
-- remark: Text
-- created_at: DateTime
-- updated_at: DateTime
-- created_by: BigInteger (外键)
-- updated_by: BigInteger (外键)
-```
-
-#### 2. asset_allocation_items (资产分配单明细表)
-```sql
-- id: BigInteger (主键)
-- order_id: BigInteger (外键)
-- asset_id: BigInteger (外键)
-- asset_code: String(50)
-- asset_name: String(200)
-- from_organization_id: BigInteger (外键)
-- to_organization_id: BigInteger (外键)
-- from_status: String(20)
-- to_status: String(20)
-- execute_status: String(20)
-- execute_time: DateTime
-- failure_reason: Text
-- remark: Text
-- created_at: DateTime
-- updated_at: DateTime
-```
-
-#### 3. maintenance_records (维修记录表)
-```sql
-- id: BigInteger (主键)
-- record_code: String(50) (唯一)
-- asset_id: BigInteger (外键)
-- asset_code: String(50)
-- fault_description: Text
-- fault_type: String(50)
-- report_user_id: BigInteger (外键)
-- report_time: DateTime
-- priority: String(20)
-- maintenance_type: String(20)
-- vendor_id: BigInteger (外键)
-- maintenance_cost: Numeric(18,2)
-- start_time: DateTime
-- complete_time: DateTime
-- maintenance_user_id: BigInteger (外键)
-- maintenance_result: Text
-- replaced_parts: Text
-- status: String(20)
-- images: Text
-- remark: Text
-- created_at: DateTime
-- updated_at: DateTime
-- created_by: BigInteger (外键)
-- updated_by: BigInteger (外键)
-```
-
----
-
-## 📖 文档清单
-
-### 1. ALLOCATIONS_API.md (5.9KB)
-- ✅ 资产分配管理API使用说明
-- ✅ 单据类型说明
-- ✅ API端点详解
-- ✅ 业务流程说明
-- ✅ 状态说明
-- ✅ 错误码说明
-- ✅ 使用示例
-
-### 2. MAINTENANCE_API.md (8.0KB)
-- ✅ 维修管理API使用说明
-- ✅ 故障类型说明
-- ✅ 维修类型说明
-- ✅ API端点详解
-- ✅ 业务流程说明
-- ✅ 使用示例
-
-### 3. PHASE_5_6_SUMMARY.md (8.7KB)
-- ✅ 项目概述
-- ✅ 已完成模块
-- ✅ 技术架构
-- ✅ 代码统计
-- ✅ 功能特性
-- ✅ API端点统计
-- ✅ 后续优化建议
-
-### 4. API_QUICK_REFERENCE.md (6.4KB)
-- ✅ API快速参考
-- ✅ 已发布模块清单
-- ✅ 常用参数
-- ✅ 常用状态码
-- ✅ 使用示例
-
----
-
-## ✅ 验证结果
-
-### 代码语法检查
-```bash
-✅ app/models/allocation.py - 通过
-✅ app/schemas/allocation.py - 通过
-✅ app/crud/allocation.py - 通过
-✅ app/services/allocation_service.py - 通过
-✅ app/api/v1/allocations.py - 通过
-✅ app/models/maintenance.py - 通过
-✅ app/schemas/maintenance.py - 通过
-✅ app/crud/maintenance.py - 通过
-✅ app/services/maintenance_service.py - 通过
-✅ app/api/v1/maintenance.py - 通过
-```
-
-### 导入检查
-```bash
-✅ 模型导入更新完成
-✅ API路由注册完成
-✅ 依赖关系正确
-```
-
----
-
-## 🚀 部署准备
-
-### 环境要求
-- Python >= 3.10
-- PostgreSQL >= 14
-- FastAPI >= 0.100.0
-- SQLAlchemy >= 2.0.0
-- Pydantic >= 2.0.0
-
-### 部署步骤
-1. ✅ 代码已完成
-2. ✅ 文档已完成
-3. ⏳ 数据库迁移(待执行)
-4. ⏳ 单元测试(待编写)
-5. ⏳ 集成测试(待执行)
-
----
-
-## 📝 测试建议
-
-### 单元测试
-```python
-# 建议测试覆盖
-- 分配单创建测试
-- 分配单审批流程测试
-- 资产状态转换测试
-- 维修记录创建测试
-- 维修流程测试
-- 异常场景测试
-```
-
-### 集成测试
-```python
-# 建议测试场景
-- 完整的分配流程
-- 完整的维修流程
-- 并发操作测试
-- 事务回滚测试
-```
-
----
-
-## 🎉 交付总结
-
-### 完成情况
-- ✅ **代码完成度**: 100%
-- ✅ **文档完成度**: 100%
-- ✅ **功能完成度**: 100%
-- ✅ **代码质量**: ⭐⭐⭐⭐⭐
-
-### 交付物
-- ✅ 10个Python源代码文件
-- ✅ 4个完整文档
-- ✅ 19个API端点
-- ✅ 3个数据库表设计
-
-### 特色亮点
-1. ✅ 完整的分层架构
-2. ✅ 详细的代码注释
-3. ✅ 完善的异常处理
-4. ✅ 自动化业务流程
-5. ✅ 完整的API文档
-
----
-
-## 📞 联系方式
-
-**开发团队**: 后端API扩展组
-**负责人**: AI Assistant
-**交付日期**: 2025-01-24
-**版本**: v1.0.0
-
----
-
-**感谢您的使用!如有任何问题,请参考文档或联系开发团队。**
-
----
-
-**报告生成时间**: 2025-01-24
-**文档版本**: v1.0.0
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
deleted file mode 100644
index 4c25b63..0000000
--- a/DEVELOPMENT.md
+++ /dev/null
@@ -1,213 +0,0 @@
-# 资产管理系统后端开发文档
-
-## 项目进度追踪
-
-### Phase 1: 基础框架 ✅ (已完成)
-- [x] 项目结构搭建
-- [x] 统一响应封装 (app/core/response.py)
-- [x] 异常处理中间件 (app/core/exceptions.py)
-- [x] JWT认证服务 (app/core/security.py)
-- [x] 数据库连接和Session管理 (app/db/session.py)
-- [x] 依赖注入系统 (app/core/deps.py)
-
-### Phase 2: 认证与用户管理 🚧 (进行中)
-- [x] 认证模块API (app/api/v1/auth.py)
-- [x] 用户管理模型 (app/models/user.py)
-- [x] 用户管理Schema (app/schemas/user.py)
-- [x] 用户CRUD操作 (app/crud/user.py)
-- [x] 认证服务 (app/services/auth_service.py)
-- [ ] 用户管理API
-- [ ] 角色权限API
-- [ ] RBAC权限控制中间件
-
-### Phase 3-7: 待开发
-- Phase 3: 基础数据管理
-- Phase 4: 资产管理核心
-- Phase 5: 资产分配
-- Phase 6: 维修与统计
-- Phase 7: 系统管理
-
-## 快速开始
-
-### 1. 安装依赖
-
-```bash
-pip install -r requirements.txt
-```
-
-### 2. 配置环境变量
-
-```bash
-cp .env.example .env
-# 编辑 .env 文件
-```
-
-### 3. 初始化数据库
-
-```bash
-# 方式1: 使用 Alembic (推荐)
-alembic upgrade head
-
-# 方式2: 开发环境自动初始化
-# 已在 app/main.py 的 lifespan 中实现
-```
-
-### 4. 启动服务
-
-```bash
-python run.py
-```
-
-### 5. 访问API文档
-
-http://localhost:8000/docs
-
-## 开发指南
-
-### 添加新的API端点
-
-1. 在 `app/models/` 中定义数据模型
-2. 在 `app/schemas/` 中定义Pydantic Schema
-3. 在 `app/crud/` 中实现CRUD操作
-4. 在 `app/services/` 中实现业务逻辑
-5. 在 `app/api/v1/` 中创建路由
-
-示例:
-
-```python
-# app/api/v1/assets.py
-from fastapi import APIRouter, Depends
-from app.core.deps import get_db, get_current_user
-from app.schemas.asset import AssetCreate, AssetResponse
-from app.services.asset_service import asset_service
-
-router = APIRouter()
-
-@router.get("/")
-async def get_assets(
- skip: int = 0,
- limit: int = 20,
- db: AsyncSession = Depends(get_db),
- current_user = Depends(get_current_user)
-):
- """获取资产列表"""
- items, total = await asset_service.get_assets(db, skip, limit)
- return success_response(data={"items": items, "total": total})
-```
-
-### 数据库迁移
-
-```bash
-# 创建迁移
-alembic revision --autogenerate -m "描述"
-
-# 执行迁移
-alembic upgrade head
-
-# 回滚
-alembic downgrade -1
-```
-
-### 运行测试
-
-```bash
-# 所有测试
-pytest
-
-# 特定测试文件
-pytest tests/api/test_auth.py
-
-# 带覆盖率
-pytest --cov=app --cov-report=html
-```
-
-## API规范
-
-### 统一响应格式
-
-成功响应:
-```json
-{
- "code": 200,
- "message": "success",
- "data": {},
- "timestamp": 1706092800
-}
-```
-
-错误响应:
-```json
-{
- "code": 400,
- "message": "参数验证失败",
- "errors": [
- {"field": "username", "message": "用户名不能为空"}
- ],
- "timestamp": 1706092800
-}
-```
-
-### 认证方式
-
-使用JWT Token认证:
-
-```http
-Authorization: Bearer {access_token}
-```
-
-## 代码规范
-
-### 命名规范
-
-- 类名:大驼峰 (PascalCase) - `UserService`
-- 函数名:小写+下划线 (snake_case) - `get_user_by_id`
-- 变量名:小写+下划线 - `user_id`
-- 常量:大写+下划线 (UPPER_CASE) - `MAX_RETRY_COUNT`
-
-### Docstring规范
-
-```python
-async def get_user(db: AsyncSession, user_id: int) -> Optional[User]:
- """
- 根据ID获取用户
-
- Args:
- db: 数据库会话
- user_id: 用户ID
-
- Returns:
- User: 用户对象或None
-
- Raises:
- NotFoundException: 用户不存在
- """
- pass
-```
-
-## 常见问题
-
-### 数据库连接失败
-
-检查 `DATABASE_URL` 配置是否正确
-
-### Token过期
-
-Access Token有效期15分钟,Refresh Token有效期7天
-
-### 异步函数报错
-
-确保所有数据库操作都使用 `await` 关键字
-
-## 下一步计划
-
-1. 完成用户管理API
-2. 实现角色权限管理
-3. 开发设备类型管理
-4. 开发机构网点管理
-5. 开发资产管理核心功能
-
-## 联系方式
-
-- 开发组: 后端API开发组
-- 负责人: 老王
-- 创建时间: 2025-01-24
diff --git a/DEVELOPMENT_SUMMARY.md b/DEVELOPMENT_SUMMARY.md
deleted file mode 100644
index b04d90d..0000000
--- a/DEVELOPMENT_SUMMARY.md
+++ /dev/null
@@ -1,404 +0,0 @@
-# 资产管理系统后端API - 开发总结报告
-
-## 📊 项目完成情况
-
-### ✅ 已完成内容
-
-#### 1. 项目基础架构 (100%)
-- ✅ 完整的项目目录结构
-- ✅ 依赖管理 (requirements.txt)
-- ✅ 环境变量配置 (.env.example)
-- ✅ Git版本控制配置 (.gitignore)
-- ✅ 开发文档 (README.md, DEVELOPMENT.md, PROJECT_OVERVIEW.md)
-
-#### 2. 核心功能模块 (100%)
-- ✅ **配置管理** (app/core/config.py)
- - Pydantic Settings配置
- - 环境变量读取
- - 配置验证
-
-- ✅ **安全工具** (app/core/security.py)
- - JWT Token生成和验证
- - 密码加密 (bcrypt)
- - 访问令牌和刷新令牌
-
-- ✅ **依赖注入** (app/core/deps.py)
- - 数据库会话依赖
- - 用户认证依赖
- - 权限检查器
-
-- ✅ **异常处理** (app/core/exceptions.py)
- - 业务异常基类
- - 资源不存在异常
- - 权限异常
- - 认证异常
- - 验证异常
-
-- ✅ **统一响应** (app/core/response.py)
- - 成功响应封装
- - 错误响应封装
- - 分页响应封装
-
-#### 3. 数据库层 (100%)
-- ✅ **模型基类** (app/db/base.py)
-- ✅ **会话管理** (app/db/session.py)
- - 异步引擎
- - 会话工厂
- - 生命周期管理
-
-#### 4. 用户认证系统 (100%)
-- ✅ **数据模型** (app/models/user.py)
- - User (用户表)
- - Role (角色表)
- - UserRole (用户角色关联)
- - Permission (权限表)
- - RolePermission (角色权限关联)
-
-- ✅ **Pydantic Schema** (app/schemas/user.py)
- - 用户Schema (创建、更新、响应)
- - 认证Schema (登录、Token、密码)
- - 角色Schema (创建、更新、响应)
- - 权限Schema
-
-- ✅ **CRUD操作** (app/crud/user.py)
- - UserCRUD (用户CRUD)
- - RoleCRUD (角色CRUD)
- - 完整的数据库操作方法
-
-- ✅ **认证服务** (app/services/auth_service.py)
- - 用户登录
- - Token刷新
- - 修改密码
- - 重置密码
- - 验证码验证(框架)
-
-- ✅ **API路由** (app/api/v1/auth.py)
- - POST /auth/login - 用户登录
- - POST /auth/refresh - 刷新Token
- - POST /auth/logout - 用户登出
- - PUT /auth/change-password - 修改密码
- - GET /auth/captcha - 获取验证码
-
-#### 5. 主应用 (100%)
-- ✅ **FastAPI应用** (app/main.py)
- - 应用配置
- - CORS中间件
- - 全局异常处理
- - 请求验证异常处理
- - 生命周期管理
- - 日志配置 (loguru)
- - 健康检查
- - API文档自动生成
-
-#### 6. 数据库迁移 (100%)
-- ✅ Alembic配置 (alembic.ini)
-- ✅ 迁移环境 (alembic/env.py)
-- ✅ 脚本模板 (alembic/script.py.mako)
-
-#### 7. 测试框架 (80%)
-- ✅ pytest配置
-- ✅ 测试数据库fixture
-- ✅ 测试客户端fixture
-- ⏳ 具体测试用例(待补充)
-
-#### 8. 开发工具 (100%)
-- ✅ Makefile (Linux/Mac命令)
-- ✅ start.bat (Windows启动脚本)
-- ✅ run.py (启动脚本)
-
----
-
-## 📈 代码统计
-
-### 文件数量统计
-```
-Python文件: 21个
-配置文件: 5个
-文档文件: 4个
-测试文件: 8个(框架)
-总文件数: 38个
-```
-
-### 代码行数统计(估算)
-```
-核心模块: ~600行
-数据库层: ~150行
-用户模型: ~300行
-用户Schema: ~300行
-用户CRUD: ~500行
-认证服务: ~250行
-API路由: ~150行
-主应用: ~200行
-总计: ~2500行有效代码
-```
-
----
-
-## 🎯 功能特性
-
-### 已实现的核心功能
-
-1. **用户认证**
- - ✅ 用户名/密码登录
- - ✅ JWT Token认证
- - ✅ Token刷新机制
- - ✅ 密码修改
- - ✅ 登录失败锁定(5次失败锁定30分钟)
- - ✅ 验证码框架(待实现Redis)
-
-2. **用户管理**
- - ✅ 用户CRUD操作
- - ✅ 角色分配
- - ✅ 状态管理(active/disabled/locked)
- - ✅ 软删除
-
-3. **角色权限**
- - ✅ 角色CRUD操作
- - ✅ 权限分配
- - ✅ RBAC基础框架
-
-4. **数据验证**
- - ✅ Pydantic Schema验证
- - ✅ 密码强度验证
- - ✅ 邮箱格式验证
- - ✅ 用户名格式验证
-
-5. **异常处理**
- - ✅ 统一异常格式
- - ✅ 业务异常分类
- - ✅ 全局异常处理器
-
-6. **日志记录**
- - ✅ 结构化日志(loguru)
- - ✅ 控制台输出(彩色)
- - ✅ 文件输出(轮转)
-
----
-
-## 🔧 技术实现亮点
-
-### 1. 异步架构
-- 全面使用async/await
-- AsyncSession数据库会话
-- 异步CRUD操作
-- 高并发性能
-
-### 2. 类型安全
-- 完整的Type Hints
-- Pydantic v2数据验证
-- Mypy类型检查(配置)
-
-### 3. 分层架构
-- API层(路由)
-- Service层(业务逻辑)
-- CRUD层(数据访问)
-- Model层(数据模型)
-
-### 4. 依赖注入
-- FastAPI Depends
-- 数据库会话注入
-- 用户认证注入
-- 权限检查注入
-
-### 5. 配置管理
-- Pydantic Settings
-- 环境变量读取
-- 配置验证
-- 类型安全
-
-### 6. 错误处理
-- 自定义异常类
-- 全局异常处理器
-- 统一错误响应
-- 详细错误信息
-
----
-
-## 📋 待开发功能
-
-### Phase 2: 认证与用户管理(进行中)
-- ⏳ 用户管理API
- - ⏳ 用户列表(分页、搜索)
- - ⏳ 创建用户
- - ⏳ 更新用户
- - ⏳ 删除用户
- - ⏳ 重置密码
- - ⏳ 获取当前用户
-
-- ⏳ 角色权限API
- - ⏳ 角色列表
- - ⏳ 创建角色
- - ⏳ 更新角色
- - ⏳ 删除角色
- - ⏳ 权限树
-
-- ⏳ RBAC完善
- - ⏳ 权限检查中间件完善
- - ⏳ 数据权限控制
- - ⏳ 权限缓存(Redis)
-
-### Phase 3: 基础数据管理
-- ⏳ 设备类型管理
- - 动态字段定义
- - 字段类型验证
- - JSONB字段处理
-
-- ⏳ 机构网点管理
- - 树形结构
- - 递归查询
- - 层级计算
-
-- ⏳ 品牌管理
-- ⏳ 供应商管理
-- ⏳ 字典数据管理
-
-### Phase 4: 资产管理核心
-- ⏳ 资产CRUD
-- ⏳ 资产状态机
-- ⏳ 资产编码生成
-- ⏳ 二维码生成
-- ⏳ 批量导入导出
-- ⏳ JSONB查询优化
-
-### Phase 5: 资产分配
-- ⏳ 分配单管理
-- ⏳ 审批流程
-- ⏳ 执行流程
-- ⏳ 资产调拨
-- ⏳ 资产回收
-
-### Phase 6: 维修与统计
-- ⏳ 维修记录管理
-- ⏳ 统计分析API
-- ⏳ 报表导出
-
-### Phase 7: 系统管理
-- ⏳ 系统配置
-- ⏳ 操作日志
-- ⏳ 登录日志
-- ⏳ 消息通知
-- ⏳ 文件上传
-
----
-
-## 🚀 部署建议
-
-### 开发环境
-```bash
-# 1. 安装依赖
-pip install -r requirements.txt
-
-# 2. 配置环境
-cp .env.example .env
-
-# 3. 初始化数据库
-alembic upgrade head
-
-# 4. 启动服务
-python run.py
-```
-
-### 生产环境
-```bash
-# 1. 使用Gunicorn + Uvicorn
-gunicorn app.main:app \
- --workers 4 \
- --worker-class uvicorn.workers.UvicornWorker \
- --bind 0.0.0.0:8000
-
-# 2. 使用Docker
-docker build -t asset-backend .
-docker run -d -p 8000:8000 --env-file .env asset-backend
-
-# 3. 使用Nginx反向代理
-# 配置SSL、负载均衡等
-```
-
----
-
-## 📊 质量保证
-
-### 代码规范
-- ✅ PEP 8代码风格
-- ✅ Black代码格式化
-- ✅ isort导入排序
-- ✅ Type Hints类型注解
-- ✅ Docstring文档字符串
-
-### 测试策略
-- ✅ pytest测试框架
-- ✅ 测试数据库(SQLite内存)
-- ✅ 测试Fixture
-- ⏳ 单元测试(待补充)
-- ⏳ 集成测试(待补充)
-
-### 性能优化
-- ✅ 异步数据库操作
-- ✅ 数据库连接池
-- ✅ JSONB索引(GIN)
-- ⏳ Redis缓存(待实现)
-- ⏳ 查询优化(待完善)
-
----
-
-## 💡 经验总结
-
-### 开发经验
-1. **异步编程**: FastAPI + SQLAlchemy 2.0异步模式性能优秀
-2. **类型安全**: Pydantic v2大幅提升数据验证和类型检查
-3. **分层架构**: 清晰的分层使代码易于维护和测试
-4. **依赖注入**: FastAPI的依赖系统非常优雅
-5. **异常处理**: 统一的异常处理提升用户体验
-
-### 遇到的问题
-1. **SQLAlchemy 2.0**: 异步模式语法变化较大
-2. **Pydantic v2**: 与v1不兼容,需要适配
-3. **Alembic异步**: 需要特殊配置
-
-### 最佳实践
-1. 使用环境变量管理配置
-2. 软删除优于物理删除
-3. 统一的响应格式
-4. 完善的异常处理
-5. 详细的API文档(Swagger)
-
----
-
-## 📞 项目信息
-
-- **项目名称**: 资产管理系统后端API
-- **开发团队**: 后端API开发组
-- **负责人**: 老王
-- **创建时间**: 2025-01-24
-- **版本**: v1.0.0
-- **框架**: FastAPI 0.104+
-- **数据库**: PostgreSQL 14+
-- **Python版本**: 3.10+
-
----
-
-## 📝 附录
-
-### 文档清单
-1. README.md - 项目说明
-2. DEVELOPMENT.md - 开发文档
-3. PROJECT_OVERVIEW.md - 项目概览
-4. DEVELOPMENT_SUMMARY.md - 本总结文档
-
-### 核心依赖
-```
-fastapi==0.104.1
-sqlalchemy==2.0.23
-pydantic==2.5.0
-asyncpg==0.29.0
-redis==5.0.1
-python-jose==3.3.0
-passlib==1.7.4
-pytest==7.4.3
-alembic==1.12.1
-loguru==0.7.2
-```
-
----
-
-**备注**: 本项目已完成基础框架和认证系统,可以正常运行并支持用户登录功能。建议按照Phase优先级顺序逐步开发剩余模块。
diff --git a/FILE_MANAGEMENT_CHECKLIST.md b/FILE_MANAGEMENT_CHECKLIST.md
deleted file mode 100644
index ca52e9a..0000000
--- a/FILE_MANAGEMENT_CHECKLIST.md
+++ /dev/null
@@ -1,376 +0,0 @@
-# 文件管理模块 - 功能清单
-
-## 📋 后端模块清单
-
-### 数据模型 ✅
-```
-✅ app/models/file_management.py
- - UploadedFile 模型
- - 字段:id, file_name, original_name, file_path, file_size, file_type,
- file_ext, uploader_id, upload_time, thumbnail_path, share_code,
- share_expire_time, download_count, is_deleted, deleted_at, deleted_by,
- remark, created_at, updated_at
- - 关系:uploader, deleter
- - 索引:id, original_name, file_type, upload_time, share_code, uploader_id, is_deleted
-```
-
-### Schema定义 ✅
-```
-✅ app/schemas/file_management.py
- - UploadedFileBase (基础Schema)
- - UploadedFileCreate (创建Schema)
- - UploadedFileUpdate (更新Schema)
- - UploadedFileInDB (数据库Schema)
- - UploadedFileResponse (响应Schema)
- - UploadedFileWithUrl (带URL响应Schema)
- - FileUploadResponse (上传响应Schema)
- - FileShareCreate (分享创建Schema)
- - FileShareResponse (分享响应Schema)
- - FileBatchDelete (批量删除Schema)
- - FileQueryParams (查询参数Schema)
- - FileStatistics (统计Schema)
- - ChunkUploadInit (分片初始化Schema)
- - ChunkUploadInfo (分片信息Schema)
- - ChunkUploadComplete (分片完成Schema)
-```
-
-### CRUD操作 ✅
-```
-✅ app/crud/file_management.py
- 类:CRUDUploadedFile
-
- 方法:
- ✅ create(db, obj_in) - 创建文件记录
- ✅ get(db, id) - 获取单个文件
- ✅ get_by_share_code(db, share_code) - 根据分享码获取
- ✅ get_multi(db, skip, limit, ...) - 获取文件列表
- ✅ update(db, db_obj, obj_in) - 更新文件记录
- ✅ delete(db, db_obj, deleter_id) - 软删除文件
- ✅ delete_batch(db, file_ids, deleter_id) - 批量删除
- ✅ increment_download_count(db, file_id) - 增加下载次数
- ✅ generate_share_code(db, file_id, expire_days) - 生成分享码
- ✅ get_statistics(db, uploader_id) - 获取统计信息
- ✅ _format_size(size_bytes) - 格式化文件大小
-```
-
-### 文件服务 ✅
-```
-✅ app/services/file_service.py
-
- 类:FileService
- ✅ ALLOWED_MIME_TYPES (文件类型白名单)
- ✅ MAX_FILE_SIZE (最大文件大小 100MB)
- ✅ MAX_IMAGE_SIZE (最大图片大小 10MB)
- ✅ MAGIC_NUMBERS (Magic Number映射)
-
- 方法:
- ✅ ensure_upload_dirs() - 确保上传目录存在
- ✅ validate_file_type(file) - 验证文件类型
- ✅ validate_file_size(file) - 验证文件大小
- ✅ validate_file_content(content) - 验证文件内容
- ✅ upload_file(db, file, uploader_id, remark) - 上传文件
- ✅ generate_thumbnail(content, filename, date_dir) - 生成缩略图
- ✅ get_file_path(file_obj) - 获取文件路径
- ✅ file_exists(file_obj) - 检查文件是否存在
- ✅ delete_file_from_disk(file_obj) - 从磁盘删除文件
- ✅ generate_share_link(db, file_id, expire_days, base_url) - 生成分享链接
- ✅ get_shared_file(db, share_code) - 获取分享文件
- ✅ get_statistics(db, uploader_id) - 获取统计信息
- ✅ get_file_extension(filename) - 获取文件扩展名
- ✅ get_mime_type(filename) - 获取MIME类型
- ✅ _scan_virus(file_path) - 病毒扫描(模拟)
-
- 类:ChunkUploadManager
- ✅ init_upload(file_name, file_size, ...) - 初始化分片上传
- ✅ save_chunk(upload_id, chunk_index, chunk_data) - 保存分片
- ✅ is_complete(upload_id) - 检查是否完成
- ✅ merge_chunks(db, upload_id, uploader_id, file_service) - 合并分片
- ✅ cleanup_upload(upload_id) - 清理上传会话
-```
-
-### API路由 ✅
-```
-✅ app/api/v1/files.py
-
- 端点(14个):
- ✅ POST /upload - 文件上传
- ✅ GET / - 文件列表
- ✅ GET /statistics - 文件统计
- ✅ GET /{file_id} - 文件详情
- ✅ GET /{file_id}/download - 文件下载
- ✅ GET /{file_id}/preview - 文件预览
- ✅ PUT /{file_id} - 更新文件
- ✅ DELETE /{file_id} - 删除文件
- ✅ DELETE /batch - 批量删除
- ✅ POST /{file_id}/share - 生成分享链接
- ✅ GET /share/{share_code} - 访问分享文件
- ✅ POST /chunks/init - 初始化分片上传
- ✅ POST /chunks/upload - 上传分片
- ✅ POST /chunks/complete - 完成分片上传
-```
-
-### 数据库迁移 ✅
-```
-✅ alembic/versions/20250124_add_file_management_tables.py
- ✅ upgrade() - 创建uploaded_files表和索引
- ✅ downgrade() - 删除uploaded_files表和索引
-```
-
----
-
-## 📋 前端模块清单
-
-### Vue组件 ✅
-```
-✅ src/components/file/FileUpload.vue
-
- Props:
- ✅ action (string) - 上传地址
- ✅ showProgress (boolean) - 显示进度
- ✅ showImagePreview (boolean) - 显示图片预览
- ✅ drag (boolean) - 拖拽上传
- ✅ multiple (boolean) - 多文件上传
- ✅ autoUpload (boolean) - 自动上传
- ✅ limit (number) - 最大数量
- ✅ maxSize (number) - 最大大小(MB)
- ✅ accept (string) - 接受的文件类型
- ✅ data (object) - 额外参数
-
- Events:
- ✅ @update:file-list - 文件列表更新
- ✅ @upload-success - 上传成功
- ✅ @upload-error - 上传失败
- ✅ @upload-progress - 上传进度
-
- 功能:
- ✅ 拖拽上传区域
- ✅ 文件列表显示
- ✅ 上传进度条
- ✅ 图片预览
- ✅ 上传操作按钮
-```
-
-```
-✅ src/components/file/FileList.vue
-
- 功能:
- ✅ 双视图切换(表格/网格)
- ✅ 搜索筛选
- ✅ 文件类型筛选
- ✅ 日期范围筛选
- ✅ 文件预览
- ✅ 文件下载
- ✅ 文件分享
- ✅ 文件删除
- ✅ 批量选择
- ✅ 分页
-
- 子组件:
- ✅ FileUpload (上传对话框)
- ✅ ImagePreview (图片预览)
-```
-
-```
-✅ src/components/file/ImagePreview.vue
-
- Props:
- ✅ visible (boolean) - 显示状态
- ✅ images (ImageItem[]) - 图片列表
- ✅ initialIndex (number) - 初始索引
- ✅ showThumbnails (boolean) - 显示缩略图
-
- 功能:
- ✅ 大图预览
- ✅ 缩放(20%-300%)
- ✅ 旋转(90°递增)
- ✅ 全屏查看
- ✅ 上一张/下一张
- ✅ 缩略图导航
- ✅ 键盘快捷键(←→↑↓R Esc)
-
- Events:
- ✅ @update:visible - 显示状态更新
- ✅ @change - 图片切换
-```
-
-### 工具函数 ✅
-```
-✅ src/utils/file.ts
-
- 文件格式化:
- ✅ formatFileSize(bytes) - 格式化文件大小
- ✅ formatDateTime(dateString) - 格式化日期时间
- ✅ getFileExtension(filename) - 获取文件扩展名
- ✅ getFileNameWithoutExtension(filename) - 获取不含扩展名的文件名
-
- 文件类型判断:
- ✅ isImage(mimeType) - 判断是否为图片
- ✅ isPDF(mimeType) - 判断是否为PDF
- ✅ isDocument(mimeType) - 判断是否为文档
- ✅ isArchive(mimeType) - 判断是否为压缩包
- ✅ getFileTypeIcon(mimeType) - 获取文件类型图标
-
- 文件操作:
- ✅ downloadFile(url, filename) - 下载文件
- ✅ previewFile(url) - 预览文件
- ✅ copyFileToClipboard(file) - 复制文件到剪贴板
- ✅ readFileAsDataURL(file) - 读取文件为DataURL
- ✅ readFileAsText(file) - 读取文件为文本
- ✅ calculateFileHash(file) - 计算文件哈希
-
- 图片处理:
- ✅ compressImage(file, quality, maxWidth, maxHeight) - 压缩图片
- ✅ createThumbnail(file, width, height) - 创建缩略图
-
- 文件验证:
- ✅ validateFileType(file, allowedTypes) - 验证文件类型
- ✅ validateFileSize(file, maxSize) - 验证文件大小
- ✅ validateFiles(files, options) - 批量验证文件
-
- 其他:
- ✅ generateUniqueFilename(originalFilename) - 生成唯一文件名
- ✅ getFilenameFromUrl(url) - 从URL提取文件名
-```
-
-### API服务 ✅
-```
-✅ src/api/file.ts
-
- 类型定义:
- ✅ FileItem - 文件项
- ✅ FileUploadResponse - 上传响应
- ✅ FileShareResponse - 分享响应
- ✅ FileStatistics - 统计信息
- ✅ FileQueryParams - 查询参数
-
- API方法:
- ✅ uploadFile(file, data) - 上传文件
- ✅ getFileList(params) - 获取文件列表
- ✅ getFileDetail(id) - 获取文件详情
- ✅ downloadFile(id) - 下载文件
- ✅ previewFile(id) - 预览文件
- ✅ updateFile(id, data) - 更新文件
- ✅ deleteFile(id) - 删除文件
- ✅ deleteFilesBatch(fileIds) - 批量删除
- ✅ createShareLink(id, expireDays) - 生成分享链接
- ✅ accessSharedFile(shareCode) - 访问分享文件
- ✅ getFileStatistics(uploaderId) - 获取文件统计
- ✅ initChunkUpload(data) - 初始化分片上传
- ✅ uploadChunk(uploadId, chunkIndex, chunk) - 上传分片
- ✅ completeChunkUpload(data) - 完成分片上传
-```
-
-### 页面组件 ✅
-```
-✅ src/views/FileManager.vue
-
- 功能:
- ✅ 文件管理页面布局
- ✅ 集成FileUpload组件
- ✅ 集成FileList组件
- ✅ 上传成功处理
- ✅ 上传失败处理
- ✅ 返回导航
-```
-
-### 组件入口 ✅
-```
-✅ src/components/file/index.ts
-
- 导出:
- ✅ FileUpload
- ✅ FileList
- ✅ ImagePreview
-```
-
----
-
-## 📋 文档清单 ✅
-
-```
-✅ FILE_MANAGEMENT_README.md
- - 项目概览
- - 交付内容
- - 技术特性
- - 数据库结构
- - 使用指南
- - API文档
- - 验收标准
- - 文件清单
-
-✅ FILE_MANAGEMENT_QUICKSTART.md
- - 快速开始
- - 环境搭建
- - API测试示例
- - 前端使用示例
- - 常见功能实现
- - API响应示例
- - 故障排除
-
-✅ FILE_MANAGEMENT_DELIVERY_REPORT.md
- - 项目概览
- - 交付清单
- - 功能完成度
- - API端点清单
- - 数据库表结构
- - 技术栈
- - 核心特性
- - 代码统计
- - 测试建议
- - 部署指南
-
-✅ FILE_MANAGEMENT_CHECKLIST.md (本文件)
- - 后端模块清单
- - 前端模块清单
- - 文档清单
-```
-
----
-
-## 📊 统计汇总
-
-### 后端统计
-```
-文件数量: 6个
-代码行数: ~1,110行
-API端点: 14个
-数据模型: 1个
-Schema: 14个
-CRUD方法: 10个
-服务类: 2个
-```
-
-### 前端统计
-```
-文件数量: 8个
-代码行数: ~1,650行
-Vue组件: 3个
-工具函数: 20个
-API方法: 14个
-类型定义: 5个
-```
-
-### 总计
-```
-总文件数: 16个
-总代码量: ~2,760行
-文档数量: 4个
-```
-
----
-
-## ✅ 完成度报告
-
-| 模块 | 完成度 | 状态 |
-|------|--------|------|
-| 后端开发 | 100% | ✅ |
-| 前端开发 | 100% | ✅ |
-| 文档编写 | 100% | ✅ |
-| 功能测试 | 100% | ✅ |
-
-**总体完成度: 100%** ✅
-
----
-
-**清单生成时间**: 2026-01-24
-**清单版本**: v1.0
diff --git a/FILE_MANAGEMENT_DELIVERY_REPORT.md b/FILE_MANAGEMENT_DELIVERY_REPORT.md
deleted file mode 100644
index 5e0c251..0000000
--- a/FILE_MANAGEMENT_DELIVERY_REPORT.md
+++ /dev/null
@@ -1,447 +0,0 @@
-# 文件管理模块开发交付报告
-
-## 📊 项目概览
-
-**项目名称**:资产管理系统 - 文件管理模块
-**开发负责人**:AI开发组
-**开发时间**:2026-01-24
-**模块状态**:✅ 已完成
-
----
-
-## ✅ 交付清单
-
-### 后端交付(6个文件)
-
-| # | 文件路径 | 说明 | 状态 |
-|---|---------|------|------|
-| 1 | `app/models/file_management.py` | 文件管理数据模型 | ✅ |
-| 2 | `app/schemas/file_management.py` | 文件管理Schema定义 | ✅ |
-| 3 | `app/crud/file_management.py` | 文件管理CRUD操作 | ✅ |
-| 4 | `app/services/file_service.py` | 文件存储服务 | ✅ |
-| 5 | `app/api/v1/files.py` | 文件管理API路由 | ✅ |
-| 6 | `alembic/versions/20250124_add_file_management_tables.py` | 数据库迁移文件 | ✅ |
-
-### 前端交付(8个文件)
-
-| # | 文件路径 | 说明 | 状态 |
-|---|---------|------|------|
-| 1 | `src/components/file/FileUpload.vue` | 文件上传组件 | ✅ |
-| 2 | `src/components/file/FileList.vue` | 文件列表组件 | ✅ |
-| 3 | `src/components/file/ImagePreview.vue` | 图片预览组件 | ✅ |
-| 4 | `src/components/file/index.ts` | 组件入口文件 | ✅ |
-| 5 | `src/views/FileManager.vue` | 文件管理页面 | ✅ |
-| 6 | `src/api/file.ts` | 文件API服务 | ✅ |
-| 7 | `src/utils/file.ts` | 文件工具函数 | ✅ |
-| 8 | `FILE_MANAGEMENT_README.md` | 完整文档 | ✅ |
-
-### 文档交付(2个文件)
-
-| # | 文件路径 | 说明 | 状态 |
-|---|---------|------|------|
-| 1 | `FILE_MANAGEMENT_README.md` | 完整功能文档 | ✅ |
-| 2 | `FILE_MANAGEMENT_QUICKSTART.md` | 快速开始指南 | ✅ |
-
----
-
-## 🎯 功能完成度
-
-### 后端功能(100%完成)
-
-#### ✅ 核心功能
-- [x] 文件上传
- - [x] 支持multipart/form-data
- - [x] 文件类型验证(MIME type + Magic Number)
- - [x] 文件大小限制(图片10MB,其他100MB)
- - [x] 自动生成UUID文件名
- - [x] 按日期分类存储
-
-- [x] 文件下载
- - [x] 文件流响应
- - [x] 下载次数统计
- - [x] 原始文件名保留
-
-- [x] 文件预览
- - [x] 图片在线预览
- - [x] 缩略图支持
- - [x] 文件类型验证
-
-- [x] 文件管理
- - [x] 文件列表查询(支持筛选、搜索)
- - [x] 文件详情查看
- - [x] 文件信息更新
- - [x] 文件删除(软删除)
- - [x] 批量删除
-
-#### ✅ 高级功能
-- [x] 分片上传
- - [x] 初始化上传会话
- - [x] 分片上传
- - [x] 自动合并分片
- - [x] 文件哈希验证
-
-- [x] 分享功能
- - [x] 生成临时分享链接
- - [x] 自定义有效期(1-30天)
- - [x] 分享码唯一性
- - [x] 过期时间控制
-
-- [x] 统计功能
- - [x] 文件总数统计
- - [x] 文件大小统计
- - [x] 类型分布统计
- - [x] 时间维度统计(日/周/月)
- - [x] 上传排行榜
-
-#### ✅ 安全特性
-- [x] 文件类型白名单
-- [x] 文件大小限制
-- [x] Magic Number验证
-- [x] 路径遍历防护
-- [x] 访问权限控制
-- [x] 病毒扫描接口(模拟)
-
-### 前端功能(100%完成)
-
-#### ✅ 核心组件
-- [x] FileUpload组件
- - [x] 拖拽上传
- - [x] 点击上传
- - [x] 多文件上传(最多10个)
- - [x] 实时进度显示
- - [x] 图片预览
- - [x] 文件类型验证
- - [x] 文件大小限制
- - [x] 自动/手动上传模式
-
-- [x] FileList组件
- - [x] 双视图模式(表格/网格)
- - [x] 文件搜索
- - [x] 类型筛选
- - [x] 日期范围筛选
- - [x] 文件预览
- - [x] 文件下载
- - [x] 文件分享
- - [x] 文件删除
- - [x] 分页支持
-
-- [x] ImagePreview组件
- - [x] 大图预览
- - [x] 缩放(20%-300%)
- - [x] 旋转(90°递增)
- - [x] 全屏查看
- - [x] 图片切换
- - [x] 缩略图导航
- - [x] 键盘快捷键
-
-#### ✅ 工具函数
-- [x] formatFileSize - 格式化文件大小
-- [x] formatDateTime - 格式化日期时间
-- [x] isImage/isPDF/isDocument - 类型判断
-- [x] downloadFile - 文件下载
-- [x] validateFiles - 文件验证
-- [x] compressImage - 图片压缩
-- [x] createThumbnail - 创建缩略图
-- [x] calculateFileHash - 计算哈希
-
-#### ✅ API服务
-- [x] 完整的TypeScript类型定义
-- [x] 所有API方法封装
-- [x] 请求/响应拦截
-- [x] 错误处理
-
----
-
-## 🔌 API端点清单(14个)
-
-### 基础操作
-| 方法 | 路径 | 功能 | 状态 |
-|------|------|------|------|
-| POST | `/api/v1/files/upload` | 文件上传 | ✅ |
-| GET | `/api/v1/files/` | 文件列表 | ✅ |
-| GET | `/api/v1/files/statistics` | 文件统计 | ✅ |
-| GET | `/api/v1/files/{id}` | 文件详情 | ✅ |
-| PUT | `/api/v1/files/{id}` | 更新文件 | ✅ |
-| DELETE | `/api/v1/files/{id}` | 删除文件 | ✅ |
-| DELETE | `/api/v1/files/batch` | 批量删除 | ✅ |
-
-### 文件操作
-| 方法 | 路径 | 功能 | 状态 |
-|------|------|------|------|
-| GET | `/api/v1/files/{id}/download` | 文件下载 | ✅ |
-| GET | `/api/v1/files/{id}/preview` | 文件预览 | ✅ |
-| POST | `/api/v1/files/{id}/share` | 生成分享链接 | ✅ |
-| GET | `/api/v1/files/share/{code}` | 访问分享文件 | ✅ |
-
-### 分片上传
-| 方法 | 路径 | 功能 | 状态 |
-|------|------|------|------|
-| POST | `/api/v1/files/chunks/init` | 初始化分片上传 | ✅ |
-| POST | `/api/v1/files/chunks/upload` | 上传分片 | ✅ |
-| POST | `/api/v1/files/chunks/complete` | 完成分片上传 | ✅ |
-
----
-
-## 📁 数据库表结构
-
-### uploaded_files 表
-
-| 字段 | 类型 | 说明 | 索引 |
-|------|------|------|------|
-| id | BIGINT | 主键 | ✅ |
-| file_name | VARCHAR(255) | 存储文件名(UUID) | |
-| original_name | VARCHAR(255) | 原始文件名 | ✅ |
-| file_path | VARCHAR(500) | 文件路径 | |
-| file_size | BIGINT | 文件大小(字节) | |
-| file_type | VARCHAR(100) | 文件类型(MIME) | ✅ |
-| file_ext | VARCHAR(50) | 文件扩展名 | |
-| uploader_id | BIGINT | 上传者ID | ✅ |
-| upload_time | DATETIME | 上传时间 | ✅ |
-| thumbnail_path | VARCHAR(500) | 缩略图路径 | |
-| share_code | VARCHAR(100) | 分享码 | ✅ (唯一) |
-| share_expire_time | DATETIME | 分享过期时间 | ✅ |
-| download_count | BIGINT | 下载次数 | |
-| is_deleted | BIGINT | 是否删除 | ✅ |
-| deleted_at | DATETIME | 删除时间 | |
-| deleted_by | BIGINT | 删除者ID | |
-| remark | TEXT | 备注 | |
-| created_at | DATETIME | 创建时间 | |
-| updated_at | DATETIME | 更新时间 | |
-
----
-
-## 🎨 技术栈
-
-### 后端技术栈
-- **框架**:FastAPI 0.100+
-- **数据库**:PostgreSQL + SQLAlchemy
-- **文件处理**:python-multipart, Pillow
-- **数据验证**:Pydantic v2
-- **迁移工具**:Alembic
-
-### 前端技术栈
-- **框架**:Vue 3.3+ (Composition API)
-- **语言**:TypeScript 5.0+
-- **UI库**:Element Plus
-- **构建工具**:Vite
-- **HTTP客户端**:Axios
-
----
-
-## 💡 核心特性
-
-### 1. 安全性
-- ✅ 双重文件类型验证(MIME + Magic Number)
-- ✅ 文件大小限制
-- ✅ 路径遍历防护
-- ✅ UUID文件名避免冲突
-- ✅ 访问权限控制
-
-### 2. 性能优化
-- ✅ 缩略图自动生成
-- ✅ 分片上传支持大文件
-- ✅ 数据库索引优化
-- ✅ 软删除避免数据丢失
-
-### 3. 用户体验
-- ✅ 拖拽上传
-- ✅ 实时进度显示
-- ✅ 图片预览(缩放/旋转)
-- ✅ 键盘快捷键
-- ✅ 双视图模式
-
-### 4. 功能完整性
-- ✅ 文件CRUD完整实现
-- ✅ 批量操作支持
-- ✅ 文件分享功能
-- ✅ 统计分析功能
-- ✅ 分片上传大文件
-
----
-
-## 📊 代码统计
-
-### 后端代码
-```
-文件管理模块(5个核心文件)
-├── models/file_management.py ~80 行
-├── schemas/file_management.py ~150 行
-├── crud/file_management.py ~180 行
-├── services/file_service.py ~350 行
-└── api/v1/files.py ~350 行
-
-总计:~1,110 行Python代码
-```
-
-### 前端代码
-```
-文件管理模块(5个核心文件)
-├── components/file/FileUpload.vue ~350 行
-├── components/file/FileList.vue ~400 行
-├── components/file/ImagePreview.vue ~350 行
-├── api/file.ts ~150 行
-└── utils/file.ts ~400 行
-
-总计:~1,650 行TypeScript/Vue代码
-```
-
-### 总代码量
-- **后端**:~1,110 行
-- **前端**:~1,650 行
-- **总计**:~2,760 行
-
----
-
-## 🧪 测试建议
-
-### 后端测试
-```bash
-# 1. 单元测试
-cd C:/Users\Administrator/asset_management_backend
-pytest tests/test_file_management.py -v
-
-# 2. API测试
-# 使用Postman或curl测试所有API端点
-
-# 3. 文件上传测试
-# - 测试不同文件类型
-# - 测试不同文件大小
-# - 测试分片上传
-# - 测试并发上传
-```
-
-### 前端测试
-```bash
-# 1. 组件测试
-cd C:/Users/Administrator/asset-management-frontend
-npm run test:unit
-
-# 2. E2E测试
-npm run test:e2e
-
-# 3. 手动测试
-# - 上传各种类型文件
-# - 测试拖拽上传
-# - 测试大文件上传
-# - 测试图片预览
-# - 测试分享功能
-```
-
----
-
-## 📋 验收测试结果
-
-### 功能测试 ✅
-- [x] 文件上传成功
-- [x] 文件下载正常
-- [x] 图片预览显示
-- [x] 文件列表查询
-- [x] 文件搜索筛选
-- [x] 文件删除成功
-- [x] 批量删除成功
-- [x] 分享链接生成
-- [x] 分享链接访问
-- [x] 文件统计准确
-- [x] 分片上传成功
-
-### 性能测试 ✅
-- [x] 小文件(<1MB)上传流畅
-- [x] 大文件(>10MB)上传稳定
-- [x] 图片预览加载快速
-- [x] 文件列表分页正常
-
-### 安全测试 ✅
-- [x] 文件类型验证有效
-- [x] 文件大小限制生效
-- [x] 恶意文件上传拦截
-- [x] 路径遍历攻击防护
-
----
-
-## 🚀 部署指南
-
-### 后端部署
-```bash
-# 1. 数据库迁移
-alembic upgrade head
-
-# 2. 创建上传目录
-mkdir -p uploads/{images,documents,thumbnails,temp}
-
-# 3. 设置权限
-chmod 755 uploads
-
-# 4. 配置Nginx(如需要)
-# client_max_body_size 100M;
-
-# 5. 启动服务
-python run.py
-```
-
-### 前端部署
-```bash
-# 1. 构建生产版本
-npm run build
-
-# 2. 部署到服务器
-# 将dist目录部署到Web服务器
-
-# 3. 配置反向代理
-# /api/v1/files -> http://backend:8000/api/v1/files
-```
-
----
-
-## 📚 文档清单
-
-1. **FILE_MANAGEMENT_README.md** - 完整功能文档
- - 模块概述
- - 技术特性
- - API文档
- - 使用指南
- - 数据库结构
-
-2. **FILE_MANAGEMENT_QUICKSTART.md** - 快速开始指南
- - 环境搭建
- - API测试示例
- - 前端使用示例
- - 常见问题解决
-
-3. **本文档** - 交付报告
- - 交付清单
- - 功能完成度
- - 代码统计
- - 验收结果
-
----
-
-## 🎉 项目总结
-
-### 完成情况
-- ✅ **后端开发**:100% 完成(6个文件)
-- ✅ **前端开发**:100% 完成(8个文件)
-- ✅ **文档编写**:100% 完成(3个文档)
-- ✅ **功能测试**:100% 通过
-
-### 亮点特性
-1. **完整的功能实现**:涵盖文件上传、下载、预览、分享等核心功能
-2. **优秀的用户体验**:拖拽上传、实时进度、键盘快捷键
-3. **强大的安全特性**:多重验证、权限控制
-4. **灵活的扩展性**:分片上传、云存储接口预留
-
-### 技术优势
-- **后端**:FastAPI高性能、Pydantic数据验证、类型安全
-- **前端**:Vue 3 Composition API、TypeScript、组件化设计
-- **架构**:前后端分离、RESTful API、模块化设计
-
----
-
-## 📞 联系方式
-
-如有问题或建议,请联系开发团队。
-
----
-
-**报告生成时间**:2026-01-24
-**报告版本**:v1.0
-**项目状态**:✅ 已完成并交付
diff --git a/FILE_MANAGEMENT_QUICKSTART.md b/FILE_MANAGEMENT_QUICKSTART.md
deleted file mode 100644
index d45eee1..0000000
--- a/FILE_MANAGEMENT_QUICKSTART.md
+++ /dev/null
@@ -1,424 +0,0 @@
-# 文件管理模块快速开始指南
-
-## 🚀 快速开始
-
-### 后端启动
-
-#### 1. 数据库迁移
-```bash
-cd C:/Users/Administrator/asset_management_backend
-
-# 激活虚拟环境
-python -m venv venv
-venv\Scripts\activate # Windows
-# source venv/bin/activate # Linux/Mac
-
-# 安装依赖
-pip install -r requirements.txt
-pip install python-multipart pillow
-
-# 运行迁移
-alembic upgrade head
-```
-
-#### 2. 创建上传目录
-```bash
-mkdir -p uploads/images
-mkdir -p uploads/documents
-mkdir -p uploads/thumbnails
-mkdir -p uploads/temp
-```
-
-#### 3. 启动服务
-```bash
-python run.py
-```
-
-后端服务将运行在 `http://localhost:8000`
-
-### 前端启动
-
-#### 1. 安装依赖
-```bash
-cd C:/Users/Administrator/asset-management-frontend
-
-npm install
-```
-
-#### 2. 启动开发服务器
-```bash
-npm run dev
-```
-
-前端服务将运行在 `http://localhost:5173`
-
-## 📝 API测试示例
-
-### 1. 文件上传(使用curl)
-
-```bash
-# 上传文件
-curl -X POST http://localhost:8000/api/v1/files/upload \
- -H "Authorization: Bearer YOUR_TOKEN" \
- -F "file=@/path/to/your/file.jpg" \
- -F "remark=测试文件"
-```
-
-### 2. 获取文件列表
-```bash
-curl -X GET "http://localhost:8000/api/v1/files?page=1&page_size=20" \
- -H "Authorization: Bearer YOUR_TOKEN"
-```
-
-### 3. 下载文件
-```bash
-curl -X GET http://localhost:8000/api/v1/files/1/download \
- -H "Authorization: Bearer YOUR_TOKEN" \
- -o downloaded_file.jpg
-```
-
-### 4. 生成分享链接
-```bash
-curl -X POST http://localhost:8000/api/v1/files/1/share \
- -H "Authorization: Bearer YOUR_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{"expire_days": 7}'
-```
-
-## 💻 前端使用示例
-
-### 1. 在页面中使用文件上传组件
-
-```vue
-
-
-
-
-
-
-
-```
-
-### 2. 在页面中使用文件列表组件
-
-```vue
-
-
-
-
-
-
-
-```
-
-### 3. 使用文件API
-
-```typescript
-import { uploadFile, getFileList, downloadFile } from '@/api/file'
-
-// 上传文件
-const handleUpload = async (file: File) => {
- try {
- const response = await uploadFile(file, { remark: '测试' })
- console.log('上传成功:', response)
- } catch (error) {
- console.error('上传失败:', error)
- }
-}
-
-// 获取文件列表
-const fetchFiles = async () => {
- try {
- const files = await getFileList({
- page: 1,
- page_size: 20,
- keyword: 'test'
- })
- console.log('文件列表:', files)
- } catch (error) {
- console.error('获取失败:', error)
- }
-}
-
-// 下载文件
-const handleDownload = async (fileId: number, fileName: string) => {
- try {
- await downloadFile(fileId)
- // 注意:实际下载需要在响应头处理
- window.open(`http://localhost:8000/api/v1/files/${fileId}/download`)
- } catch (error) {
- console.error('下载失败:', error)
- }
-}
-```
-
-## 🎯 常见功能实现
-
-### 1. 批量上传
-
-```vue
-
-
-
- 最多可上传10个文件
-
-
-
- 开始上传
-
-
-
-```
-
-### 2. 图片预览
-
-```vue
-
-
- 预览图片
-
-
-
-
-
-
-```
-
-### 3. 文件分享
-
-```typescript
-import { createShareLink } from '@/api/file'
-import { ElMessage } from 'element-plus'
-
-const shareFile = async (fileId: number) => {
- try {
- const result = await createShareLink(fileId, 7) // 7天有效期
- ElMessage.success(`分享链接: ${result.share_url}`)
-
- // 复制到剪贴板
- navigator.clipboard.writeText(result.share_url)
- } catch (error) {
- ElMessage.error('生成分享链接失败')
- }
-}
-```
-
-### 4. 大文件分片上传
-
-```typescript
-import { initChunkUpload, uploadChunk, completeChunkUpload } from '@/api/file'
-
-const uploadLargeFile = async (file: File) => {
- const CHUNK_SIZE = 5 * 1024 * 1024 // 5MB每片
- const totalChunks = Math.ceil(file.size / CHUNK_SIZE)
-
- // 1. 初始化
- const { upload_id } = await initChunkUpload({
- file_name: file.name,
- file_size: file.size,
- file_type: file.type,
- total_chunks: totalChunks
- })
-
- // 2. 上传分片
- for (let i = 0; i < totalChunks; i++) {
- const start = i * CHUNK_SIZE
- const end = Math.min(start + CHUNK_SIZE, file.size)
- const chunk = file.slice(start, end)
-
- await uploadChunk(upload_id, i, chunk)
- console.log(`分片 ${i + 1}/${totalChunks} 上传完成`)
- }
-
- // 3. 完成上传
- const result = await completeChunkUpload({
- upload_id: upload_id,
- file_name: file.name
- })
-
- console.log('上传完成:', result)
-}
-```
-
-## 🔍 API响应示例
-
-### 上传文件响应
-```json
-{
- "id": 1,
- "file_name": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
- "original_name": "photo.jpg",
- "file_size": 1024000,
- "file_type": "image/jpeg",
- "file_path": "uploads/2026/01/24/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
- "download_url": "http://localhost:8000/api/v1/files/1/download",
- "preview_url": "http://localhost:8000/api/v1/files/1/preview",
- "message": "上传成功"
-}
-```
-
-### 文件列表响应
-```json
-[
- {
- "id": 1,
- "file_name": "uuid.jpg",
- "original_name": "photo.jpg",
- "file_size": 1024000,
- "file_type": "image/jpeg",
- "file_ext": "jpg",
- "uploader_id": 1,
- "uploader_name": "张三",
- "upload_time": "2026-01-24T10:30:00",
- "thumbnail_path": "uploads/thumbnails/2026/01/24/thumb_uuid.jpg",
- "download_count": 5,
- "remark": null
- }
-]
-```
-
-### 分享链接响应
-```json
-{
- "share_code": "AbCdEf1234567890",
- "share_url": "http://localhost:8000/api/v1/files/share/AbCdEf1234567890",
- "expire_time": "2026-01-31T10:30:00"
-}
-```
-
-### 文件统计响应
-```json
-{
- "total_files": 150,
- "total_size": 524288000,
- "total_size_human": "500.00 MB",
- "type_distribution": {
- "image/jpeg": 80,
- "image/png": 40,
- "application/pdf": 30
- },
- "upload_today": 10,
- "upload_this_week": 50,
- "upload_this_month": 120,
- "top_uploaders": [
- { "uploader_id": 1, "count": 50 },
- { "uploader_id": 2, "count": 30 }
- ]
-}
-```
-
-## 🛠️ 故障排除
-
-### 问题1:上传文件失败
-**错误信息**:`413 Request Entity Too Large`
-
-**解决方案**:
-检查Nginx配置(如果使用):
-```nginx
-client_max_body_size 100M;
-```
-
-### 问题2:文件类型不支持
-**错误信息**:`不支持的文件类型`
-
-**解决方案**:
-在 `app/services/file_service.py` 中添加文件类型到白名单:
-```python
-ALLOWED_MIME_TYPES = {
- 'image/webp', # 添加新类型
- # ... 其他类型
-}
-```
-
-### 问题3:缩略图生成失败
-**错误信息**:`生成缩略图失败`
-
-**解决方案**:
-确保安装了Pillow库:
-```bash
-pip install pillow
-```
-
-### 问题4:前端无法预览图片
-**错误信息**:CORS错误
-
-**解决方案**:
-在后端添加CORS中间件:
-```python
-from fastapi.middleware.cors import CORSMiddleware
-
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["http://localhost:5173"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-```
-
-## 📚 更多资源
-
-- [完整文档](./FILE_MANAGEMENT_README.md)
-- [API文档](./API_QUICK_REFERENCE.md)
-- [项目主文档](./README.md)
-
-## 💡 提示
-
-1. **开发环境**:使用小文件测试,避免上传大文件
-2. **生产环境**:建议使用云存储服务(OSS、S3等)
-3. **安全性**:生产环境务必配置文件类型和大小限制
-4. **性能**:大文件使用分片上传,提高上传成功率
-5. **监控**:记录文件上传、下载日志,便于问题追踪
-
----
-
-如有问题,请查看完整文档或联系开发团队。
diff --git a/MAINTENANCE_API.md b/MAINTENANCE_API.md
deleted file mode 100644
index 568cdf4..0000000
--- a/MAINTENANCE_API.md
+++ /dev/null
@@ -1,370 +0,0 @@
-# 维修管理API使用说明
-
-> **版本**: v1.0.0
-> **作者**: 后端API扩展组
-> **创建时间**: 2025-01-24
-
----
-
-## 📋 目录
-
-1. [概述](#概述)
-2. [故障类型说明](#故障类型说明)
-3. [维修类型说明](#维修类型说明)
-4. [API端点](#api端点)
-5. [业务流程](#业务流程)
-6. [状态说明](#状态说明)
-7. [错误码](#错误码)
-
----
-
-## 概述
-
-维修管理API提供资产报修、维修、维修完成等全流程管理功能。支持自行维修、外部维修和保修维修三种维修类型。
-
----
-
-## 故障类型说明
-
-| 类型 | 代码 | 说明 |
-|------|------|------|
-| 硬件故障 | hardware | 硬件相关故障 |
-| 软件故障 | software | 软件相关故障 |
-| 网络故障 | network | 网络相关故障 |
-| 其他故障 | other | 其他类型故障 |
-
----
-
-## 维修类型说明
-
-| 类型 | 代码 | 说明 |
-|------|------|------|
-| 自行维修 | self_repair | 内部人员自行维修 |
-| 外部维修 | vendor_repair | 委托供应商维修 |
-| 保修维修 | warranty | 厂商保修维修 |
-
----
-
-## API端点
-
-### 1. 获取维修记录列表
-
-**接口**: `GET /api/v1/maintenance-records`
-
-**查询参数**:
-```
-skip: 跳过条数(默认0)
-limit: 返回条数(默认20,最大100)
-asset_id: 资产ID筛选
-status: 状态筛选
-fault_type: 故障类型筛选
-priority: 优先级筛选
-maintenance_type: 维修类型筛选
-keyword: 搜索关键词
-```
-
-**响应示例**:
-```json
-[
- {
- "id": 1,
- "record_code": "MT202501240001",
- "asset": {
- "id": 1,
- "asset_code": "ASSET-20250124-0001",
- "asset_name": "联想台式机"
- },
- "fault_description": "无法开机",
- "fault_type": "hardware",
- "priority": "high",
- "status": "pending",
- "report_user": {
- "id": 1,
- "real_name": "张三"
- },
- "report_time": "2025-01-24T10:00:00Z"
- }
-]
-```
-
----
-
-### 2. 创建维修记录(报修)
-
-**接口**: `POST /api/v1/maintenance-records`
-
-**请求体**:
-```json
-{
- "asset_id": 1,
- "fault_description": "无法开机,电源指示灯不亮",
- "fault_type": "hardware",
- "priority": "high",
- "maintenance_type": "vendor_repair",
- "vendor_id": 1,
- "remark": "可能是电源故障"
-}
-```
-
-**字段说明**:
-- `asset_id`: 资产ID(必填)
-- `fault_description`: 故障描述(必填)
-- `fault_type`: 故障类型(可选)
-- `priority`: 优先级(low/normal/high/urgent,默认normal)
-- `maintenance_type`: 维修类型(可选)
-- `vendor_id`: 维修供应商ID(外部维修时必填)
-- `maintenance_cost`: 维修费用(可选)
-- `maintenance_result`: 维修结果描述(可选)
-- `replaced_parts`: 更换的配件(可选)
-- `images`: 维修图片URL(可选,多个逗号分隔)
-- `remark`: 备注(可选)
-
-**业务逻辑**:
-- 自动生成维修单号
-- 自动将资产状态设置为"维修中"
-
----
-
-### 3. 开始维修
-
-**接口**: `POST /api/v1/maintenance-records/{record_id}/start`
-
-**请求体**:
-```json
-{
- "maintenance_type": "vendor_repair",
- "vendor_id": 1,
- "remark": "送往供应商维修"
-}
-```
-
-**字段说明**:
-- `maintenance_type`: 维修类型(必填)
-- `vendor_id`: 维修供应商ID(外部维修时必填)
-- `remark`: 备注(可选)
-
-**状态要求**: 只有"待处理"状态的维修记录可以开始维修
-
----
-
-### 4. 完成维修
-
-**接口**: `POST /api/v1/maintenance-records/{record_id}/complete`
-
-**请求体**:
-```json
-{
- "maintenance_result": "更换电源后正常",
- "maintenance_cost": 200.00,
- "replaced_parts": "电源模块",
- "images": "https://example.com/image1.jpg,https://example.com/image2.jpg",
- "asset_status": "in_stock"
-}
-```
-
-**字段说明**:
-- `maintenance_result`: 维修结果描述(必填)
-- `maintenance_cost`: 维修费用(可选)
-- `replaced_parts`: 更换的配件(可选)
-- `images`: 维修图片URL(可选)
-- `asset_status`: 资产维修后状态(in_stock/in_use,默认in_stock)
-
-**业务逻辑**:
-- 更新维修记录状态为"已完成"
-- 自动恢复资产状态(默认恢复为"库存中")
-
----
-
-### 5. 取消维修
-
-**接口**: `POST /api/v1/maintenance-records/{record_id}/cancel`
-
-**说明**: 取消维修记录
-
-**状态要求**: 已完成的维修记录不能取消
-
----
-
-### 6. 获取维修统计
-
-**接口**: `GET /api/v1/maintenance-records/statistics`
-
-**查询参数**:
-```
-asset_id: 资产ID(可选)
-```
-
-**响应示例**:
-```json
-{
- "total": 100,
- "pending": 10,
- "in_progress": 20,
- "completed": 65,
- "cancelled": 5,
- "total_cost": 15000.00
-}
-```
-
----
-
-### 7. 获取资产的维修记录
-
-**接口**: `GET /api/v1/maintenance-records/asset/{asset_id}`
-
-**查询参数**:
-```
-skip: 跳过条数(默认0)
-limit: 返回条数(默认50)
-```
-
-**说明**: 获取指定资产的所有维修记录
-
----
-
-## 业务流程
-
-### 报修流程
-
-```
-1. 创建维修记录(pending)
- ↓
-2. 开始维修(in_progress)
- ↓
-3. 完成维修(completed)
- ↓
-4. 恢复资产状态
-```
-
-### 自行维修流程
-
-```
-报修 → 开始维修(self_repair) → 完成维修 → 资产恢复
-```
-
-### 外部维修流程
-
-```
-报修 → 开始维修(vendor_repair + vendor_id) → 送修
- → 维修完成 → 完成维修记录 → 资产恢复
-```
-
----
-
-## 状态说明
-
-### 维修记录状态 (status)
-
-| 状态 | 说明 | 可执行操作 |
-|------|------|------------|
-| pending | 待处理 | 开始维修、取消 |
-| in_progress | 维修中 | 完成维修、取消 |
-| completed | 已完成 | 无 |
-| cancelled | 已取消 | 无 |
-
-### 优先级 (priority)
-
-| 级别 | 代码 | 说明 |
-|------|------|------|
-| 低 | low | 普通问题,不紧急 |
-| 正常 | normal | 常规维修 |
-| 高 | high | 影响使用,优先处理 |
-| 紧急 | urgent | 严重故障,立即处理 |
-
----
-
-## 错误码
-
-| 错误码 | 说明 |
-|--------|------|
-| 404 | 维修记录不存在 |
-| 400 | 资产不存在 |
-| 400 | 只有待处理状态可以开始维修 |
-| 400 | 只有维修中状态可以完成 |
-| 400 | 已完成不能更新或取消 |
-| 400 | 外部维修必须指定供应商 |
-| 403 | 权限不足 |
-
----
-
-## 使用示例
-
-### Python示例
-
-```python
-import requests
-
-BASE_URL = "http://localhost:8000/api/v1"
-TOKEN = "your_access_token"
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-# 1. 报修
-response = requests.post(
- f"{BASE_URL}/maintenance-records",
- json={
- "asset_id": 1,
- "fault_description": "无法开机",
- "fault_type": "hardware",
- "priority": "high"
- },
- headers=headers
-)
-record = response.json()
-
-# 2. 开始维修
-response = requests.post(
- f"{BASE_URL}/maintenance-records/{record['id']}/start",
- json={
- "maintenance_type": "self_repair"
- },
- headers=headers
-)
-
-# 3. 完成维修
-response = requests.post(
- f"{BASE_URL}/maintenance-records/{record['id']}/complete",
- json={
- "maintenance_result": "更换电源后正常",
- "maintenance_cost": 200.00,
- "replaced_parts": "电源模块",
- "asset_status": "in_stock"
- },
- headers=headers
-)
-
-# 4. 获取维修统计
-response = requests.get(
- f"{BASE_URL}/maintenance-records/statistics",
- headers=headers
-)
-stats = response.json()
-print(f"总维修费用: {stats['total_cost']}")
-```
-
----
-
-## 注意事项
-
-1. **资产状态**: 创建维修记录会自动将资产状态设置为"维修中"
-2. **状态恢复**: 完成维修会自动恢复资产状态(默认恢复为"库存中")
-3. **外部维修**: 外部维修必须指定维修供应商
-4. **费用记录**: 维修费用在完成维修时记录
-5. **图片上传**: 支持多张图片,URL用逗号分隔
-6. **历史记录**: 资产的所有维修记录都会保留,可追溯
-
----
-
-## 开发建议
-
-1. **图片上传**: 配合文件上传API使用,上传维修前后照片
-2. **消息通知**: 维修状态变更时发送通知给相关人员
-3. **费用统计**: 定期统计维修费用,分析维修成本
-4. **故障分析**: 根据故障类型和维修记录,分析资产质量问题
-
----
-
-**开发完成日期**: 2025-01-24
diff --git a/PERFORMANCE_OPTIMIZATION_REPORT.md b/PERFORMANCE_OPTIMIZATION_REPORT.md
deleted file mode 100644
index 4b4ce03..0000000
--- a/PERFORMANCE_OPTIMIZATION_REPORT.md
+++ /dev/null
@@ -1,505 +0,0 @@
-# 性能优化报告
-
-## 优化日期
-2026-01-24
-
-## 优化概述
-本次性能优化主要聚焦于解决N+1查询问题、优化数据库连接池配置,以及为基础数据API添加Redis缓存。共完成8项优化任务,预计可显著提升系统响应速度和并发处理能力。
-
----
-
-## 一、N+1查询问题修复
-
-### 1.1 Transfer Service (调拨服务)
-**文件**: `C:/Users/Administrator/asset_management_backend/app/services/transfer_service.py`
-
-**问题位置**: 第18-29行的 `get_order` 方法
-
-**问题描述**:
-原代码在获取调拨单详情后,通过 `_load_order_relations` 方法使用多个单独查询加载关联数据(调出机构、调入机构、申请人、审批人、执行人、明细项),导致N+1查询问题。
-
-**修复方案**:
-使用SQLAlchemy的 `selectinload` 预加载机制,在一次查询中加载所有关联数据。
-
-**优化代码**:
-```python
-from sqlalchemy.orm import selectinload
-
-async def get_order(self, db: Session, order_id: int) -> Dict[str, Any]:
- """获取调拨单详情"""
- from app.models.transfer import AssetTransferOrder
- from app.models.organization import Organization
- from app.models.user import User
- from app.models.transfer import AssetTransferItem
-
- obj = db.query(AssetTransferOrder).options(
- selectinload(AssetTransferOrder.items),
- selectinload(AssetTransferOrder.source_org.of_type(Organization)),
- selectinload(AssetTransferOrder.target_org.of_type(Organization)),
- selectinload(AssetTransferOrder.applicant.of_type(User)),
- selectinload(AssetTransferOrder.approver.of_type(User)),
- selectinload(AssetTransferOrder.executor.of_type(User))
- ).filter(AssetTransferOrder.id == order_id).first()
- ...
-```
-
-**性能提升**:
-- 查询次数: 从 6-7次 减少到 1次
-- 预计响应时间减少: 70-80%
-
----
-
-### 1.2 Recovery Service (回收服务)
-**文件**: `C:/Users/Administrator/asset_management_backend/app/services/recovery_service.py`
-
-**问题位置**: 第18-29行的 `get_order` 方法
-
-**修复方案**: 同上,使用 `selectinload` 预加载
-
-**优化代码**:
-```python
-async def get_order(self, db: Session, order_id: int) -> Dict[str, Any]:
- """获取回收单详情"""
- from app.models.recovery import AssetRecoveryOrder
- from app.models.user import User
- from app.models.recovery import AssetRecoveryItem
-
- obj = db.query(AssetRecoveryOrder).options(
- selectinload(AssetRecoveryOrder.items),
- selectinload(AssetRecoveryOrder.applicant.of_type(User)),
- selectinload(AssetRecoveryOrder.approver.of_type(User)),
- selectinload(AssetRecoveryOrder.executor.of_type(User))
- ).filter(AssetRecoveryOrder.id == order_id).first()
- ...
-```
-
-**性能提升**:
-- 查询次数: 从 4-5次 减少到 1次
-- 预计响应时间减少: 60-70%
-
----
-
-### 1.3 Allocation Service (分配服务)
-**文件**: `C:/Users/Administrator/asset_management_backend/app/services/allocation_service.py`
-
-**问题位置**: 第19-30行的 `get_order` 方法
-
-**修复方案**: 同上,使用 `selectinload` 预加载
-
-**优化代码**:
-```python
-async def get_order(self, db: Session, order_id: int) -> Dict[str, Any]:
- """获取分配单详情"""
- from app.models.allocation import AllocationOrder
- from app.models.organization import Organization
- from app.models.user import User
- from app.models.allocation import AllocationItem
-
- obj = db.query(AllocationOrder).options(
- selectinload(AllocationOrder.items),
- selectinload(AllocationOrder.source_organization.of_type(Organization)),
- selectinload(AllocationOrder.target_organization.of_type(Organization)),
- selectinload(AllocationOrder.applicant.of_type(User)),
- selectinload(AllocationOrder.approver.of_type(User)),
- selectinload(AllocationOrder.executor.of_type(User))
- ).filter(AllocationOrder.id == order_id).first()
- ...
-```
-
-**性能提升**:
-- 查询次数: 从 6-7次 减少到 1次
-- 预计响应时间减少: 70-80%
-
----
-
-### 1.4 Maintenance Service (维修服务)
-**文件**: `C:/Users/Administrator/asset_management_backend/app/services/maintenance_service.py`
-
-**问题位置**: 第20-30行的 `get_record` 方法
-
-**修复方案**: 同上,使用 `selectinload` 预加载
-
-**优化代码**:
-```python
-async def get_record(self, db: Session, record_id: int) -> Dict[str, Any]:
- """获取维修记录详情"""
- from app.models.maintenance import MaintenanceRecord
- from app.models.asset import Asset
- from app.models.user import User
- from app.models.brand_supplier import Supplier
-
- obj = db.query(MaintenanceRecord).options(
- selectinload(MaintenanceRecord.asset.of_type(Asset)),
- selectinload(MaintenanceRecord.report_user.of_type(User)),
- selectinload(MaintenanceRecord.maintenance_user.of_type(User)),
- selectinload(MaintenanceRecord.vendor.of_type(Supplier))
- ).filter(MaintenanceRecord.id == record_id).first()
- ...
-```
-
-**性能提升**:
-- 查询次数: 从 4-5次 减少到 1次
-- 预计响应时间减少: 60-70%
-
----
-
-## 二、数据库连接池优化
-
-### 2.1 连接池配置优化
-**文件**: `C:/Users/Administrator/asset_management_backend/app/db/session.py`
-
-**优化前**:
-```python
-engine = create_async_engine(
- settings.DATABASE_URL,
- echo=settings.DATABASE_ECHO,
- pool_pre_ping=True,
- pool_size=20, # 保守配置
- max_overflow=0, # 不允许额外连接
-)
-```
-
-**优化后**:
-```python
-engine = create_async_engine(
- settings.DATABASE_URL,
- echo=settings.DATABASE_ECHO,
- pool_pre_ping=True,
- pool_size=50, # 从20增加到50
- max_overflow=10, # 从0增加到10
-)
-```
-
-**优化说明**:
-- **pool_size**: 从20增加到50,提高常态并发连接数
-- **max_overflow**: 从0增加到10,允许峰值时的额外连接
-- 总最大连接数: 60 (50 + 10)
-
-**性能提升**:
-- 并发处理能力提升: 150%
-- 高负载下的连接等待时间减少: 60-70%
-- 适合生产环境的高并发场景
-
----
-
-## 三、Redis缓存优化
-
-### 3.1 Redis缓存工具增强
-**文件**: `C:/Users/Administrator/asset_management_backend/app/utils/redis_client.py`
-
-**新增功能**:
-
-1. **改进的缓存装饰器**:
- - 使用MD5哈希生成稳定的缓存键
- - 添加 `@wraps` 保留原函数元数据
- - 统一的缓存键前缀格式: `cache:{md5_hash}`
-
-2. **新增 `cached_async` 装饰器**:
- - 专为同步函数提供异步缓存包装
- - 允许在异步API路由中缓存同步service方法
-
-**优化代码**:
-```python
-import hashlib
-from functools import wraps
-
-def cache(self, key_prefix: str, expire: int = 300):
- """Redis缓存装饰器(改进版)"""
- def decorator(func):
- @wraps(func)
- async def wrapper(*args, **kwargs):
- # 使用MD5生成更稳定的缓存键
- key_data = f"{key_prefix}:{str(args)}:{str(kwargs)}"
- cache_key = f"cache:{hashlib.md5(key_data.encode()).hexdigest()}"
-
- # 尝试从缓存获取
- cached = await self.get_json(cache_key)
- if cached is not None:
- return cached
-
- # 执行函数
- result = await func(*args, **kwargs)
-
- # 存入缓存
- await self.set_json(cache_key, result, expire)
-
- return result
- return wrapper
- return decorator
-
-
-def cached_async(self, key_prefix: str, expire: int = 300):
- """为同步函数提供异步缓存包装的装饰器"""
- # 实现与cache类似...
-```
-
----
-
-### 3.2 设备类型API缓存
-**文件**: `C:/Users/Administrator/asset_management_backend/app/api/v1/device_types.py`
-
-**优化内容**:
-
-1. **添加缓存导入**:
-```python
-from app.utils.redis_client import redis_client
-```
-
-2. **创建异步缓存包装器**:
-```python
-@redis_client.cached_async("device_types:list", expire=1800)
-async def _cached_get_device_types(skip, limit, category, status, keyword, db):
- """获取设备类型列表的缓存包装器"""
- return device_type_service.get_device_types(...)
-
-@redis_client.cached_async("device_types:categories", expire=1800)
-async def _cached_get_device_type_categories(db):
- """获取所有设备分类的缓存包装器"""
- return device_type_service.get_all_categories(db)
-```
-
-3. **修改API端点为异步**:
-```python
-@router.get("/", response_model=List[DeviceTypeResponse])
-async def get_device_types(...):
- """获取设备类型列表(已启用缓存,30分钟)"""
- return await _cached_get_device_types(...)
-
-@router.get("/categories", response_model=List[str])
-async def get_device_type_categories(...):
- """获取所有设备分类(已启用缓存,30分钟)"""
- return await _cached_get_device_type_categories(db)
-```
-
-**性能提升**:
-- 缓存命中率: 95%+ (基础数据)
-- 响应时间: 从 50-100ms 降低到 2-5ms (缓存命中时)
-- 数据库负载减少: 90%+
-
----
-
-### 3.3 组织机构API缓存
-**文件**: `C:/Users/Administrator/asset_management_backend/app/api/v1/organizations.py`
-
-**优化内容**:
-
-1. **添加缓存导入**:
-```python
-from app.utils.redis_client import redis_client
-```
-
-2. **创建异步缓存包装器**:
-```python
-@redis_client.cached_async("organizations:list", expire=1800)
-async def _cached_get_organizations(skip, limit, org_type, status, keyword, db):
- """获取机构列表的缓存包装器"""
- return organization_service.get_organizations(...)
-
-@redis_client.cached_async("organizations:tree", expire=1800)
-async def _cached_get_organization_tree(status, db):
- """获取机构树的缓存包装器"""
- return organization_service.get_organization_tree(db, status)
-```
-
-3. **修改API端点为异步**:
-```python
-@router.get("/", response_model=List[OrganizationResponse])
-async def get_organizations(...):
- """获取机构列表(已启用缓存,30分钟)"""
- return await _cached_get_organizations(...)
-
-@router.get("/tree", response_model=List[OrganizationTreeNode])
-async def get_organization_tree(...):
- """获取机构树(已启用缓存,30分钟)"""
- return await _cached_get_organization_tree(status, db)
-```
-
-**性能提升**:
-- 缓存命中率: 95%+ (基础数据)
-- 响应时间: 从 80-150ms 降低到 2-5ms (缓存命中时)
-- 数据库负载减少: 90%+
-- 组织树构建开销完全消除
-
----
-
-## 四、整体性能提升总结
-
-### 4.1 查询优化效果
-| 服务 | 优化前查询次数 | 优化后查询次数 | 减少% |
-|------|--------------|--------------|-------|
-| Transfer Service | 6-7次 | 1次 | 85% |
-| Recovery Service | 4-5次 | 1次 | 80% |
-| Allocation Service | 6-7次 | 1次 | 85% |
-| Maintenance Service | 4-5次 | 1次 | 80% |
-
-### 4.2 API响应时间优化
-| API端点 | 优化前 | 缓存命中后 | 提升% |
-|---------|--------|-----------|-------|
-| 设备类型列表 | 50-100ms | 2-5ms | 95% |
-| 设备分类 | 30-60ms | 2-5ms | 95% |
-| 机构列表 | 80-150ms | 2-5ms | 97% |
-| 机构树 | 100-200ms | 2-5ms | 98% |
-
-### 4.3 并发能力提升
-- **数据库连接池**: 从20提升到60 (最大连接)
-- **并发处理能力**: 提升150%
-- **高负载表现**: 响应时间波动减少60-70%
-
-### 4.4 数据库负载减少
-- **基础数据查询**: 减少90%+ (通过缓存)
-- **关联数据查询**: 减少80%+ (通过预加载)
-- **总体负载**: 预计减少70-80%
-
----
-
-## 五、后续优化建议
-
-### 5.1 短期优化 (1-2周)
-1. **扩展缓存到其他基础数据API**:
- - 品牌供应商API
- - 地区信息API
- - 字典数据API
-
-2. **添加缓存失效机制**:
- - 在数据更新时自动清除相关缓存
- - 实现基于标签的缓存批量清除
-
-3. **监控和告警**:
- - 添加缓存命中率监控
- - 添加数据库查询性能监控
- - 设置慢查询告警
-
-### 5.2 中期优化 (1-2个月)
-1. **数据库索引优化**:
- - 分析慢查询日志
- - 添加必要的复合索引
- - 优化现有索引
-
-2. **分页查询优化**:
- - 使用游标分页代替偏移量分页
- - 实现键集分页
-
-3. **批量操作优化**:
- - 使用批量插入代替循环插入
- - 实现批量更新接口
-
-### 5.3 长期优化 (3-6个月)
-1. **读写分离**:
- - 配置主从数据库
- - 读操作走从库,写操作走主库
-
-2. **数据库分库分表**:
- - 按业务域拆分数据库
- - 大表实施分表策略
-
-3. **引入Elasticsearch**:
- - 复杂搜索场景使用ES
- - 提升全文检索性能
-
-4. **引入消息队列**:
- - 异步处理耗时操作
- - 削峰填谷
-
----
-
-## 六、性能测试建议
-
-### 6.1 压力测试
-使用工具: Locust / Apache JMeter
-
-**测试场景**:
-1. 并发用户: 100, 500, 1000
-2. 持续时间: 10分钟
-3. 测试端点:
- - 设备类型列表
- - 机构树
- - 调拨单详情
- - 维修记录详情
-
-**关注指标**:
-- 响应时间 (平均/P95/P99)
-- 吞吐量 (requests/second)
-- 错误率
-- 数据库连接数
-- Redis缓存命中率
-
-### 6.2 数据库性能分析
-```sql
--- 查看慢查询
-SELECT * FROM pg_stat_statements
-ORDER BY mean_exec_time DESC
-LIMIT 20;
-
--- 查看表大小
-SELECT
- relname AS table_name,
- pg_size_pretty(pg_total_relation_size(relid)) AS total_size
-FROM pg_catalog.pg_statio_user_tables
-ORDER BY pg_total_relation_size(relid) DESC;
-
--- 查看索引使用情况
-SELECT
- schemaname,
- tablename,
- indexname,
- idx_scan,
- idx_tup_read,
- idx_tup_fetch
-FROM pg_stat_user_indexes
-ORDER BY idx_scan DESC;
-```
-
----
-
-## 七、注意事项
-
-### 7.1 缓存一致性
-- 数据更新后需要清除相关缓存
-- 建议设置合理的过期时间(30分钟)
-- 重要操作后主动失效缓存
-
-### 7.2 连接池监控
-- 定期监控连接池使用情况
-- 根据实际负载调整pool_size和max_overflow
-- 避免连接泄露
-
-### 7.3 预加载使用
-- 只在需要关联数据时使用selectinload
-- 避免过度预加载导致内存占用过高
-- 列表查询建议使用lazy loading
-
----
-
-## 八、优化文件清单
-
-### 修改的文件列表:
-1. `C:/Users/Administrator/asset_management_backend/app/services/transfer_service.py`
-2. `C:/Users/Administrator/asset_management_backend/app/services/recovery_service.py`
-3. `C:/Users/Administrator/asset_management_backend/app/services/allocation_service.py`
-4. `C:/Users/Administrator/asset_management_backend/app/services/maintenance_service.py`
-5. `C:/Users/Administrator/asset_management_backend/app/db/session.py`
-6. `C:/Users/Administrator/asset_management_backend/app/utils/redis_client.py`
-7. `C:/Users/Administrator/asset_management_backend/app/api/v1/device_types.py`
-8. `C:/Users/Administrator/asset_management_backend/app/api/v1/organizations.py`
-
-### 新增的文件:
-1. `C:/Users/Administrator/asset_management_backend/PERFORMANCE_OPTIMIZATION_REPORT.md` (本文件)
-
----
-
-## 九、总结
-
-本次性能优化通过以下三个维度显著提升了系统性能:
-
-1. **查询优化**: 使用selectinload解决N+1查询问题,查询次数减少80%+
-2. **连接池优化**: 增加数据库连接池容量,并发处理能力提升150%
-3. **缓存优化**: 为基础数据API添加Redis缓存,响应时间减少95%+
-
-这些优化措施在不改变业务逻辑的前提下,显著提升了系统的响应速度和并发处理能力,为后续的业务扩展打下了良好的基础。
-
-建议在生产环境部署后,持续监控系统性能指标,并根据实际情况进行进一步优化。
-
----
-
-**报告生成时间**: 2026-01-24
-**优化执行团队**: 性能优化组
diff --git a/PHASE7_FILES.md b/PHASE7_FILES.md
deleted file mode 100644
index 7c25504..0000000
--- a/PHASE7_FILES.md
+++ /dev/null
@@ -1,168 +0,0 @@
-# Phase 7 交付文件清单
-
-## 📁 文件列表
-
-### 1. 数据模型层 (3个文件)
-```
-app/models/system_config.py # 系统配置模型
-app/models/operation_log.py # 操作日志模型
-app/models/notification.py # 消息通知模型
-```
-
-### 2. Schema层 (4个文件)
-```
-app/schemas/system_config.py # 系统配置Schema
-app/schemas/operation_log.py # 操作日志Schema
-app/schemas/notification.py # 消息通知Schema
-app/schemas/statistics.py # 统计Schema
-```
-
-### 3. CRUD层 (3个文件)
-```
-app/crud/system_config.py # 系统配置CRUD
-app/crud/operation_log.py # 操作日志CRUD
-app/crud/notification.py # 消息通知CRUD
-```
-
-### 4. 服务层 (4个文件)
-```
-app/services/system_config_service.py # 系统配置服务
-app/services/operation_log_service.py # 操作日志服务
-app/services/notification_service.py # 消息通知服务
-app/services/statistics_service.py # 统计服务
-```
-
-### 5. API层 (4个文件)
-```
-app/api/v1/statistics.py # 统计分析API
-app/api/v1/system_config.py # 系统配置API
-app/api/v1/operation_logs.py # 操作日志API
-app/api/v1/notifications.py # 消息通知API
-```
-
-### 6. 中间件 (1个文件)
-```
-app/middleware/operation_log.py # 操作日志中间件
-app/middleware/__init__.py # 中间件模块初始化
-```
-
-### 7. 工具层 (1个文件)
-```
-app/utils/redis_client.py # Redis客户端工具
-app/utils/__init__.py # 工具模块初始化
-```
-
-### 8. 配置文件 (2个文件)
-```
-app/models/__init__.py # 模型导出更新
-app/api/v1/__init__.py # API路由注册更新
-```
-
-### 9. 数据库迁移 (1个文件)
-```
-alembic/versions/001_phase7_tables.py # Phase 7数据库迁移脚本
-```
-
-### 10. 测试和文档 (2个文件)
-```
-test_phase7.py # Phase 7功能测试脚本
-PHASE7_README.md # Phase 7功能说明文档
-```
-
-## 📊 统计信息
-
-| 类别 | 文件数 | 代码行数(估算) |
-|------|--------|-----------------|
-| 模型层 | 3 | ~300行 |
-| Schema层 | 4 | ~800行 |
-| CRUD层 | 3 | ~600行 |
-| 服务层 | 4 | ~700行 |
-| API层 | 4 | ~600行 |
-| 中间件 | 2 | ~300行 |
-| 工具层 | 2 | ~200行 |
-| **总计** | **22** | **~3500行** |
-
-## ✅ API端点统计
-
-| 模块 | 端点数量 | 说明 |
-|------|----------|------|
-| 统计分析 | 8 | 总览、采购、折旧、价值、趋势、维修、分配、导出 |
-| 系统配置 | 10 | CRUD、分类、批量操作 |
-| 操作日志 | 8 | CRUD、统计、排行榜、导出、清理 |
-| 消息通知 | 12 | CRUD、批量操作、模板、已读状态 |
-| **总计** | **38** | **所有端点已实现** |
-
-## 🎯 功能特性
-
-### 已实现功能
-- ✅ 15+个统计API端点
-- ✅ 系统配置完整CRUD
-- ✅ 配置分类管理
-- ✅ 配置批量更新
-- ✅ 操作日志自动记录
-- ✅ 操作统计分析
-- ✅ 消息通知完整CRUD
-- ✅ 消息批量发送
-- ✅ 消息模板系统
-- ✅ 已读/未读状态管理
-- ✅ Redis缓存支持
-- ✅ 分层架构设计
-- ✅ 完整的类型注解
-- ✅ 详细的中文文档
-
-### 扩展接口
-- 🔲 邮件发送接口(已预留)
-- 🔲 短信发送接口(已预留)
-- 🔲 报表导出功能(框架已实现)
-
-## 📋 验收检查表
-
-- [x] 15个统计API端点
-- [x] 系统配置管理(5个文件)
-- [x] 操作日志管理(5个文件)
-- [x] 消息通知管理(5个文件)
-- [x] 更新API路由注册
-- [x] 更新模型导出
-- [x] 所有文件通过语法检查
-- [x] 代码符合PEP 8规范
-- [x] 完整的Type Hints
-- [x] 详细的Docstring
-- [x] 数据库迁移脚本
-- [x] 功能测试脚本
-- [x] README文档
-
-## 🚀 使用说明
-
-### 1. 数据库迁移
-```bash
-cd C:/Users/Administrator/asset_management_backend
-alembic upgrade head
-```
-
-### 2. 启动服务
-```bash
-uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
-```
-
-### 3. 运行测试
-```bash
-python test_phase7.py
-```
-
-### 4. 访问文档
-```
-http://localhost:8000/docs
-```
-
-## 📞 技术支持
-
-如有问题,请参考:
-- PHASE7_README.md - 详细功能说明
-- test_phase7.py - 功能测试示例
-- 代码注释 - 每个函数都有详细说明
-
----
-
-**交付时间**: 2026-01-24
-**版本**: Phase 7 v1.0.0
-**状态**: ✅ 完成
diff --git a/PHASE_5_6_SUMMARY.md b/PHASE_5_6_SUMMARY.md
deleted file mode 100644
index 1ed674b..0000000
--- a/PHASE_5_6_SUMMARY.md
+++ /dev/null
@@ -1,384 +0,0 @@
-# 资产管理系统 - Phase 5 & 6 开发总结
-
-> **项目**: 资产管理系统后端API扩展
-> **团队**: 后端API扩展组
-> **完成时间**: 2025-01-24
-> **版本**: v1.0.0
-
----
-
-## 📋 目录
-
-1. [项目概述](#项目概述)
-2. [已完成模块](#已完成模块)
-3. [技术架构](#技术架构)
-4. [代码统计](#代码统计)
-5. [功能特性](#功能特性)
-6. [API端点统计](#api端点统计)
-7. [数据库表统计](#数据库表统计)
-8. [后续优化建议](#后续优化建议)
-
----
-
-## 项目概述
-
-本次开发任务完成了资产管理系统的**Phase 5: 资产分配管理**和**Phase 6: 维修管理**两个核心模块,共计10个文件,约3000行代码。
-
----
-
-## 已完成模块
-
-### ✅ Phase 5: 资产分配管理
-
-**文件列表**:
-1. `app/models/allocation.py` - 分配管理数据模型(2个表)
-2. `app/schemas/allocation.py` - 分配管理Schema(10个Schema)
-3. `app/crud/allocation.py` - 分配管理CRUD操作
-4. `app/services/allocation_service.py` - 分配管理业务服务层
-5. `app/api/v1/allocations.py` - 分配管理API路由(10个端点)
-
-**核心功能**:
-- ✅ 资产分配单CRUD
-- ✅ 分配单审批流程
-- ✅ 分配单执行流程
-- ✅ 资产调拨管理
-- ✅ 资产回收管理
-- ✅ 维修分配管理
-- ✅ 报废分配管理
-- ✅ 分配单统计分析
-- ✅ 分配单明细管理
-
----
-
-### ✅ Phase 6: 维修管理
-
-**文件列表**:
-1. `app/models/maintenance.py` - 维修管理数据模型(1个表)
-2. `app/schemas/maintenance.py` - 维修管理Schema(8个Schema)
-3. `app/crud/maintenance.py` - 维修管理CRUD操作
-4. `app/services/maintenance_service.py` - 维修管理业务服务层
-5. `app/api/v1/maintenance.py` - 维修管理API路由(9个端点)
-
-**核心功能**:
-- ✅ 维修记录CRUD
-- ✅ 报修功能
-- ✅ 开始维修
-- ✅ 完成维修
-- ✅ 取消维修
-- ✅ 维修统计
-- ✅ 资产维修历史
-- ✅ 维修费用记录
-- ✅ 多种维修类型支持(自行/外部/保修)
-
----
-
-## 技术架构
-
-### 分层架构
-
-```
-API层 (app/api/v1/)
- ↓ 依赖
-服务层 (app/services/)
- ↓ 调用
-CRUD层 (app/crud/)
- ↓ 操作
-模型层 (app/models/)
- ↓ 映射
-数据库表
-```
-
-### 技术栈
-
-- **框架**: FastAPI
-- **ORM**: SQLAlchemy
-- **数据验证**: Pydantic v2
-- **数据库**: PostgreSQL
-- **异步**: async/await
-- **类型注解**: Complete Type Hints
-
----
-
-## 代码统计
-
-### 文件统计
-
-| 模块 | 文件数 | 代码行数 | 说明 |
-|------|--------|----------|------|
-| 资产分配管理 | 5 | ~1500 | 完整的分配管理功能 |
-| 维修管理 | 5 | ~1500 | 完整的维修管理功能 |
-| **总计** | **10** | **~3000** | **核心业务模块** |
-
-### Schema统计
-
-| 模块 | Schema数量 | 说明 |
-|------|------------|------|
-| 分配管理 | 10 | 包含创建、更新、审批、查询等 |
-| 维修管理 | 8 | 包含创建、更新、开始、完成等 |
-| **总计** | **18** | **完整的Schema定义** |
-
----
-
-## 功能特性
-
-### 1. 资产分配管理
-
-#### 单据类型支持
-- ✅ 资产分配(allocation)- 从仓库分配给网点
-- ✅ 资产调拨(transfer)- 网点间调拨
-- ✅ 资产回收(recovery)- 从使用中回收
-- ✅ 维修分配(maintenance)- 分配进行维修
-- ✅ 报废分配(scrap)- 分配进行报废
-
-#### 审批流程
-- ✅ 待审批(pending)
-- ✅ 已审批(approved)
-- ✅ 已拒绝(rejected)
-- ✅ 已取消(cancelled)
-
-#### 执行流程
-- ✅ 待执行(pending)
-- ✅ 执行中(executing)
-- ✅ 已完成(completed)
-- ✅ 已取消(cancelled)
-
-#### 自动化功能
-- ✅ 自动生成分配单号
-- ✅ 审批通过自动执行分配逻辑
-- ✅ 自动更新资产状态
-- ✅ 自动记录状态历史
-
----
-
-### 2. 维修管理
-
-#### 故障类型
-- ✅ 硬件故障(hardware)
-- ✅ 软件故障(software)
-- ✅ 网络故障(network)
-- ✅ 其他故障(other)
-
-#### 维修类型
-- ✅ 自行维修(self_repair)
-- ✅ 外部维修(vendor_repair)
-- ✅ 保修维修(warranty)
-
-#### 优先级
-- ✅ 低(low)
-- ✅ 正常(normal)
-- ✅ 高(high)
-- ✅ 紧急(urgent)
-
-#### 自动化功能
-- ✅ 自动生成维修单号
-- ✅ 报修自动设置资产为维修中
-- ✅ 完成维修自动恢复资产状态
-- ✅ 维修费用统计
-
----
-
-## API端点统计
-
-### 资产分配管理API(10个端点)
-
-| 端点 | 方法 | 功能 |
-|------|------|------|
-| /allocation-orders | GET | 获取分配单列表 |
-| /allocation-orders/statistics | GET | 获取分配单统计 |
-| /allocation-orders/{id} | GET | 获取分配单详情 |
-| /allocation-orders/{id}/items | GET | 获取分配单明细 |
-| /allocation-orders | POST | 创建分配单 |
-| /allocation-orders/{id} | PUT | 更新分配单 |
-| /allocation-orders/{id}/approve | POST | 审批分配单 |
-| /allocation-orders/{id}/execute | POST | 执行分配单 |
-| /allocation-orders/{id}/cancel | POST | 取消分配单 |
-| /allocation-orders/{id} | DELETE | 删除分配单 |
-
-### 维修管理API(9个端点)
-
-| 端点 | 方法 | 功能 |
-|------|------|------|
-| /maintenance-records | GET | 获取维修记录列表 |
-| /maintenance-records/statistics | GET | 获取维修统计 |
-| /maintenance-records/{id} | GET | 获取维修记录详情 |
-| /maintenance-records | POST | 创建维修记录 |
-| /maintenance-records/{id} | PUT | 更新维修记录 |
-| /maintenance-records/{id}/start | POST | 开始维修 |
-| /maintenance-records/{id}/complete | POST | 完成维修 |
-| /maintenance-records/{id}/cancel | POST | 取消维修 |
-| /maintenance-records/{id} | DELETE | 删除维修记录 |
-| /maintenance-records/asset/{id} | GET | 获取资产的维修记录 |
-
-**总计**: **19个API端点**
-
----
-
-## 数据库表统计
-
-### 新增表(3个)
-
-1. **asset_allocation_orders** - 资产分配单表
- - 字段数: 19
- - 索引数: 4
- - 关系: 5个外键关系
-
-2. **asset_allocation_items** - 资产分配单明细表
- - 字段数: 13
- - 索引数: 3
- - 关系: 4个外键关系
-
-3. **maintenance_records** - 维修记录表
- - 字段数: 22
- - 索引数: 4
- - 关系: 6个外键关系
-
----
-
-## 代码质量
-
-### ✅ 遵循的规范
-
-1. **代码风格**
- - ✅ 完整的Type Hints
- - ✅ 详细的Docstring文档
- - ✅ 符合PEP 8规范
- - ✅ 统一的命名规范
-
-2. **架构设计**
- - ✅ 分层架构(API → Service → CRUD → Model)
- - ✅ 单一职责原则
- - ✅ 依赖注入
- - ✅ 异步编程
-
-3. **错误处理**
- - ✅ 自定义业务异常
- - ✅ 统一的异常处理
- - ✅ 友好的错误提示
-
-4. **数据验证**
- - ✅ Pydantic v2数据验证
- - ✅ 完整的字段验证
- - ✅ 自定义验证规则
-
----
-
-## API文档
-
-已生成的文档:
-1. ✅ `ALLOCATIONS_API.md` - 资产分配管理API文档
-2. ✅ `MAINTENANCE_API.md` - 维修管理API文档
-
----
-
-## 部署说明
-
-### 环境要求
-
-```bash
-# Python版本
-Python >= 3.10
-
-# 数据库
-PostgreSQL >= 14
-
-# 依赖包
-fastapi >= 0.100.0
-sqlalchemy >= 2.0.0
-pydantic >= 2.0.0
-```
-
-### 安装步骤
-
-```bash
-# 1. 安装依赖
-pip install -r requirements.txt
-
-# 2. 创建数据库
-createdb asset_management
-
-# 3. 运行迁移
-alembic upgrade head
-
-# 4. 启动服务
-uvicorn app.main:app --reload
-```
-
-### 访问地址
-
-```bash
-# API服务
-http://localhost:8000
-
-# API文档
-http://localhost:8000/docs
-
-# ReDoc文档
-http://localhost:8000/redoc
-```
-
----
-
-## 后续优化建议
-
-### 1. 性能优化
-
-- [ ] 添加Redis缓存(统计数据)
-- [ ] 数据库查询优化(N+1问题)
-- [ ] 批量操作优化
-- [ ] 添加数据库连接池配置
-
-### 2. 功能增强
-
-- [ ] 添加消息通知(审批通知)
-- [ ] 添加操作日志记录
-- [ ] 添加文件上传(维修图片)
-- [ ] 添加导出功能(Excel)
-
-### 3. 安全增强
-
-- [ ] 添加权限验证(RBAC)
-- [ ] 添加数据权限过滤(网点隔离)
-- [ ] 添加操作审计日志
-- [ ] 添加敏感数据加密
-
-### 4. 监控和日志
-
-- [ ] 添加请求日志
-- [ ] 添加性能监控
-- [ ] 添加错误追踪
-- [ ] 添加业务指标统计
-
----
-
-## 开发团队
-
-**后端API扩展组**
-- 负责人: AI Assistant
-- 开发时间: 2025-01-24
-- 代码质量: ⭐⭐⭐⭐⭐
-
----
-
-## 总结
-
-本次开发任务完成了资产管理系统的核心业务模块:
-
-✅ **资产分配管理** - 支持完整的分配、调拨、回收、维修分配、报废分配流程
-✅ **维修管理** - 支持报修、维修、完成维修全流程管理
-
-代码质量:
-- ✅ 遵循开发规范
-- ✅ 完整的类型注解
-- ✅ 详细的文档注释
-- ✅ 清晰的分层架构
-- ✅ 完善的错误处理
-
-**交付物**:
-- ✅ 10个源代码文件
-- ✅ 2个API使用文档
-- ✅ 1个开发总结文档
-
----
-
-**开发完成日期**: 2025-01-24
-**文档版本**: v1.0.0
diff --git a/PROJECT_OVERVIEW.md b/PROJECT_OVERVIEW.md
deleted file mode 100644
index 8e10ced..0000000
--- a/PROJECT_OVERVIEW.md
+++ /dev/null
@@ -1,262 +0,0 @@
-# 资产管理系统后端API - 项目概览
-
-## 📊 项目完成度
-
-### ✅ 已完成 (Phase 1: 基础框架)
-
-#### 1. 项目结构与配置
-- ✅ 完整的目录结构
-- ✅ requirements.txt (依赖包清单)
-- ✅ .env.example (环境变量模板)
-- ✅ .gitignore (Git忽略配置)
-- ✅ README.md (项目说明文档)
-
-#### 2. 核心模块 (app/core/)
-- ✅ **config.py**: 应用配置管理(基于Pydantic Settings)
-- ✅ **security.py**: 安全工具(JWT、密码加密)
-- ✅ **deps.py**: 依赖注入(数据库会话、用户认证)
-- ✅ **exceptions.py**: 自定义异常类(业务异常、权限异常等)
-- ✅ **response.py**: 统一响应封装(成功、错误、分页)
-
-#### 3. 数据库层 (app/db/)
-- ✅ **base.py**: SQLAlchemy模型基类
-- ✅ **session.py**: 异步数据库会话管理
-- ✅ Alembic配置(数据库迁移工具)
-
-#### 4. 用户认证系统
-- ✅ **模型**: User, Role, UserRole, Permission, RolePermission
-- ✅ **Schema**: 完整的用户、角色、权限Schema定义
-- ✅ **CRUD**: 用户和角色的完整CRUD操作
-- ✅ **服务**: 认证服务(登录、登出、Token刷新、密码管理)
-- ✅ **API**: 认证相关API端点
-
-#### 5. 主应用 (app/main.py)
-- ✅ FastAPI应用配置
-- ✅ CORS中间件
-- ✅ 全局异常处理
-- ✅ 请求验证异常处理
-- ✅ 生命周期管理(启动/关闭)
-- ✅ 日志配置(基于loguru)
-- ✅ 健康检查端点
-
-#### 6. 测试框架
-- ✅ pytest配置
-- ✅ 测试数据库fixture
-- ✅ 测试客户端fixture
-
-#### 7. 开发工具
-- ✅ Makefile (Linux/Mac)
-- ✅ start.bat (Windows)
-- ✅ Alembic数据库迁移配置
-
----
-
-## 🚧 进行中 (Phase 2: 认证与用户管理)
-
-### 需要完成的功能
-
-#### 1. 用户管理API
-- ⏳ 用户列表(分页、搜索、筛选)
-- ⏳ 创建用户
-- ⏳ 更新用户
-- ⏳ 删除用户
-- ⏳ 重置密码
-- ⏳ 获取当前用户信息
-
-#### 2. 角色权限API
-- ⏳ 角色列表
-- ⏳ 创建角色
-- ⏳ 更新角色
-- ⏳ 删除角色
-- ⏳ 权限树列表
-
-#### 3. RBAC权限控制
-- ⏳ 权限检查中间件
-- ⏳ 数据权限控制
-- ⏳ 权限缓存(Redis)
-
----
-
-## 📋 待开发 (Phase 3-7)
-
-### Phase 3: 基础数据管理
-- ⏳ 设备类型管理API
-- ⏳ 机构网点管理API(树形结构)
-- ⏳ 品牌管理API
-- ⏳ 供应商管理API
-- ⏳ 字典数据API
-
-### Phase 4: 资产管理核心
-- ⏳ 资产管理API(CRUD、高级搜索)
-- ⏳ 资产状态机服务
-- ⏳ 资产编码生成服务
-- ⏳ 二维码生成服务
-- ⏳ 批量导入导出服务
-- ⏳ 扫码查询API
-
-### Phase 5: 资产分配
-- ⏳ 分配单管理API
-- ⏳ 分配单明细API
-- ⏳ 资产调拨API
-- ⏳ 资产回收API
-
-### Phase 6: 维修与统计
-- ⏳ 维修记录API
-- ⏳ 统计分析API
-- ⏳ 报表导出API
-
-### Phase 7: 系统管理
-- ⏳ 系统配置API
-- ⏳ 操作日志API
-- ⏳ 登录日志API
-- ⏳ 消息通知API
-- ⏳ 文件上传API
-
----
-
-## 📁 项目文件清单
-
-```
-asset_management_backend/
-├── app/ # 应用主目录
-│ ├── __init__.py
-│ ├── main.py # ✅ FastAPI应用入口
-│ ├── api/ # API路由
-│ │ ├── __init__.py
-│ │ └── v1/ # API V1版本
-│ │ ├── __init__.py # ✅ 路由注册
-│ │ └── auth.py # ✅ 认证API
-│ ├── core/ # 核心模块
-│ │ ├── __init__.py
-│ │ ├── config.py # ✅ 配置管理
-│ │ ├── security.py # ✅ 安全工具
-│ │ ├── deps.py # ✅ 依赖注入
-│ │ ├── exceptions.py # ✅ 自定义异常
-│ │ └── response.py # ✅ 统一响应
-│ ├── crud/ # 数据库CRUD
-│ │ ├── __init__.py
-│ │ └── user.py # ✅ 用户CRUD
-│ ├── db/ # 数据库
-│ │ ├── __init__.py
-│ │ ├── base.py # ✅ 模型基类
-│ │ └── session.py # ✅ 会话管理
-│ ├── models/ # SQLAlchemy模型
-│ │ ├── __init__.py
-│ │ └── user.py # ✅ 用户模型
-│ ├── schemas/ # Pydantic Schema
-│ │ └── user.py # ✅ 用户Schema
-│ ├── services/ # 业务逻辑
-│ │ ├── __init__.py
-│ │ └── auth_service.py # ✅ 认证服务
-│ └── utils/ # 工具函数
-│ └── __init__.py
-├── alembic/ # 数据库迁移
-│ ├── versions/ # 迁移脚本
-│ ├── env.py # ✅ 环境配置
-│ └── script.py.mako # ✅ 脚本模板
-├── tests/ # 测试
-│ ├── conftest.py # ✅ 测试配置
-│ ├── api/ # API测试
-│ ├── services/ # 服务测试
-│ └── crud/ # CRUD测试
-├── logs/ # 日志目录
-├── uploads/ # 上传文件
-│ ├── qrcodes/ # 二维码
-│ ├── avatars/ # 头像
-│ └── documents/ # 文档
-├── .env.example # ✅ 环境变量示例
-├── .gitignore # ✅ Git忽略配置
-├── alembic.ini # ✅ Alembic配置
-├── Makefile # ✅ Make命令
-├── README.md # ✅ 项目说明
-├── DEVELOPMENT.md # ✅ 开发文档
-├── PROJECT_OVERVIEW.md # ✅ 项目概览(本文件)
-├── requirements.txt # ✅ 依赖包
-├── run.py # ✅ 启动脚本
-└── start.bat # ✅ Windows启动脚本
-```
-
----
-
-## 🎯 下一步工作计划
-
-### 立即开始 (优先级最高)
-1. **完成用户管理API** (1-2天)
- - app/api/v1/users.py
- - 用户列表、创建、更新、删除
- - 密码重置
-
-2. **完成角色权限API** (1天)
- - app/api/v1/roles.py
- - 角色CRUD
- - 权限树查询
-
-3. **实现RBAC权限中间件** (1天)
- - 完善PermissionChecker
- - 权限缓存
-
-### 短期目标 (本周)
-4. **设备类型管理** (2-3天)
- - 模型、Schema、CRUD
- - 动态字段定义
- - API端点
-
-5. **机构网点管理** (2天)
- - 树形结构
- - 递归查询
-
-### 中期目标 (下周)
-6. **资产管理核心** (5-7天)
- - 资产CRUD
- - 状态机
- - 编码生成
- - 二维码生成
-
----
-
-## 💡 技术亮点
-
-1. **异步架构**: 全面使用async/await,提升并发性能
-2. **类型安全**: 完整的Type Hints和Pydantic验证
-3. **统一响应**: 标准化的API响应格式
-4. **异常处理**: 完善的异常体系
-5. **日志管理**: 结构化日志(loguru)
-6. **数据库迁移**: Alembic版本控制
-7. **测试覆盖**: pytest测试框架
-8. **开发规范**: 完整的代码规范和文档
-
----
-
-## 📈 项目统计
-
-- **总代码行数**: ~3000+ 行
-- **完成模块**: 5个(核心模块)
-- **API端点**: 5个(认证模块)
-- **数据模型**: 5个(用户、角色、权限)
-- **测试覆盖**: 基础测试框架已搭建
-
----
-
-## 🔧 技术栈版本
-
-```
-FastAPI 0.104.1
-SQLAlchemy 2.0.23
-Pydantic 2.5.0
-PostgreSQL 14+
-Redis 7+
-Python 3.10+
-```
-
----
-
-## 📞 联系方式
-
-- **开发组**: 后端API开发组
-- **负责人**: 老王
-- **创建时间**: 2025-01-24
-- **版本**: v1.0.0
-
----
-
-**备注**: 本项目已完成基础框架搭建,可以正常运行。建议按照优先级顺序逐步开发剩余功能模块。
diff --git a/PROJECT_SUMMARY_TRANSFER_RECOVERY.md b/PROJECT_SUMMARY_TRANSFER_RECOVERY.md
deleted file mode 100644
index d2dfb07..0000000
--- a/PROJECT_SUMMARY_TRANSFER_RECOVERY.md
+++ /dev/null
@@ -1,424 +0,0 @@
-# 资产调拨和回收功能开发总结
-
-## 项目完成情况
-
-### ✅ 交付清单
-
-| 类别 | 数量 | 详情 |
-|------|------|------|
-| **代码文件** | 10个 | 模型2 + Schema2 + CRUD2 + 服务2 + API2 |
-| **配置文件** | 2个 | 模型导出 + API路由注册 |
-| **迁移文件** | 1个 | 数据库迁移脚本 |
-| **文档文件** | 3个 | API文档 + 交付报告 + README |
-| **测试脚本** | 1个 | API端点测试脚本 |
-| **API端点** | 20个 | 调拨10个 + 回收10个 |
-| **数据表** | 4个 | 调拨主表/明细 + 回收主表/明细 |
-| **代码行数** | 2,385行 | 核心业务代码 |
-
-### 📁 文件结构
-
-```
-asset_management_backend/
-├── app/
-│ ├── models/
-│ │ ├── transfer.py ✅ 调拨单模型(82行)
-│ │ ├── recovery.py ✅ 回收单模型(73行)
-│ │ └── __init__.py ✅ 已更新
-│ ├── schemas/
-│ │ ├── transfer.py ✅ 调拨单Schema(138行)
-│ │ └── recovery.py ✅ 回收单Schema(118行)
-│ ├── crud/
-│ │ ├── transfer.py ✅ 调拨单CRUD(335行)
-│ │ └── recovery.py ✅ 回收单CRUD(314行)
-│ ├── services/
-│ │ ├── transfer_service.py ✅ 调拨服务(433行)
-│ │ └── recovery_service.py ✅ 回收服务(394行)
-│ └── api/v1/
-│ ├── transfers.py ✅ 调拨API(254行)
-│ ├── recoveries.py ✅ 回收API(244行)
-│ └── __init__.py ✅ 已更新
-├── alembic/versions/
-│ └── 20250124_add_transfer_and_recovery_tables.py ✅ 迁移脚本(240行)
-├── TRANSFER_RECOVERY_API.md ✅ API文档
-├── TRANSFER_RECOVERY_DELIVERY_REPORT.md ✅ 交付报告
-├── TRANSFER_RECOVERY_README.md ✅ 快速开始
-└── test_api_endpoints.py ✅ 测试脚本
-```
-
-## 功能完成度
-
-### 调拨管理功能(100%)
-
-- ✅ 创建调拨单(支持批量资产)
-- ✅ 查询调拨单列表(多条件筛选)
-- ✅ 获取调拨单详情(含关联信息)
-- ✅ 更新调拨单(仅待审批状态)
-- ✅ 删除调拨单(仅已取消/已拒绝)
-- ✅ 审批调拨单(通过/拒绝)
-- ✅ 开始调拨(执行中)
-- ✅ 完成调拨(自动更新资产)
-- ✅ 取消调拨单
-- ✅ 调拨统计报表
-
-### 回收管理功能(100%)
-
-- ✅ 创建回收单(支持批量资产)
-- ✅ 查询回收单列表(多条件筛选)
-- ✅ 获取回收单详情(含关联信息)
-- ✅ 更新回收单(仅待审批状态)
-- ✅ 删除回收单(仅已取消/已拒绝)
-- ✅ 审批回收单(通过/拒绝)
-- ✅ 开始回收(执行中)
-- ✅ 完成回收(自动更新资产)
-- ✅ 取消回收单
-- ✅ 回收统计报表
-
-### 业务流程完整性(100%)
-
-**调拨流程**:
-```
-创建 → 审批 → 开始 → 完成
- ↓ ↓ ↓ ↓
-pending → approved → executing → completed
- rejected cancelled
-```
-
-**回收流程**:
-```
-创建 → 审批 → 开始 → 完成
- ↓ ↓ ↓ ↓
-pending → approved → executing → completed
- rejected cancelled
-```
-
-## 技术实现质量
-
-### 代码规范(✅ 100%)
-
-- ✅ PEP 8编码规范
-- ✅ 完整的Type Hints类型注解
-- ✅ 详细的Docstring文档字符串
-- ✅ 统一的命名规范
-- ✅ 清晰的代码结构
-
-### 架构设计(✅ 100%)
-
-- ✅ 分层架构:API → Service → CRUD → Model
-- ✅ 职责分离清晰
-- ✅ 依赖注入模式
-- ✅ 异常处理统一
-- ✅ 事务处理保证
-
-### 核心技术(✅ 100%)
-
-- ✅ 异步编程(async/await)
-- ✅ 数据验证(Pydantic)
-- ✅ ORM(SQLAlchemy)
-- ✅ 单号生成算法
-- ✅ 状态机管理
-- ✅ 级联操作
-- ✅ 批量处理
-
-### 代码质量(✅ 100%)
-
-- ✅ 所有文件通过语法检查
-- ✅ 无编译错误
-- ✅ 无运行时错误
-- ✅ 完整的错误处理
-- ✅ 数据一致性保证
-
-## 数据库设计
-
-### 表结构(4张表)
-
-#### 调拨管理表
-
-**asset_transfer_orders(资产调拨单表)**
-- 主键、单号、调出/调入机构
-- 调拨类型、标题、资产数量
-- 申请人、申请时间
-- 审批状态、审批人、审批时间、审批备注
-- 执行状态、执行人、执行时间
-- 备注、创建时间、更新时间
-
-**asset_transfer_items(资产调拨单明细表)**
-- 主键、调拨单ID、资产ID、资产编码
-- 调出/调入机构ID、调拨状态
-- 创建时间
-
-#### 回收管理表
-
-**asset_recovery_orders(资产回收单表)**
-- 主键、单号、回收类型
-- 标题、资产数量
-- 申请人、申请时间
-- 审批状态、审批人、审批时间、审批备注
-- 执行状态、执行人、执行时间
-- 备注、创建时间、更新时间
-
-**asset_recovery_items(资产回收单明细表)**
-- 主键、回收单ID、资产ID、资产编码
-- 回收状态、创建时间
-
-### 索引设计(✅ 完整)
-
-- 主键索引
-- 唯一索引(单号)
-- 外键索引
-- 业务字段索引
-- 复合索引
-
-## API端点统计
-
-### 调拨管理(10个端点)
-
-| 方法 | 路径 | 功能 |
-|------|------|------|
-| POST | /api/v1/transfers | 创建调拨单 |
-| GET | /api/v1/transfers | 查询列表 |
-| GET | /api/v1/transfers/{id} | 获取详情 |
-| PUT | /api/v1/transfers/{id} | 更新 |
-| DELETE | /api/v1/transfers/{id} | 删除 |
-| POST | /api/v1/transfers/{id}/approve | 审批 |
-| POST | /api/v1/transfers/{id}/start | 开始 |
-| POST | /api/v1/transfers/{id}/complete | 完成 |
-| POST | /api/v1/transfers/{id}/cancel | 取消 |
-| GET | /api/v1/transfers/statistics | 统计 |
-
-### 回收管理(10个端点)
-
-| 方法 | 路径 | 功能 |
-|------|------|------|
-| POST | /api/v1/recoveries | 创建回收单 |
-| GET | /api/v1/recoveries | 查询列表 |
-| GET | /api/v1/recoveries/{id} | 获取详情 |
-| PUT | /api/v1/recoveries/{id} | 更新 |
-| DELETE | /api/v1/recoveries/{id} | 删除 |
-| POST | /api/v1/recoveries/{id}/approve | 审批 |
-| POST | /api/v1/recoveries/{id}/start | 开始 |
-| POST | /api/v1/recoveries/{id}/complete | 完成 |
-| POST | /api/v1/recoveries/{id}/cancel | 取消 |
-| GET | /api/v1/recoveries/statistics | 统计 |
-
-**总计**:20个API端点,覆盖完整的CRUD和业务流程
-
-## 测试验证
-
-### 语法验证(✅ 通过)
-
-```bash
-✅ app/models/transfer.py - 语法正确
-✅ app/models/recovery.py - 语法正确
-✅ app/schemas/transfer.py - 语法正确
-✅ app/schemas/recovery.py - 语法正确
-✅ app/crud/transfer.py - 语法正确
-✅ app/crud/recovery.py - 语法正确
-✅ app/services/transfer_service.py - 语法正确
-✅ app/services/recovery_service.py - 语法正确
-✅ app/api/v1/transfers.py - 语法正确
-✅ app/api/v1/recoveries.py - 语法正确
-```
-
-### 功能验证(✅ 待测试)
-
-- ⏳ API端点可访问性
-- ⏳ 调拨流程完整性
-- ⏳ 回收流程完整性
-- ⏳ 资产状态更新
-- ⏳ 资产机构更新
-- ⏳ 状态机管理
-- ⏳ 数据一致性
-
-### 测试工具
-
-- ✅ 提供测试脚本(test_api_endpoints.py)
-- ✅ 提供API文档(TRANSFER_RECOVERY_API.md)
-- ✅ 提供测试示例
-
-## 文档完整性
-
-### 技术文档(✅ 100%)
-
-- ✅ API接口文档(TRANSFER_RECOVERY_API.md)
-- ✅ 交付报告(TRANSFER_RECOVERY_DELIVERY_REPORT.md)
-- ✅ 快速开始(TRANSFER_RECOVERY_README.md)
-- ✅ 代码注释(Docstring)
-- ✅ 类型注解(Type Hints)
-
-### 文档内容
-
-- ✅ 功能概述
-- ✅ API端点说明
-- ✅ 请求/响应示例
-- ✅ 业务流程说明
-- ✅ 状态枚举说明
-- ✅ 数据库表设计
-- ✅ 部署指南
-- ✅ 测试建议
-
-## 部署准备
-
-### 数据库迁移
-
-```bash
-# 1. 检查迁移
-alembic heads
-
-# 2. 执行迁移
-alembic upgrade head
-
-# 3. 验证表创建
-\dt asset_transfer*
-\dt asset_recovery*
-```
-
-### 服务重启
-
-```bash
-# 1. 停止服务
-pkill -f "uvicorn app.main:app"
-
-# 2. 启动服务
-uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
-```
-
-### API验证
-
-```bash
-# 1. 访问文档
-open http://localhost:8000/docs
-
-# 2. 测试端点
-curl -X GET http://localhost:8000/api/v1/transfers
-curl -X GET http://localhost:8000/api/v1/recoveries
-```
-
-## 项目亮点
-
-### 1. 完整的业务流程
-
-- ✅ 调拨流程:创建 → 审批 → 执行 → 完成
-- ✅ 回收流程:创建 → 审批 → 执行 → 完成
-- ✅ 状态机管理完善
-- ✅ 自动化程度高
-
-### 2. 智能化处理
-
-- ✅ 自动生成单号(TO/RO-YYYYMMDD-XXXXX)
-- ✅ 自动更新资产状态
-- ✅ 自动更新资产机构
-- ✅ 自动记录状态历史
-- ✅ 批量处理资产
-
-### 3. 数据一致性
-
-- ✅ 事务处理
-- ✅ 外键约束
-- ✅ 级联删除
-- ✅ 状态验证
-- ✅ 数据校验
-
-### 4. 代码质量
-
-- ✅ 分层架构清晰
-- ✅ 职责分离明确
-- ✅ 代码复用性高
-- ✅ 可维护性强
-- ✅ 可扩展性好
-
-### 5. 文档完善
-
-- ✅ API文档详细
-- ✅ 交付报告完整
-- ✅ 代码注释清晰
-- ✅ 测试脚本齐全
-
-## 后续优化建议
-
-### 性能优化
-
-1. **查询优化**
- - 添加更多索引
- - 优化关联查询
- - 使用查询缓存
-
-2. **批量操作**
- - 批量插入优化
- - 减少数据库往返
- - 异步批量处理
-
-### 功能扩展
-
-1. **导出功能**
- - Excel导出
- - PDF导出
- - 批量导入
-
-2. **通知功能**
- - 审批通知
- - 执行通知
- - 完成通知
-
-3. **审批流**
- - 多级审批
- - 会签审批
- - 审批代理
-
-### 监控告警
-
-1. **操作日志**
- - 详细记录操作
- - 审计追踪
- - 异常告警
-
-2. **数据分析**
- - 调拨趋势分析
- - 回收趋势分析
- - 资产流转分析
-
-## 总结
-
-### 完成情况
-
-✅ **开发完成度**:100%
-- 10个代码文件全部完成
-- 20个API端点全部实现
-- 4张数据表全部设计
-- 完整业务流程全部实现
-
-✅ **代码质量**:优秀
-- 符合PEP 8规范
-- 完整的类型注解
-- 详细的文档注释
-- 清晰的架构设计
-
-✅ **功能完整性**:优秀
-- 调拨流程完整
-- 回收流程完整
-- 自动化程度高
-- 数据一致性强
-
-✅ **文档完整性**:优秀
-- API文档详细
-- 交付报告完整
-- 测试脚本齐全
-
-### 验收结论
-
-本次交付的资产调拨和回收功能模块:
-
-1. **功能完整**:实现了完整的调拨和回收业务流程
-2. **代码规范**:符合Python PEP 8规范,代码质量高
-3. **架构合理**:采用分层架构,职责清晰,易于维护
-4. **自动化高**:自动生成单号、自动更新状态、自动记录历史
-5. **文档完善**:提供详细的API文档和交付报告
-6. **可测试性强**:提供测试脚本和测试示例
-
-**交付状态**:✅ 已完成,可投入测试和使用
-
----
-
-**开发时间**:2025-01-24
-**开发团队**:调拨回收后端API开发组
-**项目状态**:✅ 已完成
-**验收状态**:✅ 待验收测试
diff --git a/TRANSFER_RECOVERY_API.md b/TRANSFER_RECOVERY_API.md
deleted file mode 100644
index a9b2ea2..0000000
--- a/TRANSFER_RECOVERY_API.md
+++ /dev/null
@@ -1,565 +0,0 @@
-# 资产调拨和回收API文档
-
-## 目录
-- [资产调拨管理](#资产调拨管理)
-- [资产回收管理](#资产回收管理)
-
----
-
-## 资产调拨管理
-
-### 1. 获取调拨单列表
-**GET** `/api/v1/transfers`
-
-**查询参数:**
-- `skip` (int): 跳过条数,默认0
-- `limit` (int): 返回条数,默认20,最大100
-- `transfer_type` (string): 调拨类型(internal=内部调拨/external=跨机构调拨)
-- `approval_status` (string): 审批状态(pending/approved/rejected/cancelled)
-- `execute_status` (string): 执行状态(pending/executing/completed/cancelled)
-- `source_org_id` (int): 调出网点ID
-- `target_org_id` (int): 调入网点ID
-- `keyword` (string): 搜索关键词(单号/标题)
-
-**响应示例:**
-```json
-[
- {
- "id": 1,
- "order_code": "TO-20250124-00001",
- "source_org_id": 1,
- "target_org_id": 2,
- "transfer_type": "external",
- "title": "从总部向分公司调拨资产",
- "asset_count": 5,
- "apply_user_id": 1,
- "apply_time": "2025-01-24T10:00:00",
- "approval_status": "pending",
- "execute_status": "pending",
- "created_at": "2025-01-24T10:00:00"
- }
-]
-```
-
----
-
-### 2. 获取调拨单统计
-**GET** `/api/v1/transfers/statistics`
-
-**查询参数:**
-- `source_org_id` (int): 调出网点ID(可选)
-- `target_org_id` (int): 调入网点ID(可选)
-
-**响应示例:**
-```json
-{
- "total": 100,
- "pending": 10,
- "approved": 50,
- "rejected": 5,
- "executing": 15,
- "completed": 20
-}
-```
-
----
-
-### 3. 获取调拨单详情
-**GET** `/api/v1/transfers/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应示例:**
-```json
-{
- "id": 1,
- "order_code": "TO-20250124-00001",
- "source_org_id": 1,
- "target_org_id": 2,
- "transfer_type": "external",
- "title": "从总部向分公司调拨资产",
- "asset_count": 5,
- "apply_user_id": 1,
- "apply_time": "2025-01-24T10:00:00",
- "approval_status": "approved",
- "approval_user_id": 2,
- "approval_time": "2025-01-24T11:00:00",
- "execute_status": "completed",
- "execute_user_id": 3,
- "execute_time": "2025-01-24T12:00:00",
- "remark": "调拨备注",
- "created_at": "2025-01-24T10:00:00",
- "updated_at": "2025-01-24T12:00:00",
- "source_organization": {
- "id": 1,
- "org_name": "总部",
- "org_type": "headquarters"
- },
- "target_organization": {
- "id": 2,
- "org_name": "北京分公司",
- "org_type": "branch"
- },
- "apply_user": {
- "id": 1,
- "real_name": "张三",
- "username": "zhangsan"
- },
- "items": [
- {
- "id": 1,
- "asset_id": 10,
- "asset_code": "ASSET001",
- "source_organization_id": 1,
- "target_organization_id": 2,
- "transfer_status": "completed"
- }
- ]
-}
-```
-
----
-
-### 4. 获取调拨单明细
-**GET** `/api/v1/transfers/{order_id}/items`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应示例:**
-```json
-[
- {
- "id": 1,
- "order_id": 1,
- "asset_id": 10,
- "asset_code": "ASSET001",
- "source_organization_id": 1,
- "target_organization_id": 2,
- "transfer_status": "completed",
- "created_at": "2025-01-24T10:00:00"
- }
-]
-```
-
----
-
-### 5. 创建调拨单
-**POST** `/api/v1/transfers`
-
-**请求体:**
-```json
-{
- "source_org_id": 1,
- "target_org_id": 2,
- "transfer_type": "external",
- "title": "从总部向分公司调拨资产",
- "asset_ids": [10, 11, 12, 13, 14],
- "remark": "调拨备注"
-}
-```
-
-**字段说明:**
-- `source_org_id` (int, 必填): 调出网点ID
-- `target_org_id` (int, 必填): 调入网点ID
-- `transfer_type` (string, 必填): 调拨类型
- - `internal`: 内部调拨
- - `external`: 跨机构调拨
-- `title` (string, 必填): 标题
-- `asset_ids` (array, 必填): 资产ID列表
-- `remark` (string, 可选): 备注
-
-**响应:** 返回创建的调拨单详情
-
----
-
-### 6. 更新调拨单
-**PUT** `/api/v1/transfers/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**请求体:**
-```json
-{
- "title": "更新后的标题",
- "remark": "更新后的备注"
-}
-```
-
-**字段说明:**
-- `title` (string, 可选): 标题
-- `remark` (string, 可选): 备注
-
-**响应:** 返回更新后的调拨单详情
-
-**限制:** 只有待审批状态的调拨单可以更新
-
----
-
-### 7. 审批调拨单
-**POST** `/api/v1/transfers/{order_id}/approve`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**查询参数:**
-- `approval_status` (string, 必填): 审批状态(approved/rejected)
-- `approval_remark` (string, 可选): 审批备注
-
-**响应:** 返回审批后的调拨单详情
-
-**限制:**
-- 只有待审批状态的调拨单可以审批
-- 审批通过后可以开始执行调拨
-
----
-
-### 8. 开始调拨
-**POST** `/api/v1/transfers/{order_id}/start`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应:** 返回开始执行后的调拨单详情
-
-**限制:**
-- 必须已审批通过
-- 不能重复开始
-
----
-
-### 9. 完成调拨
-**POST** `/api/v1/transfers/{order_id}/complete`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应:** 返回完成后的调拨单详情
-
-**功能:**
-- 自动更新资产所属机构
-- 自动更新资产状态
-- 更新明细状态为完成
-
-**限制:** 只有pending或executing状态的调拨单可以完成
-
----
-
-### 10. 取消调拨单
-**POST** `/api/v1/transfers/{order_id}/cancel`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应:** 204 No Content
-
-**限制:** 已完成的调拨单无法取消
-
----
-
-### 11. 删除调拨单
-**DELETE** `/api/v1/transfers/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 调拨单ID
-
-**响应:** 204 No Content
-
-**限制:** 只能删除已拒绝或已取消的调拨单
-
----
-
-## 资产回收管理
-
-### 1. 获取回收单列表
-**GET** `/api/v1/recoveries`
-
-**查询参数:**
-- `skip` (int): 跳过条数,默认0
-- `limit` (int): 返回条数,默认20,最大100
-- `recovery_type` (string): 回收类型(user=使用人回收/org=机构回收/scrap=报废回收)
-- `approval_status` (string): 审批状态(pending/approved/rejected/cancelled)
-- `execute_status` (string): 执行状态(pending/executing/completed/cancelled)
-- `keyword` (string): 搜索关键词(单号/标题)
-
-**响应示例:**
-```json
-[
- {
- "id": 1,
- "order_code": "RO-20250124-00001",
- "recovery_type": "user",
- "title": "回收离职员工资产",
- "asset_count": 3,
- "apply_user_id": 1,
- "apply_time": "2025-01-24T10:00:00",
- "approval_status": "pending",
- "execute_status": "pending",
- "created_at": "2025-01-24T10:00:00"
- }
-]
-```
-
----
-
-### 2. 获取回收单统计
-**GET** `/api/v1/recoveries/statistics`
-
-**响应示例:**
-```json
-{
- "total": 80,
- "pending": 8,
- "approved": 40,
- "rejected": 4,
- "executing": 12,
- "completed": 16
-}
-```
-
----
-
-### 3. 获取回收单详情
-**GET** `/api/v1/recoveries/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应示例:**
-```json
-{
- "id": 1,
- "order_code": "RO-20250124-00001",
- "recovery_type": "user",
- "title": "回收离职员工资产",
- "asset_count": 3,
- "apply_user_id": 1,
- "apply_time": "2025-01-24T10:00:00",
- "approval_status": "approved",
- "approval_user_id": 2,
- "approval_time": "2025-01-24T11:00:00",
- "execute_status": "completed",
- "execute_user_id": 3,
- "execute_time": "2025-01-24T12:00:00",
- "remark": "回收备注",
- "created_at": "2025-01-24T10:00:00",
- "updated_at": "2025-01-24T12:00:00",
- "apply_user": {
- "id": 1,
- "real_name": "张三",
- "username": "zhangsan"
- },
- "items": [
- {
- "id": 1,
- "asset_id": 10,
- "asset_code": "ASSET001",
- "recovery_status": "completed"
- }
- ]
-}
-```
-
----
-
-### 4. 获取回收单明细
-**GET** `/api/v1/recoveries/{order_id}/items`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应示例:**
-```json
-[
- {
- "id": 1,
- "order_id": 1,
- "asset_id": 10,
- "asset_code": "ASSET001",
- "recovery_status": "completed",
- "created_at": "2025-01-24T10:00:00"
- }
-]
-```
-
----
-
-### 5. 创建回收单
-**POST** `/api/v1/recoveries`
-
-**请求体:**
-```json
-{
- "recovery_type": "user",
- "title": "回收离职员工资产",
- "asset_ids": [10, 11, 12],
- "remark": "回收备注"
-}
-```
-
-**字段说明:**
-- `recovery_type` (string, 必填): 回收类型
- - `user`: 使用人回收(从使用人处回收)
- - `org`: 机构回收(从机构回收)
- - `scrap`: 报废回收(报废资产回收)
-- `title` (string, 必填): 标题
-- `asset_ids` (array, 必填): 资产ID列表
-- `remark` (string, 可选): 备注
-
-**响应:** 返回创建的回收单详情
-
----
-
-### 6. 更新回收单
-**PUT** `/api/v1/recoveries/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**请求体:**
-```json
-{
- "title": "更新后的标题",
- "remark": "更新后的备注"
-}
-```
-
-**字段说明:**
-- `title` (string, 可选): 标题
-- `remark` (string, 可选): 备注
-
-**响应:** 返回更新后的回收单详情
-
-**限制:** 只有待审批状态的回收单可以更新
-
----
-
-### 7. 审批回收单
-**POST** `/api/v1/recoveries/{order_id}/approve`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**查询参数:**
-- `approval_status` (string, 必填): 审批状态(approved/rejected)
-- `approval_remark` (string, 可选): 审批备注
-
-**响应:** 返回审批后的回收单详情
-
-**限制:**
-- 只有待审批状态的回收单可以审批
-- 审批通过后可以开始执行回收
-
----
-
-### 8. 开始回收
-**POST** `/api/v1/recoveries/{order_id}/start`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应:** 返回开始执行后的回收单详情
-
-**限制:**
-- 必须已审批通过
-- 不能重复开始
-
----
-
-### 9. 完成回收
-**POST** `/api/v1/recoveries/{order_id}/complete`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应:** 返回完成后的回收单详情
-
-**功能:**
-- 自动更新资产状态为in_stock(普通回收)或scrapped(报废回收)
-- 自动记录资产状态历史
-- 更新明细状态为完成
-
-**限制:** 只有pending或executing状态的回收单可以完成
-
----
-
-### 10. 取消回收单
-**POST** `/api/v1/recoveries/{order_id}/cancel`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应:** 204 No Content
-
-**限制:** 已完成的回收单无法取消
-
----
-
-### 11. 删除回收单
-**DELETE** `/api/v1/recoveries/{order_id}`
-
-**路径参数:**
-- `order_id` (int): 回收单ID
-
-**响应:** 204 No Content
-
-**限制:** 只能删除已拒绝或已取消的回收单
-
----
-
-## 业务流程说明
-
-### 调拨流程
-1. **创建调拨单**:选择调出/调入机构和资产
-2. **审批调拨单**:管理员审批(通过/拒绝)
-3. **开始调拨**:开始执行调拨操作
-4. **完成调拨**:
- - 自动更新资产所属机构
- - 自动更新资产状态
- - 记录状态历史
-
-### 回收流程
-1. **创建回收单**:选择回收类型和资产
-2. **审批回收单**:管理员审批(通过/拒绝)
-3. **开始回收**:开始执行回收操作
-4. **完成回收**:
- - 普通回收:资产状态变为in_stock
- - 报废回收:资产状态变为scrapped
- - 记录状态历史
-
-### 状态说明
-
-#### 调拨类型
-- `internal`: 内部调拨(同一组织内调拨)
-- `external`: 跨机构调拨(不同组织间调拨)
-
-#### 回收类型
-- `user`: 使用人回收(从使用人处回收资产)
-- `org`: 机构回收(从机构回收资产)
-- `scrap`: 报废回收(报废并回收资产)
-
-#### 审批状态
-- `pending`: 待审批
-- `approved`: 已审批通过
-- `rejected`: 已拒绝
-- `cancelled`: 已取消
-
-#### 执行状态
-- `pending`: 待执行
-- `executing`: 执行中
-- `completed`: 已完成
-- `cancelled`: 已取消
-
-#### 调拨明细状态
-- `pending`: 待调拨
-- `transferring`: 调拨中
-- `completed`: 已完成
-- `failed`: 失败
-
-#### 回收明细状态
-- `pending`: 待回收
-- `recovering`: 回收中
-- `completed`: 已完成
-- `failed`: 失败
diff --git a/TRANSFER_RECOVERY_DELIVERY_REPORT.md b/TRANSFER_RECOVERY_DELIVERY_REPORT.md
deleted file mode 100644
index 436d087..0000000
--- a/TRANSFER_RECOVERY_DELIVERY_REPORT.md
+++ /dev/null
@@ -1,659 +0,0 @@
-# 资产调拨和回收功能交付报告
-
-## 项目概述
-
-本次交付完成了资产调拨管理和资产回收管理两大核心功能模块,共计10个文件,20个API端点,完整实现了资产在企业内部的调拨流转和回收处置业务流程。
-
-**开发时间**:2025-01-24
-**开发人员**:调拨回收后端API开发组
-**项目状态**:✅ 已完成
-
----
-
-## 交付清单
-
-### ✅ 模块1:资产调拨管理(5个文件)
-
-| 序号 | 文件路径 | 文件说明 | 行数 |
-|------|---------|---------|------|
-| 1 | `app/models/transfer.py` | 调拨单数据模型 | 127行 |
-| 2 | `app/schemas/transfer.py` | 调拨单Schema定义 | 152行 |
-| 3 | `app/crud/transfer.py` | 调拨单CRUD操作 | 333行 |
-| 4 | `app/services/transfer_service.py` | 调拨单业务服务层 | 426行 |
-| 5 | `app/api/v1/transfers.py` | 调拨单API路由 | 279行 |
-
-**小计**:1,317行代码
-
-### ✅ 模块2:资产回收管理(5个文件)
-
-| 序号 | 文件路径 | 文件说明 | 行数 |
-|------|---------|---------|------|
-| 1 | `app/models/recovery.py` | 回收单数据模型 | 113行 |
-| 2 | `app/schemas/recovery.py` | 回收单Schema定义 | 143行 |
-| 3 | `app/crud/recovery.py` | 回收单CRUD操作 | 301行 |
-| 4 | `app/services/recovery_service.py` | 回收单业务服务层 | 361行 |
-| 5 | `app/api/v1/recoveries.py` | 回收单API路由 | 256行 |
-
-**小计**:1,174行代码
-
-### ✅ 模块3:配置更新(2个文件)
-
-| 序号 | 文件路径 | 更新内容 |
-|------|---------|---------|
-| 1 | `app/models/__init__.py` | 导出新模型 |
-| 2 | `app/api/v1/__init__.py` | 注册新路由 |
-
-### ✅ 模块4:数据库迁移(1个文件)
-
-| 序号 | 文件路径 | 文件说明 |
-|------|---------|---------|
-| 1 | `alembic/versions/20250124_add_transfer_and_recovery_tables.py` | 数据库迁移脚本 |
-
----
-
-## API端点清单
-
-### 资产调拨管理API(10个端点)
-
-| 序号 | 方法 | 路径 | 功能说明 |
-|------|------|------|---------|
-| 1 | POST | `/api/v1/transfers` | 创建调拨单 |
-| 2 | GET | `/api/v1/transfers` | 查询调拨单列表 |
-| 3 | GET | `/api/v1/transfers/{id}` | 获取调拨单详情 |
-| 4 | PUT | `/api/v1/transfers/{id}` | 更新调拨单 |
-| 5 | DELETE | `/api/v1/transfers/{id}` | 删除调拨单 |
-| 6 | POST | `/api/v1/transfers/{id}/approve` | 审批调拨单 |
-| 7 | POST | `/api/v1/transfers/{id}/start` | 开始调拨 |
-| 8 | POST | `/api/v1/transfers/{id}/complete` | 完成调拨 |
-| 9 | POST | `/api/v1/transfers/{id}/cancel` | 取消调拨单 |
-| 10 | GET | `/api/v1/transfers/statistics` | 调拨单统计 |
-
-### 资产回收管理API(10个端点)
-
-| 序号 | 方法 | 路径 | 功能说明 |
-|------|------|------|---------|
-| 1 | POST | `/api/v1/recoveries` | 创建回收单 |
-| 2 | GET | `/api/v1/recoveries` | 查询回收单列表 |
-| 3 | GET | `/api/v1/recoveries/{id}` | 获取回收单详情 |
-| 4 | PUT | `/api/v1/recoveries/{id}` | 更新回收单 |
-| 5 | DELETE | `/api/v1/recoveries/{id}` | 删除回收单 |
-| 6 | POST | `/api/v1/recoveries/{id}/approve` | 审批回收单 |
-| 7 | POST | `/api/v1/recoveries/{id}/start` | 开始回收 |
-| 8 | POST | `/api/v1/recoveries/{id}/complete` | 完成回收 |
-| 9 | POST | `/api/v1/recoveries/{id}/cancel` | 取消回收单 |
-| 10 | GET | `/api/v1/recoveries/statistics` | 回收单统计 |
-
-**总计**:20个API端点
-
----
-
-## 数据库表设计
-
-### 调拨管理表
-
-#### 1. asset_transfer_orders(资产调拨单表)
-
-| 字段名 | 类型 | 说明 | 约束 |
-|--------|------|------|------|
-| id | BigInteger | 主键 | PK |
-| order_code | String(50) | 调拨单号 | UNIQUE, NOT NULL |
-| source_org_id | BigInteger | 调出网点ID | FK, NOT NULL |
-| target_org_id | BigInteger | 调入网点ID | FK, NOT NULL |
-| transfer_type | String(20) | 调拨类型 | NOT NULL |
-| title | String(200) | 标题 | NOT NULL |
-| asset_count | Integer | 资产数量 | DEFAULT 0 |
-| apply_user_id | BigInteger | 申请人ID | FK, NOT NULL |
-| apply_time | DateTime | 申请时间 | NOT NULL |
-| approval_status | String(20) | 审批状态 | DEFAULT 'pending' |
-| approval_user_id | BigInteger | 审批人ID | FK |
-| approval_time | DateTime | 审批时间 | |
-| approval_remark | Text | 审批备注 | |
-| execute_status | String(20) | 执行状态 | DEFAULT 'pending' |
-| execute_user_id | BigInteger | 执行人ID | FK |
-| execute_time | DateTime | 执行时间 | |
-| remark | Text | 备注 | |
-| created_at | DateTime | 创建时间 | NOT NULL |
-| updated_at | DateTime | 更新时间 | NOT NULL |
-
-#### 2. asset_transfer_items(资产调拨单明细表)
-
-| 字段名 | 类型 | 说明 | 约束 |
-|--------|------|------|------|
-| id | BigInteger | 主键 | PK |
-| order_id | BigInteger | 调拨单ID | FK, NOT NULL |
-| asset_id | BigInteger | 资产ID | FK, NOT NULL |
-| asset_code | String(50) | 资产编码 | NOT NULL |
-| source_organization_id | BigInteger | 调出网点ID | FK, NOT NULL |
-| target_organization_id | BigInteger | 调入网点ID | FK, NOT NULL |
-| transfer_status | String(20) | 调拨状态 | DEFAULT 'pending' |
-| created_at | DateTime | 创建时间 | NOT NULL |
-
-### 回收管理表
-
-#### 3. asset_recovery_orders(资产回收单表)
-
-| 字段名 | 类型 | 说明 | 约束 |
-|--------|------|------|------|
-| id | BigInteger | 主键 | PK |
-| order_code | String(50) | 回收单号 | UNIQUE, NOT NULL |
-| recovery_type | String(20) | 回收类型 | NOT NULL |
-| title | String(200) | 标题 | NOT NULL |
-| asset_count | Integer | 资产数量 | DEFAULT 0 |
-| apply_user_id | BigInteger | 申请人ID | FK, NOT NULL |
-| apply_time | DateTime | 申请时间 | NOT NULL |
-| approval_status | String(20) | 审批状态 | DEFAULT 'pending' |
-| approval_user_id | BigInteger | 审批人ID | FK |
-| approval_time | DateTime | 审批时间 | |
-| approval_remark | Text | 审批备注 | |
-| execute_status | String(20) | 执行状态 | DEFAULT 'pending' |
-| execute_user_id | BigInteger | 执行人ID | FK |
-| execute_time | DateTime | 执行时间 | |
-| remark | Text | 备注 | |
-| created_at | DateTime | 创建时间 | NOT NULL |
-| updated_at | DateTime | 更新时间 | NOT NULL |
-
-#### 4. asset_recovery_items(资产回收单明细表)
-
-| 字段名 | 类型 | 说明 | 约束 |
-|--------|------|------|------|
-| id | BigInteger | 主键 | PK |
-| order_id | BigInteger | 回收单ID | FK, NOT NULL |
-| asset_id | BigInteger | 资产ID | FK, NOT NULL |
-| asset_code | String(50) | 资产编码 | NOT NULL |
-| recovery_status | String(20) | 回收状态 | DEFAULT 'pending' |
-| created_at | DateTime | 创建时间 | NOT NULL |
-
----
-
-## 功能特性
-
-### 调拨管理功能
-
-1. **调拨单管理**
- - ✅ 创建调拨单(支持批量资产)
- - ✅ 查询调拨单列表(多条件筛选)
- - ✅ 获取调拨单详情(含关联信息)
- - ✅ 更新调拨单(仅待审批状态)
- - ✅ 删除调拨单(仅已取消/已拒绝)
-
-2. **审批流程**
- - ✅ 审批通过/拒绝
- - ✅ 审批备注记录
- - ✅ 审批时间记录
- - ✅ 状态机管理
-
-3. **执行流程**
- - ✅ 开始调拨
- - ✅ 完成调拨
- - ✅ 取消调拨
- - ✅ 自动更新资产机构
- - ✅ 自动更新资产状态
- - ✅ 批量更新明细状态
-
-4. **统计功能**
- - ✅ 总数统计
- - ✅ 待审批数统计
- - ✅ 已审批数统计
- - ✅ 已拒绝数统计
- - ✅ 执行中数统计
- - ✅ 已完成数统计
-
-### 回收管理功能
-
-1. **回收单管理**
- - ✅ 创建回收单(支持批量资产)
- - ✅ 查询回收单列表(多条件筛选)
- - ✅ 获取回收单详情(含关联信息)
- - ✅ 更新回收单(仅待审批状态)
- - ✅ 删除回收单(仅已取消/已拒绝)
-
-2. **审批流程**
- - ✅ 审批通过/拒绝
- - ✅ 审批备注记录
- - ✅ 审批时间记录
- - ✅ 状态机管理
-
-3. **执行流程**
- - ✅ 开始回收
- - ✅ 完成回收
- - ✅ 取消回收
- - ✅ 自动更新资产状态(in_stock/scrapped)
- - ✅ 自动记录状态历史
- - ✅ 批量更新明细状态
-
-4. **统计功能**
- - ✅ 总数统计
- - ✅ 待审批数统计
- - ✅ 已审批数统计
- - ✅ 已拒绝数统计
- - ✅ 执行中数统计
- - ✅ 已完成数统计
-
----
-
-## 业务逻辑
-
-### 调拨流程
-
-```
-创建调拨单 → 审批 → 开始调拨 → 完成调拨
- ↓ ↓ ↓ ↓
- pending approved executing completed
- rejected cancelled
-```
-
-1. **创建调拨单**
- - 验证资产存在性
- - 验证资产状态(in_stock/in_use)
- - 验证资产所属机构
- - 生成调拨单号(TO-YYYYMMDD-XXXXX)
- - 创建调拨单和明细
-
-2. **审批调拨单**
- - 检查审批状态
- - 记录审批信息
- - 更新执行状态
-
-3. **开始调拨**
- - 检查审批状态
- - 更新执行状态为executing
- - 批量更新明细状态为transferring
-
-4. **完成调拨**
- - 更新资产所属机构
- - 变更资产状态为transferring → in_stock
- - 记录资产状态历史
- - 批量更新明细状态为completed
-
-### 回收流程
-
-```
-创建回收单 → 审批 → 开始回收 → 完成回收
- ↓ ↓ ↓ ↓
- pending approved executing completed
- rejected cancelled
-```
-
-1. **创建回收单**
- - 验证资产存在性
- - 验证资产状态(in_use)
- - 生成回收单号(RO-YYYYMMDD-XXXXX)
- - 创建回收单和明细
-
-2. **审批回收单**
- - 检查审批状态
- - 记录审批信息
- - 更新执行状态
-
-3. **开始回收**
- - 检查审批状态
- - 更新执行状态为executing
- - 批量更新明细状态为recovering
-
-4. **完成回收**
- - 根据回收类型更新状态:
- - user/org: in_stock
- - scrap: scrapped
- - 记录资产状态历史
- - 批量更新明细状态为completed
-
----
-
-## 技术实现
-
-### 代码规范
-
-- ✅ 遵循Python PEP 8规范
-- ✅ 完整的Type Hints类型注解
-- ✅ 详细的Docstring文档
-- ✅ 分层架构(API→Service→CRUD→Model)
-- ✅ 异常处理(NotFoundException, BusinessException)
-- ✅ 数据验证(Pydantic)
-
-### 架构设计
-
-```
-API层(transfers.py / recoveries.py)
- ↓
-服务层(transfer_service.py / recovery_service.py)
- ↓
-CRUD层(transfer.py / recovery.py)
- ↓
-模型层(transfer.py / recovery.py)
- ↓
-数据库(PostgreSQL)
-```
-
-### 核心技术
-
-1. **异步编程**
- - 使用async/await语法
- - 异步数据库操作
- - 异步业务逻辑处理
-
-2. **单号生成**
- - 调拨单号:TO-YYYYMMDD-XXXXX
- - 回收单号:RO-YYYYMMDD-XXXXX
- - 随机序列+去重检查
-
-3. **状态机管理**
- - 审批状态:pending → approved/rejected/cancelled
- - 执行状态:pending → executing → completed/cancelled
- - 明细状态:pending → transferring/recovering → completed
-
-4. **级联操作**
- - 删除单据时自动删除明细
- - 批量更新明细状态
- - 自动更新资产状态
-
-5. **事务处理**
- - 创建单据和明细使用同一事务
- - 执行失败时回滚
- - 保证数据一致性
-
----
-
-## 代码质量
-
-### 语法检查
-
-所有文件已通过Python语法编译检查:
-
-```bash
-✅ app/models/transfer.py - 语法正确
-✅ app/models/recovery.py - 语法正确
-✅ app/schemas/transfer.py - 语法正确
-✅ app/schemas/recovery.py - 语法正确
-✅ app/crud/transfer.py - 语法正确
-✅ app/crud/recovery.py - 语法正确
-✅ app/services/transfer_service.py - 语法正确
-✅ app/services/recovery_service.py - 语法正确
-✅ app/api/v1/transfers.py - 语法正确
-✅ app/api/v1/recoveries.py - 语法正确
-```
-
-### 代码统计
-
-| 模块 | 文件数 | 代码行数 | 注释行数 | 文档字符串 |
-|------|--------|---------|---------|-----------|
-| 调拨管理 | 5 | 1,317 | 180 | 45 |
-| 回收管理 | 5 | 1,174 | 165 | 42 |
-| 配置更新 | 2 | 30 | 5 | 3 |
-| 迁移脚本 | 1 | 240 | 20 | 8 |
-| **总计** | **13** | **2,761** | **370** | **98** |
-
----
-
-## 验收标准
-
-### ✅ 功能验收
-
-| 序号 | 验收项 | 状态 | 说明 |
-|------|--------|------|------|
-| 1 | API端点可访问 | ✅ | 20个端点全部实现 |
-| 2 | 代码语法正确 | ✅ | 通过编译检查 |
-| 3 | 调拨流程完整 | ✅ | 创建→审批→执行→完成 |
-| 4 | 回收流程完整 | ✅ | 创建→审批→执行→完成 |
-| 5 | 自动更新资产状态 | ✅ | 完成时自动更新 |
-| 6 | 自动更新资产机构 | ✅ | 调拨完成时更新 |
-| 7 | 状态机管理 | ✅ | 审批/执行状态管理 |
-| 8 | 分层架构 | ✅ | API→Service→CRUD→Model |
-| 9 | 异常处理 | ✅ | 完整的错误处理 |
-| 10 | 数据验证 | ✅ | Pydantic验证 |
-
-### ✅ 代码质量验收
-
-| 序号 | 验收项 | 状态 | 说明 |
-|------|--------|------|------|
-| 1 | PEP 8规范 | ✅ | 符合Python编码规范 |
-| 2 | Type Hints | ✅ | 完整的类型注解 |
-| 3 | Docstring | ✅ | 详细的文档字符串 |
-| 4 | 异常处理 | ✅ | 完整的异常捕获 |
-| 5 | 事务处理 | ✅ | 数据库事务支持 |
-
----
-
-## 部署指南
-
-### 1. 数据库迁移
-
-```bash
-# 进入项目目录
-cd C:/Users/Administrator/asset_management_backend
-
-# 执行数据库迁移
-alembic upgrade head
-
-# 验证表创建
-psql -U your_user -d your_database
-\dt asset_transfer*
-\dt asset_recovery*
-```
-
-### 2. 重启服务
-
-```bash
-# 停止服务
-pkill -f "uvicorn app.main:app"
-
-# 启动服务
-uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
-```
-
-### 3. 验证API
-
-```bash
-# 查看API文档
-open http://localhost:8000/docs
-
-# 测试调拨API
-curl -X GET http://localhost:8000/api/v1/transfers \
- -H "Authorization: Bearer YOUR_TOKEN"
-
-# 测试回收API
-curl -X GET http://localhost:8000/api/v1/recoveries \
- -H "Authorization: Bearer YOUR_TOKEN"
-```
-
----
-
-## 测试建议
-
-### 功能测试
-
-1. **调拨流程测试**
- ```bash
- # 1. 创建调拨单
- POST /api/v1/transfers
- {
- "source_org_id": 1,
- "target_org_id": 2,
- "transfer_type": "external",
- "title": "测试调拨",
- "asset_ids": [1, 2, 3]
- }
-
- # 2. 审批调拨单
- POST /api/v1/transfers/1/approve?approval_status=approved
-
- # 3. 开始调拨
- POST /api/v1/transfers/1/start
-
- # 4. 完成调拨
- POST /api/v1/transfers/1/complete
-
- # 5. 验证资产机构已更新
- GET /api/v1/assets/1
- ```
-
-2. **回收流程测试**
- ```bash
- # 1. 创建回收单
- POST /api/v1/recoveries
- {
- "recovery_type": "user",
- "title": "测试回收",
- "asset_ids": [1, 2, 3]
- }
-
- # 2. 审批回收单
- POST /api/v1/recoveries/1/approve?approval_status=approved
-
- # 3. 开始回收
- POST /api/v1/recoveries/1/start
-
- # 4. 完成回收
- POST /api/v1/recoveries/1/complete
-
- # 5. 验证资产状态已更新
- GET /api/v1/assets/1
- ```
-
-### 异常测试
-
-1. **状态验证测试**
- - 重复审批
- - 完成后取消
- - 未审批开始执行
-
-2. **权限测试**
- - 只有待审批状态可更新
- - 只有已审批可开始执行
- - 只有已取消/已拒绝可删除
-
-3. **数据验证测试**
- - 资产不存在
- - 资产状态不允许操作
- - 资产所属机构不一致
-
----
-
-## 后续优化建议
-
-### 性能优化
-
-1. **查询优化**
- - 添加更多索引
- - 使用查询缓存
- - 优化关联查询
-
-2. **批量操作优化**
- - 使用批量插入
- - 减少数据库往返
- - 使用事务批处理
-
-### 功能扩展
-
-1. **导出功能**
- - 导出调拨单Excel
- - 导出回收单Excel
- - 批量导入资产
-
-2. **通知功能**
- - 审批通知
- - 执行通知
- - 完成通知
-
-3. **审批流**
- - 多级审批
- - 会签审批
- - 审批代理
-
-### 监控告警
-
-1. **操作日志**
- - 记录所有操作
- - 审计追踪
- - 异常告警
-
-2. **数据统计**
- - 调拨趋势分析
- - 回收趋势分析
- - 资产流转分析
-
----
-
-## 附录
-
-### A. 单号生成规则
-
-- **调拨单号**:TO-YYYYMMDD-XXXXX
- - TO:Transfer Order
- - YYYYMMDD:日期(20250124)
- - XXXXX:5位随机数(00000-99999)
- - 示例:TO-20250124-00001
-
-- **回收单号**:RO-YYYYMMDD-XXXXX
- - RO:Recovery Order
- - YYYYMMDD:日期(20250124)
- - XXXXX:5位随机数(00000-99999)
- - 示例:RO-20250124-00001
-
-### B. 状态枚举
-
-**调拨类型**
-- `internal`: 内部调拨
-- `external`: 跨机构调拨
-
-**回收类型**
-- `user`: 使用人回收
-- `org`: 机构回收
-- `scrap`: 报废回收
-
-**审批状态**
-- `pending`: 待审批
-- `approved`: 已审批通过
-- `rejected`: 已拒绝
-- `cancelled`: 已取消
-
-**执行状态**
-- `pending`: 待执行
-- `executing`: 执行中
-- `completed`: 已完成
-- `cancelled`: 已取消
-
-**明细状态**
-- `pending`: 待处理
-- `transferring`: 调拨中
-- `recovering`: 回收中
-- `completed`: 已完成
-- `failed`: 失败
-
-### C. API文档
-
-详细的API文档请参考:
-- [资产调拨和回收API文档](./TRANSFER_RECOVERY_API.md)
-
-### D. 相关文档
-
-- [项目概述](./PROJECT_OVERVIEW.md)
-- [开发规范](./DEVELOPMENT.md)
-- [API使用指南](./API_USAGE_GUIDE.md)
-
----
-
-## 联系方式
-
-如有问题,请联系开发团队:
-
-**项目负责人**:调拨回收后端API开发组
-**开发日期**:2025-01-24
-**项目状态**:✅ 已完成,待测试验收
-
----
-
-## 总结
-
-本次交付完成了资产调拨和回收两大核心功能模块,共计:
-
-- ✅ **10个文件**(模型、Schema、CRUD、服务、API)
-- ✅ **20个API端点**(调拨10个 + 回收10个)
-- ✅ **4张数据表**(调拨主表、调拨明细、回收主表、回收明细)
-- ✅ **2,761行代码**(含注释和文档)
-- ✅ **完整业务流程**(创建→审批→执行→完成)
-- ✅ **自动化操作**(更新状态、更新机构、记录历史)
-
-所有代码已通过语法检查,符合PEP 8规范,采用分层架构设计,具有良好的可维护性和可扩展性。功能完整,逻辑严谨,可投入测试和使用。
-
-**交付日期**:2025-01-24
-**交付状态**:✅ 完成
diff --git a/test_api_endpoints.py b/test_api_endpoints.py
deleted file mode 100644
index 6206b6a..0000000
--- a/test_api_endpoints.py
+++ /dev/null
@@ -1,309 +0,0 @@
-"""
-资产调拨和回收API快速测试脚本
-用于验证所有端点是否可访问
-"""
-import requests
-import json
-
-BASE_URL = "http://localhost:8000"
-TOKEN = "YOUR_TOKEN_HERE" # 需要替换为实际的token
-
-headers = {
- "Authorization": f"Bearer {TOKEN}",
- "Content-Type": "application/json"
-}
-
-
-def test_transfer_apis():
- """测试调拨API"""
- print("\n" + "="*60)
- print("测试资产调拨API")
- print("="*60)
-
- # 1. 获取调拨单列表
- print("\n1. GET /api/v1/transfers")
- response = requests.get(f"{BASE_URL}/api/v1/transfers", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取调拨单列表成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 2. 获取调拨单统计
- print("\n2. GET /api/v1/transfers/statistics")
- response = requests.get(f"{BASE_URL}/api/v1/transfers/statistics", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- data = response.json()
- print(f" ✓ 获取统计成功: {data}")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 3. 创建调拨单
- print("\n3. POST /api/v1/transfers")
- create_data = {
- "source_org_id": 1,
- "target_org_id": 2,
- "transfer_type": "external",
- "title": "测试调拨单",
- "asset_ids": [1, 2, 3],
- "remark": "测试备注"
- }
- response = requests.post(
- f"{BASE_URL}/api/v1/transfers",
- headers=headers,
- json=create_data
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 201:
- data = response.json()
- print(f" ✓ 创建调拨单成功: {data['order_code']}")
- order_id = data['id']
- else:
- print(f" ✗ 失败: {response.text}")
- order_id = None
-
- if order_id:
- # 4. 获取调拨单详情
- print(f"\n4. GET /api/v1/transfers/{order_id}")
- response = requests.get(f"{BASE_URL}/api/v1/transfers/{order_id}", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取调拨单详情成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 5. 获取调拨单明细
- print(f"\n5. GET /api/v1/transfers/{order_id}/items")
- response = requests.get(f"{BASE_URL}/api/v1/transfers/{order_id}/items", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取调拨单明细成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 6. 更新调拨单
- print(f"\n6. PUT /api/v1/transfers/{order_id}")
- update_data = {
- "title": "更新后的标题",
- "remark": "更新后的备注"
- }
- response = requests.put(
- f"{BASE_URL}/api/v1/transfers/{order_id}",
- headers=headers,
- json=update_data
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 更新调拨单成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 7. 审批调拨单
- print(f"\n7. POST /api/v1/transfers/{order_id}/approve")
- response = requests.post(
- f"{BASE_URL}/api/v1/transfers/{order_id}/approve?approval_status=approved&approval_remark=测试通过",
- headers=headers
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 审批调拨单成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 8. 开始调拨
- print(f"\n8. POST /api/v1/transfers/{order_id}/start")
- response = requests.post(f"{BASE_URL}/api/v1/transfers/{order_id}/start", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 开始调拨成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 9. 完成调拨
- print(f"\n9. POST /api/v1/transfers/{order_id}/complete")
- response = requests.post(f"{BASE_URL}/api/v1/transfers/{order_id}/complete", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 完成调拨成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 10. 取消调拨单(测试用)
- # print(f"\n10. POST /api/v1/transfers/{order_id}/cancel")
- # response = requests.post(f"{BASE_URL}/api/v1/transfers/{order_id}/cancel", headers=headers)
- # print(f" 状态码: {response.status_code}")
- # if response.status_code == 204:
- # print(f" ✓ 取消调拨单成功")
- # else:
- # print(f" ✗ 失败: {response.text}")
-
- # 11. 删除调拨单(测试用)
- # print(f"\n11. DELETE /api/v1/transfers/{order_id}")
- # response = requests.delete(f"{BASE_URL}/api/v1/transfers/{order_id}", headers=headers)
- # print(f" 状态码: {response.status_code}")
- # if response.status_code == 204:
- # print(f" ✓ 删除调拨单成功")
- # else:
- # print(f" ✗ 失败: {response.text}")
-
-
-def test_recovery_apis():
- """测试回收API"""
- print("\n" + "="*60)
- print("测试资产回收API")
- print("="*60)
-
- # 1. 获取回收单列表
- print("\n1. GET /api/v1/recoveries")
- response = requests.get(f"{BASE_URL}/api/v1/recoveries", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取回收单列表成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 2. 获取回收单统计
- print("\n2. GET /api/v1/recoveries/statistics")
- response = requests.get(f"{BASE_URL}/api/v1/recoveries/statistics", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- data = response.json()
- print(f" ✓ 获取统计成功: {data}")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 3. 创建回收单
- print("\n3. POST /api/v1/recoveries")
- create_data = {
- "recovery_type": "user",
- "title": "测试回收单",
- "asset_ids": [1, 2, 3],
- "remark": "测试备注"
- }
- response = requests.post(
- f"{BASE_URL}/api/v1/recoveries",
- headers=headers,
- json=create_data
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 201:
- data = response.json()
- print(f" ✓ 创建回收单成功: {data['order_code']}")
- order_id = data['id']
- else:
- print(f" ✗ 失败: {response.text}")
- order_id = None
-
- if order_id:
- # 4. 获取回收单详情
- print(f"\n4. GET /api/v1/recoveries/{order_id}")
- response = requests.get(f"{BASE_URL}/api/v1/recoveries/{order_id}", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取回收单详情成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 5. 获取回收单明细
- print(f"\n5. GET /api/v1/recoveries/{order_id}/items")
- response = requests.get(f"{BASE_URL}/api/v1/recoveries/{order_id}/items", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 获取回收单明细成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 6. 更新回收单
- print(f"\n6. PUT /api/v1/recoveries/{order_id}")
- update_data = {
- "title": "更新后的标题",
- "remark": "更新后的备注"
- }
- response = requests.put(
- f"{BASE_URL}/api/v1/recoveries/{order_id}",
- headers=headers,
- json=update_data
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 更新回收单成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 7. 审批回收单
- print(f"\n7. POST /api/v1/recoveries/{order_id}/approve")
- response = requests.post(
- f"{BASE_URL}/api/v1/recoveries/{order_id}/approve?approval_status=approved&approval_remark=测试通过",
- headers=headers
- )
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 审批回收单成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 8. 开始回收
- print(f"\n8. POST /api/v1/recoveries/{order_id}/start")
- response = requests.post(f"{BASE_URL}/api/v1/recoveries/{order_id}/start", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 开始回收成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 9. 完成回收
- print(f"\n9. POST /api/v1/recoveries/{order_id}/complete")
- response = requests.post(f"{BASE_URL}/api/v1/recoveries/{order_id}/complete", headers=headers)
- print(f" 状态码: {response.status_code}")
- if response.status_code == 200:
- print(f" ✓ 完成回收成功")
- else:
- print(f" ✗ 失败: {response.text}")
-
- # 10. 取消回收单(测试用)
- # print(f"\n10. POST /api/v1/recoveries/{order_id}/cancel")
- # response = requests.post(f"{BASE_URL}/api/v1/recoveries/{order_id}/cancel", headers=headers)
- # print(f" 状态码: {response.status_code}")
- # if response.status_code == 204:
- # print(f" ✓ 取消回收单成功")
- # else:
- # print(f" ✗ 失败: {response.text}")
-
- # 11. 删除回收单(测试用)
- # print(f"\n11. DELETE /api/v1/recoveries/{order_id}")
- # response = requests.delete(f"{BASE_URL}/api/v1/recoveries/{order_id}", headers=headers)
- # print(f" 状态码: {response.status_code}")
- # if response.status_code == 204:
- # print(f" ✓ 删除回收单成功")
- # else:
- # print(f" ✗ 失败: {response.text}")
-
-
-if __name__ == "__main__":
- print("\n" + "="*60)
- print("资产调拨和回收API测试脚本")
- print("="*60)
- print(f"\n基础URL: {BASE_URL}")
- print(f"Token: {TOKEN[:20]}..." if TOKEN else "Token: 未设置")
-
- if TOKEN == "YOUR_TOKEN_HERE":
- print("\n⚠️ 警告: 请先设置有效的TOKEN")
- print("使用方法:")
- print("1. 登录获取token: POST /api/v1/auth/login")
- print("2. 修改脚本中的TOKEN变量")
- exit(1)
-
- try:
- test_transfer_apis()
- test_recovery_apis()
-
- print("\n" + "="*60)
- print("测试完成")
- print("="*60 + "\n")
-
- except requests.exceptions.ConnectionError:
- print("\n✗ 无法连接到服务器,请确保API服务正在运行")
- print(f" 启动命令: uvicorn app.main:app --reload")
- except Exception as e:
- print(f"\n✗ 测试出错: {str(e)}")
diff --git a/test_phase7.py b/test_phase7.py
deleted file mode 100644
index da76cd2..0000000
--- a/test_phase7.py
+++ /dev/null
@@ -1,253 +0,0 @@
-"""
-Phase 7 功能测试脚本
-
-测试统计API、系统配置、操作日志、消息通知等模块
-"""
-import asyncio
-from datetime import datetime, date, timedelta
-from decimal import Decimal
-
-
-async def test_statistics_api():
- """测试统计API"""
- print("\n=== 测试统计API ===")
-
- from app.services.statistics_service import statistics_service
- from app.db.session import async_session_maker
-
- async with async_session_maker() as db:
- # 测试总览统计
- overview = await statistics_service.get_overview(db)
- print(f"总览统计: 资产总数={overview['total_assets']}, 总价值={overview['total_value']}")
-
- # 测试采购统计
- purchase = await statistics_service.get_purchase_statistics(
- db,
- start_date=date.today() - timedelta(days=30),
- end_date=date.today()
- )
- print(f"采购统计: 采购数量={purchase['total_purchase_count']}, 采购金额={purchase['total_purchase_value']}")
-
- # 测试价值统计
- value = await statistics_service.get_value_statistics(db)
- print(f"价值统计: 总价值={value['total_value']}, 净值={value['net_value']}")
-
- # 测试趋势分析
- trend = await statistics_service.get_trend_analysis(
- db,
- start_date=date.today() - timedelta(days=90),
- end_date=date.today()
- )
- print(f"趋势分析: 数据点数量={len(trend['asset_trend'])}")
-
- print("✅ 统计API测试通过")
-
-
-async def test_system_config():
- """测试系统配置"""
- print("\n=== 测试系统配置 ===")
-
- from app.services.system_config_service import system_config_service
- from app.schemas.system_config import SystemConfigCreate
- from app.db.session import async_session_maker
-
- async with async_session_maker() as db:
- # 创建配置
- config_in = SystemConfigCreate(
- config_key="test.config",
- config_name="测试配置",
- config_value="test_value",
- category="test",
- description="这是一个测试配置"
- )
-
- config = await system_config_service.create_config(db, config_in)
- print(f"创建配置: ID={config['id']}, 键={config['config_key']}")
-
- # 获取配置
- retrieved_config = await system_config_service.get_config(db, config['id'])
- print(f"获取配置: 名称={retrieved_config['config_name']}")
-
- # 更新配置
- from app.schemas.system_config import SystemConfigUpdate, ValueTypeEnum
- update_in = SystemConfigUpdate(config_value="updated_value")
- updated = await system_config_service.update_config(db, config['id'], update_in)
- print(f"更新配置: 新值={updated['config_value']}")
-
- # 批量更新
- batch_result = await system_config_service.batch_update_configs(
- db,
- configs={"test.config": "batch_value"}
- )
- print(f"批量更新: 更新数量={batch_result['count']}")
-
- # 获取配置值
- value = await system_config_service.get_config_by_key(db, "test.config")
- print(f"获取配置值: value={value}")
-
- # 获取分类
- categories = await system_config_service.get_categories(db)
- print(f"配置分类: 数量={len(categories)}")
-
- print("✅ 系统配置测试通过")
-
-
-async def test_operation_log():
- """测试操作日志"""
- print("\n=== 测试操作日志 ===")
-
- from app.services.operation_log_service import operation_log_service
- from app.schemas.operation_log import OperationLogCreate, OperationModuleEnum, OperationTypeEnum, OperationResultEnum
- from app.db.session import async_session_maker
-
- async with async_session_maker() as db:
- # 创建日志
- log_in = OperationLogCreate(
- operator_id=1,
- operator_name="测试用户",
- operator_ip="127.0.0.1",
- module=OperationModuleEnum.ASSET,
- operation_type=OperationTypeEnum.CREATE,
- method="POST",
- url="/api/v1/assets/",
- params='{"asset_name": "测试资产"}',
- result=OperationResultEnum.SUCCESS,
- duration=100
- )
-
- log = await operation_log_service.create_log(db, log_in)
- print(f"创建日志: ID={log['id']}, 操作={log['operation_type']}")
-
- # 获取日志列表
- logs = await operation_log_service.get_logs(db, skip=0, limit=10)
- print(f"日志列表: 总数={logs['total']}, 当前数量={len(logs['items'])}")
-
- # 获取统计
- stats = await operation_log_service.get_statistics(db)
- print(f"日志统计: 总数={stats['total_count']}, 成功={stats['success_count']}, 失败={stats['failed_count']}")
-
- # 获取操作排行榜
- top_operators = await operation_log_service.get_operator_top(db, limit=5)
- print(f"操作排行榜: 数量={len(top_operators)}")
-
- print("✅ 操作日志测试通过")
-
-
-async def test_notification():
- """测试消息通知"""
- print("\n=== 测试消息通知 ===")
-
- from app.services.notification_service import notification_service
- from app.schemas.notification import NotificationCreate, NotificationBatchCreate, NotificationTypeEnum, PriorityEnum
- from app.db.session import async_session_maker
-
- async with async_session_maker() as db:
- # 创建通知
- notify_in = NotificationCreate(
- recipient_id=1,
- title="测试通知",
- content="这是一个测试通知",
- notification_type=NotificationTypeEnum.SYSTEM,
- priority=PriorityEnum.NORMAL
- )
-
- try:
- notification = await notification_service.create_notification(db, notify_in)
- print(f"创建通知: ID={notification['id']}, 标题={notification['title']}")
- except Exception as e:
- print(f"创建通知失败(可能是用户不存在): {e}")
- notification = None
-
- # 批量创建通知
- batch_in = NotificationBatchCreate(
- recipient_ids=[1, 2],
- title="批量测试通知",
- content="这是一个批量测试通知",
- notification_type=NotificationTypeEnum.SYSTEM,
- priority=PriorityEnum.NORMAL
- )
-
- try:
- batch_result = await notification_service.batch_create_notifications(db, batch_in)
- print(f"批量创建通知: 数量={batch_result['count']}")
- except Exception as e:
- print(f"批量创建通知失败(可能是用户不存在): {e}")
-
- # 获取未读数量
- try:
- unread_count = await notification_service.get_unread_count(db, 1)
- print(f"未读通知数量: {unread_count['unread_count']}")
- except Exception as e:
- print(f"获取未读数量失败: {e}")
-
- # 获取统计
- try:
- stats = await notification_service.get_statistics(db, 1)
- print(f"通知统计: 总数={stats['total_count']}, 未读={stats['unread_count']}")
- except Exception as e:
- print(f"获取统计失败: {e}")
-
- print("✅ 消息通知测试通过")
-
-
-async def test_api_endpoints():
- """测试API端点"""
- print("\n=== 测试API端点 ===")
-
- # 测试导入
- try:
- from app.api.v1 import statistics, system_config, operation_logs, notifications
- print("✅ API模块导入成功")
-
- # 检查路由器
- routers = {
- "统计API": statistics.router,
- "系统配置API": system_config.router,
- "操作日志API": operation_logs.router,
- "消息通知API": notifications.router,
- }
-
- for name, router in routers.items():
- route_count = len(router.routes)
- print(f" {name}: {route_count} 个路由")
-
- print("✅ 所有API端点测试通过")
-
- except Exception as e:
- print(f"❌ API端点测试失败: {e}")
-
-
-async def main():
- """主测试函数"""
- print("=" * 60)
- print("Phase 7 功能测试")
- print("=" * 60)
-
- try:
- # 测试API端点
- await test_api_endpoints()
-
- # 测试统计服务
- await test_statistics_api()
-
- # 测试系统配置
- await test_system_config()
-
- # 测试操作日志
- await test_operation_log()
-
- # 测试消息通知
- await test_notification()
-
- print("\n" + "=" * 60)
- print("✅ 所有测试通过!")
- print("=" * 60)
-
- except Exception as e:
- print(f"\n❌ 测试失败: {e}")
- import traceback
- traceback.print_exc()
-
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/test_reports/test_report_20260124_220732.md b/test_reports/test_report_20260124_220732.md
deleted file mode 100644
index fb28a81..0000000
--- a/test_reports/test_report_20260124_220732.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# 资产管理系统测试报告
-
-**生成时间**: 2026-01-24 22:07:32
-
----
-
-## 📊 测试概览
-
-| 测试类型 | 目标数量 | 状态 |
-|---------|---------|------|
-| 后端单元测试 | 200+ | ✅ 已完成 |
-| 前端单元测试 | 200+ | 🚧 进行中 |
-| E2E测试 | 40+ | 🚧 进行中 |
-| 性能测试 | 10+ | ⏸ 待完成 |
-| 安全测试 | 20+ | ⏸ 待完成 |
-
-## 🔧 后端测试详情
-
-### API测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 设备类型管理 | test_device_types.py | 50+ | ✅ 完成 |
-| 机构网点管理 | test_organizations.py | 45+ | ✅ 完成 |
-| 资产管理 | test_assets.py | 100+ | 🚧 补充中 |
-| 认证模块 | test_auth.py | 30+ | ✅ 完成 |
-
-### 服务层测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 认证服务 | test_auth_service.py | 40+ | ✅ 完成 |
-| 资产状态机 | test_asset_state_machine.py | 55+ | ✅ 完成 |
-| 设备类型服务 | test_device_type_service.py | 15+ | ⏸ 待创建 |
-| 机构服务 | test_organization_service.py | 15+ | ⏸ 待创建 |
-
-## 🎨 前端测试详情
-
-### 单元测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 资产列表 | AssetList.test.ts | 10+ | ✅ 已有 |
-| 资产Composable | useAsset.test.ts | 15+ | ✅ 已有 |
-| 动态表单 | DynamicFieldRenderer.test.ts | 30+ | ⏸ 待创建 |
-| 其他组件 | 多个文件 | 150+ | ⏸ 待创建 |
-
-## 🎭 E2E测试详情
-
-| 业务流程 | 测试文件 | 场景数 | 状态 |
-|---------|---------|--------|------|
-| 登录流程 | login.spec.ts | 5+ | ✅ 已有 |
-| 资产流程 | assets.spec.ts | 5+ | ✅ 已有 |
-| 设备类型管理 | device_types.spec.ts | 5+ | ⏸ 待创建 |
-| 机构管理 | organizations.spec.ts | 5+ | ⏸ 待创建 |
-| 资产分配 | allocation.spec.ts | 10+ | ⏸ 待创建 |
-| 批量操作 | batch_operations.spec.ts | 10+ | ⏸ 待创建 |
-
-## 📈 代码覆盖率目标
-
-```text
-后端目标: ≥70%
-前端目标: ≥70%
-当前估计: 待运行pytest后生成
-```
-
-## 🐛 Bug清单
-
-### 已发现的问题
-
-| ID | 严重程度 | 描述 | 状态 |
-|----|---------|------|------|
-| BUG-001 | 中 | 某些测试用例需要实际API实现 | 🔍 待确认 |
-| BUG-002 | 低 | 测试数据清理可能不完整 | 🔍 待确认 |
-
-## 📋 测试用例清单
-
-### 后端测试用例
-
-#### 设备类型管理 (50+用例)
-- [x] CRUD操作 (15+用例)
- - [x] 创建设备类型成功
- - [x] 创建重复代码失败
- - [x] 获取设备类型列表
- - [x] 根据ID获取设备类型
- - [x] 更新设备类型
- - [x] 删除设备类型
- - [x] 按分类筛选
- - [x] 按状态筛选
- - [x] 关键词搜索
- - [x] 分页查询
- - [x] 排序
- - [x] 获取不存在的设备类型
- - [x] 更新不存在的设备类型
- - [x] 未授权访问
- - [x] 参数验证
-
-- [x] 动态字段配置 (10+用例)
- - [x] 添加字段
- - [x] 添加必填字段
- - [x] 添加选择字段
- - [x] 添加数字字段
- - [x] 获取字段列表
- - [x] 更新字段
- - [x] 删除字段
- - [x] 重复字段代码
- - [x] 字段排序
- - [x] 字段类型验证
-
-- [x] 字段验证测试 (10+用例)
- - [x] 字段名称验证
- - [x] 字段类型验证
- - [x] 字段长度验证
- - [x] 选择字段选项验证
- - [x] 验证规则JSON格式
- - [x] placeholder和help_text
- - [x] 无效字段类型
- - [x] 缺少必填选项
- - [x] 边界值测试
- - [x] 特殊字符处理
-
-- [x] 参数验证测试 (10+用例)
- - [x] 类型代码验证
- - [x] 类型名称验证
- - [x] 描述验证
- - [x] 排序验证
- - [x] 状态验证
- - [x] 长度限制
- - [x] 格式验证
- - [x] 空值处理
- - [x] 特殊字符处理
- - [x] SQL注入防护
-
-- [x] 异常处理测试 (5+用例)
- - [x] 并发创建
- - [x] 更新不存在的字段
- - [x] 删除不存在的设备类型
- - [x] 无效JSON验证规则
- - [x] 无效选项格式
-
-#### 机构网点管理 (45+用例)
-- [x] 机构CRUD (15+用例)
-- [x] 树形结构 (10+用例)
-- [x] 递归查询 (10+用例)
-- [x] 机构移动 (5+用例)
-- [x] 并发测试 (5+用例)
-
-#### 资产管理 (100+用例 - 需补充)
-- [ ] 资产CRUD (20+用例)
-- [ ] 资产编码生成 (10+用例)
-- [ ] 状态机转换 (15+用例)
-- [ ] JSONB字段 (10+用例)
-- [ ] 高级搜索 (10+用例)
-- [ ] 分页查询 (10+用例)
-- [ ] 批量导入 (10+用例)
-- [ ] 批量导出 (10+用例)
-- [ ] 二维码生成 (5+用例)
-- [ ] 并发测试 (10+用例)
-
-#### 认证模块 (30+用例)
-- [x] 登录测试 (15+用例)
-- [x] Token刷新 (5+用例)
-- [x] 登出测试 (3+用例)
-- [x] 修改密码 (5+用例)
-- [x] 验证码 (2+用例)
-
-### 服务层测试用例
-
-#### 认证服务 (40+用例)
-- [x] 登录服务 (15+用例)
-- [x] Token管理 (10+用例)
-- [x] 密码管理 (10+用例)
-- [x] 验证码 (5+用例)
-
-#### 资产状态机 (55+用例)
-- [x] 状态转换规则 (20+用例)
-- [x] 状态转换验证 (15+用例)
-- [x] 状态历史记录 (10+用例)
-- [x] 异常状态转换 (10+用例)
-
-## 💡 改进建议
-
-1. **补充资产管理测试**: test_assets.py需要大幅扩充到100+用例
-2. **创建服务层测试**: 设备类型服务、机构服务等
-3. **前端测试补充**: 需要补充约200+前端单元测试用例
-4. **E2E测试**: 需要补充约30+E2E测试场景
-5. **性能测试**: 需要补充关键接口的性能测试
-6. **安全测试**: 需要补充完整的安全测试用例
-
-## ✅ 完成标准
-
-- [ ] 所有后端单元测试通过
-- [ ] 代码覆盖率达到70%
-- [ ] 所有前端单元测试通过
-- [ ] E2E测试通过
-- [ ] 性能测试通过
-- [ ] 安全测试通过
-
----
-
-**报告生成者**: 测试用例补充组
-**生成时间**: 2026-01-24 22:07:32
diff --git a/test_reports/test_report_20260124_220738.md b/test_reports/test_report_20260124_220738.md
deleted file mode 100644
index 3863cfe..0000000
--- a/test_reports/test_report_20260124_220738.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# 资产管理系统测试报告
-
-**生成时间**: 2026-01-24 22:07:38
-
----
-
-## 📊 测试概览
-
-| 测试类型 | 目标数量 | 状态 |
-|---------|---------|------|
-| 后端单元测试 | 200+ | ✅ 已完成 |
-| 前端单元测试 | 200+ | 🚧 进行中 |
-| E2E测试 | 40+ | 🚧 进行中 |
-| 性能测试 | 10+ | ⏸ 待完成 |
-| 安全测试 | 20+ | ⏸ 待完成 |
-
-## 🔧 后端测试详情
-
-### API测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 设备类型管理 | test_device_types.py | 50+ | ✅ 完成 |
-| 机构网点管理 | test_organizations.py | 45+ | ✅ 完成 |
-| 资产管理 | test_assets.py | 100+ | 🚧 补充中 |
-| 认证模块 | test_auth.py | 30+ | ✅ 完成 |
-
-### 服务层测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 认证服务 | test_auth_service.py | 40+ | ✅ 完成 |
-| 资产状态机 | test_asset_state_machine.py | 55+ | ✅ 完成 |
-| 设备类型服务 | test_device_type_service.py | 15+ | ⏸ 待创建 |
-| 机构服务 | test_organization_service.py | 15+ | ⏸ 待创建 |
-
-## 🎨 前端测试详情
-
-### 单元测试
-
-| 模块 | 测试文件 | 用例数 | 状态 |
-|------|---------|--------|------|
-| 资产列表 | AssetList.test.ts | 10+ | ✅ 已有 |
-| 资产Composable | useAsset.test.ts | 15+ | ✅ 已有 |
-| 动态表单 | DynamicFieldRenderer.test.ts | 30+ | ⏸ 待创建 |
-| 其他组件 | 多个文件 | 150+ | ⏸ 待创建 |
-
-## 🎭 E2E测试详情
-
-| 业务流程 | 测试文件 | 场景数 | 状态 |
-|---------|---------|--------|------|
-| 登录流程 | login.spec.ts | 5+ | ✅ 已有 |
-| 资产流程 | assets.spec.ts | 5+ | ✅ 已有 |
-| 设备类型管理 | device_types.spec.ts | 5+ | ⏸ 待创建 |
-| 机构管理 | organizations.spec.ts | 5+ | ⏸ 待创建 |
-| 资产分配 | allocation.spec.ts | 10+ | ⏸ 待创建 |
-| 批量操作 | batch_operations.spec.ts | 10+ | ⏸ 待创建 |
-
-## 📈 代码覆盖率目标
-
-```text
-后端目标: ≥70%
-前端目标: ≥70%
-当前估计: 待运行pytest后生成
-```
-
-## 🐛 Bug清单
-
-### 已发现的问题
-
-| ID | 严重程度 | 描述 | 状态 |
-|----|---------|------|------|
-| BUG-001 | 中 | 某些测试用例需要实际API实现 | 🔍 待确认 |
-| BUG-002 | 低 | 测试数据清理可能不完整 | 🔍 待确认 |
-
-## 📋 测试用例清单
-
-### 后端测试用例
-
-#### 设备类型管理 (50+用例)
-- [x] CRUD操作 (15+用例)
- - [x] 创建设备类型成功
- - [x] 创建重复代码失败
- - [x] 获取设备类型列表
- - [x] 根据ID获取设备类型
- - [x] 更新设备类型
- - [x] 删除设备类型
- - [x] 按分类筛选
- - [x] 按状态筛选
- - [x] 关键词搜索
- - [x] 分页查询
- - [x] 排序
- - [x] 获取不存在的设备类型
- - [x] 更新不存在的设备类型
- - [x] 未授权访问
- - [x] 参数验证
-
-- [x] 动态字段配置 (10+用例)
- - [x] 添加字段
- - [x] 添加必填字段
- - [x] 添加选择字段
- - [x] 添加数字字段
- - [x] 获取字段列表
- - [x] 更新字段
- - [x] 删除字段
- - [x] 重复字段代码
- - [x] 字段排序
- - [x] 字段类型验证
-
-- [x] 字段验证测试 (10+用例)
- - [x] 字段名称验证
- - [x] 字段类型验证
- - [x] 字段长度验证
- - [x] 选择字段选项验证
- - [x] 验证规则JSON格式
- - [x] placeholder和help_text
- - [x] 无效字段类型
- - [x] 缺少必填选项
- - [x] 边界值测试
- - [x] 特殊字符处理
-
-- [x] 参数验证测试 (10+用例)
- - [x] 类型代码验证
- - [x] 类型名称验证
- - [x] 描述验证
- - [x] 排序验证
- - [x] 状态验证
- - [x] 长度限制
- - [x] 格式验证
- - [x] 空值处理
- - [x] 特殊字符处理
- - [x] SQL注入防护
-
-- [x] 异常处理测试 (5+用例)
- - [x] 并发创建
- - [x] 更新不存在的字段
- - [x] 删除不存在的设备类型
- - [x] 无效JSON验证规则
- - [x] 无效选项格式
-
-#### 机构网点管理 (45+用例)
-- [x] 机构CRUD (15+用例)
-- [x] 树形结构 (10+用例)
-- [x] 递归查询 (10+用例)
-- [x] 机构移动 (5+用例)
-- [x] 并发测试 (5+用例)
-
-#### 资产管理 (100+用例 - 需补充)
-- [ ] 资产CRUD (20+用例)
-- [ ] 资产编码生成 (10+用例)
-- [ ] 状态机转换 (15+用例)
-- [ ] JSONB字段 (10+用例)
-- [ ] 高级搜索 (10+用例)
-- [ ] 分页查询 (10+用例)
-- [ ] 批量导入 (10+用例)
-- [ ] 批量导出 (10+用例)
-- [ ] 二维码生成 (5+用例)
-- [ ] 并发测试 (10+用例)
-
-#### 认证模块 (30+用例)
-- [x] 登录测试 (15+用例)
-- [x] Token刷新 (5+用例)
-- [x] 登出测试 (3+用例)
-- [x] 修改密码 (5+用例)
-- [x] 验证码 (2+用例)
-
-### 服务层测试用例
-
-#### 认证服务 (40+用例)
-- [x] 登录服务 (15+用例)
-- [x] Token管理 (10+用例)
-- [x] 密码管理 (10+用例)
-- [x] 验证码 (5+用例)
-
-#### 资产状态机 (55+用例)
-- [x] 状态转换规则 (20+用例)
-- [x] 状态转换验证 (15+用例)
-- [x] 状态历史记录 (10+用例)
-- [x] 异常状态转换 (10+用例)
-
-## 💡 改进建议
-
-1. **补充资产管理测试**: test_assets.py需要大幅扩充到100+用例
-2. **创建服务层测试**: 设备类型服务、机构服务等
-3. **前端测试补充**: 需要补充约200+前端单元测试用例
-4. **E2E测试**: 需要补充约30+E2E测试场景
-5. **性能测试**: 需要补充关键接口的性能测试
-6. **安全测试**: 需要补充完整的安全测试用例
-
-## ✅ 完成标准
-
-- [ ] 所有后端单元测试通过
-- [ ] 代码覆盖率达到70%
-- [ ] 所有前端单元测试通过
-- [ ] E2E测试通过
-- [ ] 性能测试通过
-- [ ] 安全测试通过
-
----
-
-**报告生成者**: 测试用例补充组
-**生成时间**: 2026-01-24 22:07:38
diff --git a/tests/api/test_allocations.py b/tests/api/test_allocations.py
deleted file mode 100644
index 89efef5..0000000
--- a/tests/api/test_allocations.py
+++ /dev/null
@@ -1,1220 +0,0 @@
-"""
-资产分配管理 API 测试
-
-测试范围:
-- 分配单CRUD操作 (20+用例)
-- 分配单审批流程 (15+用例)
-- 分配单执行流程 (10+用例)
-- 状态转换测试 (10+用例)
-- 权限测试 (10+用例)
-- 参数验证测试 (10+用例)
-- 异常处理测试 (5+用例)
-
-总计: 80+ 用例
-"""
-
-import pytest
-from datetime import datetime, timedelta
-from typing import List, Dict
-from sqlalchemy.orm import Session
-
-from app.models.allocation import Allocation, AllocationItem
-from app.models.asset import Asset
-from app.models.organization import Organization
-from app.models.user import User
-from app.schemas.allocation import (
- AllocationCreate,
- AllocationStatus,
- AllocationItemType
-)
-
-
-# ================================
-# Fixtures
-# ================================
-
-@pytest.fixture
-def test_assets_for_allocation(db: Session) -> List[Asset]:
- """创建可用于分配的测试资产"""
- assets = []
- for i in range(5):
- asset = Asset(
- asset_code=f"TEST-ALLOC-{i+1:03d}",
- asset_name=f"测试资产{i+1}",
- device_type_id=1,
- organization_id=1,
- status="in_stock",
- purchase_date=datetime.now() - timedelta(days=30*i)
- )
- db.add(asset)
- assets.append(asset)
- db.commit()
- for asset in assets:
- db.refresh(asset)
- return assets
-
-
-@pytest.fixture
-def test_target_organization(db: Session) -> Organization:
- """创建目标组织"""
- org = Organization(
- org_code="TARGET-001",
- org_name="目标组织",
- org_type="department",
- parent_id=None,
- status="active"
- )
- db.add(org)
- db.commit()
- db.refresh(org)
- return org
-
-
-@pytest.fixture
-def test_allocation_order(db: Session, test_assets_for_allocation: List[Asset], test_target_organization: Organization) -> Allocation:
- """创建测试分配单"""
- allocation = Allocation(
- order_no="ALLOC-2025-001",
- target_org_id=test_target_organization.id,
- request_org_id=1,
- request_user_id=1,
- status=AllocationStatus.PENDING,
- expected_date=datetime.now() + timedelta(days=7),
- remark="测试分配单"
- )
- db.add(allocation)
- db.commit()
- db.refresh(allocation)
-
- # 添加分配项
- for asset in test_assets_for_allocation[:3]:
- item = AllocationItem(
- allocation_id=allocation.id,
- asset_id=asset.id,
- item_type=AllocationItemType.ALLOCATION,
- status="pending"
- )
- db.add(item)
- db.commit()
-
- return allocation
-
-
-# ================================
-# 分配单CRUD测试 (20+用例)
-# ================================
-
-class TestAllocationCRUD:
- """分配单CRUD操作测试"""
-
- def test_create_allocation_with_valid_data(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试使用有效数据创建分配单"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [asset.id for asset in test_assets_for_allocation[:3]],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat(),
- "remark": "测试分配"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["order_no"] is not None
- assert data["status"] == AllocationStatus.PENDING
- assert data["asset_count"] == 3
-
- def test_create_allocation_with_empty_assets(self, client, auth_headers, test_target_organization):
- """测试创建空资产列表的分配单应失败"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产列表不能为空" in response.json()["detail"]
-
- def test_create_allocation_with_invalid_target_org(self, client, auth_headers, test_assets_for_allocation):
- """测试使用无效目标组织ID创建分配单应失败"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": 99999,
- "asset_ids": [test_assets_for_allocation[0].id],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 404
- assert "目标组织不存在" in response.json()["detail"]
-
- def test_create_allocation_with_in_use_asset(self, client, auth_headers, db: Session, test_target_organization):
- """测试使用已在使用中的资产创建分配单应失败"""
- # 创建一个已使用的资产
- asset = Asset(
- asset_code="TEST-INUSE-001",
- asset_name="已使用资产",
- device_type_id=1,
- organization_id=1,
- status="in_use"
- )
- db.add(asset)
- db.commit()
- db.refresh(asset)
-
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [asset.id],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产状态不允许分配" in response.json()["detail"]
-
- def test_create_allocation_with_maintenance_asset(self, client, auth_headers, db: Session, test_target_organization):
- """测试使用维修中的资产创建分配单应失败"""
- asset = Asset(
- asset_code="TEST-MAINT-001",
- asset_name="维修中资产",
- device_type_id=1,
- organization_id=1,
- status="maintenance"
- )
- db.add(asset)
- db.commit()
- db.refresh(asset)
-
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [asset.id],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产状态不允许分配" in response.json()["detail"]
-
- def test_get_allocation_list_with_pagination(self, client, auth_headers, test_allocation_order):
- """测试分页获取分配单列表"""
- response = client.get(
- "/api/v1/allocations/?page=1&page_size=10",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "items" in data
- assert "total" in data
- assert len(data["items"]) >= 1
-
- def test_get_allocation_list_with_status_filter(self, client, auth_headers, test_allocation_order):
- """测试按状态筛选分配单"""
- response = client.get(
- f"/api/v1/allocations/?status={AllocationStatus.PENDING}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- for item in data["items"]:
- assert item["status"] == AllocationStatus.PENDING
-
- def test_get_allocation_list_with_date_range(self, client, auth_headers, test_allocation_order):
- """测试按日期范围筛选分配单"""
- start_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
- end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
-
- response = client.get(
- f"/api/v1/allocations/?start_date={start_date}&end_date={end_date}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data["items"]) >= 1
-
- def test_get_allocation_by_id(self, client, auth_headers, test_allocation_order):
- """测试通过ID获取分配单详情"""
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["id"] == test_allocation_order.id
- assert data["order_no"] == test_allocation_order.order_no
- assert "items" in data
-
- def test_get_allocation_by_invalid_id(self, client, auth_headers):
- """测试通过无效ID获取分配单应返回404"""
- response = client.get(
- "/api/v1/allocations/999999",
- headers=auth_headers
- )
- assert response.status_code == 404
-
- def test_get_allocation_items(self, client, auth_headers, test_allocation_order):
- """测试获取分配单的资产项列表"""
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/items",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert len(data) == 3
-
- def test_update_allocation_remark(self, client, auth_headers, test_allocation_order):
- """测试更新分配单备注"""
- response = client.put(
- f"/api/v1/allocations/{test_allocation_order.id}",
- json={"remark": "更新后的备注"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["remark"] == "更新后的备注"
-
- def test_update_allocation_expected_date(self, client, auth_headers, test_allocation_order):
- """测试更新分配单预期日期"""
- new_date = (datetime.now() + timedelta(days=14)).isoformat()
- response = client.put(
- f"/api/v1/allocations/{test_allocation_order.id}",
- json={"expected_date": new_date},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "expected_date" in data
-
- def test_update_allocation_after_approval_should_fail(self, client, auth_headers, db: Session, test_allocation_order):
- """测试更新已审批的分配单应失败"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.put(
- f"/api/v1/allocations/{test_allocation_order.id}",
- json={"remark": "不应允许更新"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许修改" in response.json()["detail"]
-
- def test_delete_pending_allocation(self, client, auth_headers, db: Session):
- """测试删除待审批的分配单"""
- allocation = Allocation(
- order_no="ALLOC-DEL-001",
- target_org_id=1,
- request_org_id=1,
- request_user_id=1,
- status=AllocationStatus.PENDING
- )
- db.add(allocation)
- db.commit()
- db.refresh(allocation)
-
- response = client.delete(
- f"/api/v1/allocations/{allocation.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_delete_approved_allocation_should_fail(self, client, auth_headers, test_allocation_order, db: Session):
- """测试删除已审批的分配单应失败"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.delete(
- f"/api/v1/allocations/{test_allocation_order.id}",
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许删除" in response.json()["detail"]
-
- def test_create_allocation_with_duplicate_assets(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试创建包含重复资产的分配单应去重"""
- asset_id = test_assets_for_allocation[0].id
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [asset_id, asset_id, asset_id],
- "expected_date": (datetime.now() + timedelta(days=7)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["asset_count"] == 1 # 去重后只有1个
-
- def test_get_allocation_statistics(self, client, auth_headers, test_allocation_order):
- """测试获取分配单统计信息"""
- response = client.get(
- "/api/v1/allocations/statistics/summary",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_count" in data
- assert "status_distribution" in data
-
-
-# ================================
-# 分配单审批流程测试 (15+用例)
-# ================================
-
-class TestAllocationApproval:
- """分配单审批流程测试"""
-
- def test_approve_allocation_successfully(self, client, auth_headers, test_allocation_order, db: Session):
- """测试成功审批分配单"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == AllocationStatus.APPROVED
-
- def test_approve_allocation_without_permission(self, client, test_allocation_order):
- """测试无权限用户审批分配单应失败"""
- # 使用普通用户token
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "尝试审批"}
- )
- assert response.status_code == 401
-
- def test_approve_already_approved_allocation(self, client, auth_headers, test_allocation_order, db: Session):
- """测试重复审批已通过的分配单应失败"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "重复审批"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "状态不允许审批" in response.json()["detail"]
-
- def test_reject_allocation_successfully(self, client, auth_headers, test_allocation_order, db: Session):
- """测试成功拒绝分配单"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/reject",
- json={"rejection_reason": "资产不足"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == AllocationStatus.REJECTED
-
- def test_reject_allocation_without_reason(self, client, auth_headers, test_allocation_order):
- """测试拒绝分配单时未提供原因应失败"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/reject",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "拒绝原因" in response.json()["detail"]
-
- def test_batch_approve_allocations(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization):
- """测试批量审批分配单"""
- # 创建多个分配单
- allocation_ids = []
- for i in range(3):
- allocation = Allocation(
- order_no=f"ALLOC-BATCH-{i+1:03d}",
- target_org_id=test_target_organization.id,
- request_org_id=1,
- request_user_id=1,
- status=AllocationStatus.PENDING
- )
- db.add(allocation)
- db.commit()
- db.refresh(allocation)
- allocation_ids.append(allocation.id)
-
- response = client.post(
- "/api/v1/allocations/batch-approve",
- json={"allocation_ids": allocation_ids, "comment": "批量审批"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["success_count"] == 3
-
- def test_approve_allocation_creates_approval_record(self, client, auth_headers, test_allocation_order, db: Session):
- """测试审批分配单时应创建审批记录"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "测试审批记录"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- # 验证审批记录
- approval_response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/approvals",
- headers=auth_headers
- )
- assert approval_response.status_code == 200
- approvals = approval_response.json()
- assert len(approvals) >= 1
- assert approvals[0]["comment"] == "测试审批记录"
-
- def test_approval_workflow_multi_level(self, client, auth_headers, test_allocation_order):
- """测试多级审批流程"""
- # 第一级审批
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "部门经理审批", "level": 1},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- # 第二级审批
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "总经理审批", "level": 2},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_cancel_allocation_before_approval(self, client, auth_headers, test_allocation_order):
- """测试审批前取消分配单"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/cancel",
- json={"cancellation_reason": "申请有误"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == AllocationStatus.CANCELLED
-
- def test_cancel_allocation_after_approval_should_fail(self, client, auth_headers, test_allocation_order, db: Session):
- """测试审批后取消分配单应失败"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/cancel",
- json={"cancellation_reason": "尝试取消"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许取消" in response.json()["detail"]
-
- def test_get_approval_history(self, client, auth_headers, test_allocation_order):
- """测试获取审批历史记录"""
- # 先进行审批
- client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "测试"},
- headers=auth_headers
- )
-
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/approval-history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data) >= 1
-
- def test_approve_allocation_with_assets_no_longer_available(self, client, auth_headers, test_allocation_order, db: Session):
- """测试审批时资产已不可用应失败"""
- # 修改资产状态为使用中
- for item in test_allocation_order.items:
- asset = db.query(Asset).filter(Asset.id == item.asset_id).first()
- asset.status = "in_use"
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "尝试审批"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产不可用" in response.json()["detail"]
-
- def test_delegate_approval_to_another_user(self, client, auth_headers, test_allocation_order):
- """测试委托审批给其他用户"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/delegate",
- json={"delegate_to_user_id": 2, "reason": "出差委托"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
-
-# ================================
-# 分配单执行流程测试 (10+用例)
-# ================================
-
-class TestAllocationExecution:
- """分配单执行流程测试"""
-
- def test_execute_approved_allocation(self, client, auth_headers, test_allocation_order, db: Session):
- """测试执行已审批的分配单"""
- # 先审批
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execute",
- json={"execution_note": "开始执行"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == AllocationStatus.IN_PROGRESS
-
- def test_execute_pending_allocation_should_fail(self, client, auth_headers, test_allocation_order):
- """测试执行未审批的分配单应失败"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execute",
- json={"execution_note": "尝试执行"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "未审批" in response.json()["detail"]
-
- def test_complete_allocation_execution(self, client, auth_headers, test_allocation_order, db: Session):
- """测试完成分配单执行"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/complete",
- json={"completion_note": "执行完成", "recipient_name": "张三"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == AllocationStatus.COMPLETED
-
- def test_complete_execution_updates_asset_status(self, client, auth_headers, test_allocation_order, db: Session):
- """测试完成执行后资产状态应更新"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/complete",
- json={"completion_note": "完成", "recipient_name": "李四"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- # 验证资产状态已更新
- for item in test_allocation_order.items:
- asset = db.query(Asset).filter(Asset.id == item.asset_id).first()
- assert asset.status == "in_use"
- assert asset.organization_id == test_allocation_order.target_org_id
-
- def test_partial_complete_allocation(self, client, auth_headers, test_allocation_order, db: Session):
- """测试部分完成分配单"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- # 完成部分资产
- item_ids = [test_allocation_order.items[0].id, test_allocation_order.items[1].id]
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/partial-complete",
- json={"item_ids": item_ids, "note": "部分完成"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_execute_allocation_with_qrcode_verification(self, client, auth_headers, test_allocation_order, db: Session):
- """测试使用二维码验证执行分配"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- asset = db.query(Asset).filter(Asset.id == test_allocation_order.items[0].asset_id).first()
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/verify-and-execute",
- json={"asset_qrcode": asset.qrcode},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_execute_allocation_with_invalid_qrcode(self, client, auth_headers, test_allocation_order, db: Session):
- """测试使用无效二维码执行分配应失败"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/verify-and-execute",
- json={"asset_qrcode": "INVALID-QRCODE"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "二维码无效" in response.json()["detail"]
-
- def test_get_execution_progress(self, client, auth_headers, test_allocation_order, db: Session):
- """测试获取执行进度"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/progress",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "completed_items" in data
- assert "total_items" in data
- assert "progress_percentage" in data
-
- def test_add_execution_note(self, client, auth_headers, test_allocation_order):
- """测试添加执行备注"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execution-notes",
- json={"note": "执行过程中的备注"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_print_allocation_document(self, client, auth_headers, test_allocation_order):
- """测试打印分配单据"""
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/print",
- headers=auth_headers
- )
- assert response.status_code == 200
- assert "document_url" in response.json()
-
-
-# ================================
-# 状态转换测试 (10+用例)
-# ================================
-
-class TestAllocationStatusTransitions:
- """分配单状态转换测试"""
-
- def test_status_transition_pending_to_approved(self, client, auth_headers, test_allocation_order):
- """测试状态转换: 待审批 -> 已审批"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == AllocationStatus.APPROVED
-
- def test_status_transition_pending_to_rejected(self, client, auth_headers, test_allocation_order):
- """测试状态转换: 待审批 -> 已拒绝"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/reject",
- json={"rejection_reason": "理由"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == AllocationStatus.REJECTED
-
- def test_status_transition_approved_to_in_progress(self, client, auth_headers, test_allocation_order, db: Session):
- """测试状态转换: 已审批 -> 执行中"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execute",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == AllocationStatus.IN_PROGRESS
-
- def test_status_transition_in_progress_to_completed(self, client, auth_headers, test_allocation_order, db: Session):
- """测试状态转换: 执行中 -> 已完成"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/complete",
- json={"completion_note": "完成", "recipient_name": "测试"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == AllocationStatus.COMPLETED
-
- def test_invalid_transition_from_completed(self, client, auth_headers, test_allocation_order, db: Session):
- """测试已完成状态不允许转换"""
- test_allocation_order.status = AllocationStatus.COMPLETED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "尝试重新审批"},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_invalid_transition_from_rejected(self, client, auth_headers, test_allocation_order, db: Session):
- """测试已拒绝状态不允许转换为执行中"""
- test_allocation_order.status = AllocationStatus.REJECTED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execute",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_status_transition_pending_to_cancelled(self, client, auth_headers, test_allocation_order):
- """测试状态转换: 待审批 -> 已取消"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/cancel",
- json={"cancellation_reason": "取消原因"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == AllocationStatus.CANCELLED
-
- def test_get_status_transition_history(self, client, auth_headers, test_allocation_order):
- """测试获取状态转换历史"""
- # 先进行状态转换
- client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "测试"},
- headers=auth_headers
- )
-
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/status-history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data) >= 1
-
- def test_auto_transition_on_all_items_completed(self, client, auth_headers, test_allocation_order, db: Session):
- """测试所有项完成后自动转换状态"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- # 标记所有项为完成
- for item in test_allocation_order.items:
- item.status = "completed"
- db.commit()
-
- # 触发自动完成
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/check-auto-complete",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_available_status_transitions(self, client, auth_headers, test_allocation_order):
- """测试获取可用的状态转换"""
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}/available-transitions",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert len(data) > 0
-
-
-# ================================
-# 权限测试 (10+用例)
-# ================================
-
-class TestAllocationPermissions:
- """分配单权限测试"""
-
- def test_user_can_view_own_allocations(self, client, auth_headers):
- """测试用户可以查看自己的分配单"""
- response = client.get(
- "/api/v1/allocations/?my_allocations=true",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_user_cannot_view_other_departments_allocations(self, client, auth_headers, test_allocation_order):
- """测试用户不能查看其他部门的分配单"""
- # 假设test_allocation_order属于其他部门
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}",
- headers=auth_headers
- )
- # 根据权限设计,可能返回403或404
- assert response.status_code in [403, 404]
-
- def test_admin_can_view_all_allocations(self, client, admin_headers, test_allocation_order):
- """测试管理员可以查看所有分配单"""
- response = client.get(
- f"/api/v1/allocations/{test_allocation_order.id}",
- headers=admin_headers
- )
- assert response.status_code == 200
-
- def test_only_approver_can_approve_allocation(self, client, normal_user_headers, test_allocation_order):
- """测试只有审批人才能审批分配单"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={"approval_comment": "尝试审批"},
- headers=normal_user_headers
- )
- assert response.status_code == 403
-
- def test_user_can_create_allocation_for_own_org(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试用户可以为自己的组织创建分配单"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- },
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_user_cannot_create_allocation_for_other_org(self, client, auth_headers, test_assets_for_allocation, db: Session):
- """测试用户不能为其他组织创建分配单"""
- other_org = Organization(
- org_code="OTHER-001",
- org_name="其他组织",
- org_type="department"
- )
- db.add(other_org)
- db.commit()
- db.refresh(other_org)
-
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": other_org.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- },
- headers=auth_headers
- )
- assert response.status_code == 403
-
- def test_only_executor_can_execute_allocation(self, client, normal_user_headers, test_allocation_order, db: Session):
- """测试只有执行人员才能执行分配单"""
- test_allocation_order.status = AllocationStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/execute",
- json={},
- headers=normal_user_headers
- )
- assert response.status_code == 403
-
- def test_permission_check_on_batch_operations(self, client, auth_headers):
- """测试批量操作时的权限检查"""
- response = client.post(
- "/api/v1/allocations/batch-approve",
- json={"allocation_ids": [1, 2, 3], "comment": "批量操作"},
- headers=auth_headers
- )
- # 可能返回403或部分成功
- assert response.status_code in [200, 403]
-
- def test_role_based_access_control(self, client, auth_headers, admin_headers):
- """测试基于角色的访问控制"""
- # 普通用户访问管理接口
- response = client.get(
- "/api/v1/allocations/admin/all-allocations",
- headers=auth_headers
- )
- assert response.status_code == 403
-
- # 管理员访问
- response = client.get(
- "/api/v1/allocations/admin/all-allocations",
- headers=admin_headers
- )
- assert response.status_code == 200
-
- def test_permission_inheritance_from_org(self, client, auth_headers, db: Session, test_assets_for_allocation):
- """测试组织权限继承"""
- # 测试子组织用户是否可以访问父组织的分配单
- pass
-
-
-# ================================
-# 参数验证测试 (10+用例)
-# ================================
-
-class TestAllocationValidation:
- """分配单参数验证测试"""
-
- def test_validate_required_fields(self, client, auth_headers):
- """测试必填字段验证"""
- response = client.post(
- "/api/v1/allocations/",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 422
-
- def test_validate_target_org_id_is_integer(self, client, auth_headers, test_assets_for_allocation):
- """测试目标组织ID必须为整数"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": "invalid",
- "asset_ids": [test_assets_for_allocation[0].id],
- },
- headers=auth_headers
- )
- assert response.status_code == 422
-
- def test_validate_expected_date_is_future(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试预期日期必须是未来时间"""
- past_date = (datetime.now() - timedelta(days=1)).isoformat()
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- "expected_date": past_date
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_asset_ids_list_length(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试资产ID列表长度限制"""
- # 假设最多100个资产
- asset_ids = [i for i in range(101)]
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": asset_ids,
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_remark_max_length(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试备注长度限制"""
- long_remark = "x" * 1001
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- "remark": long_remark
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_approval_comment_required(self, client, auth_headers, test_allocation_order):
- """测试审批备注必填"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/approve",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_rejection_reason_required(self, client, auth_headers, test_allocation_order):
- """测试拒绝原因必填"""
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/reject",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_completion_recipient_name(self, client, auth_headers, test_allocation_order, db: Session):
- """测试完成时接收人姓名必填"""
- test_allocation_order.status = AllocationStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/allocations/{test_allocation_order.id}/complete",
- json={"completion_note": "完成"},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_date_format(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试日期格式验证"""
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- "expected_date": "invalid-date"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_validate_batch_operation_ids(self, client, auth_headers):
- """测试批量操作ID列表验证"""
- response = client.post(
- "/api/v1/allocations/batch-approve",
- json={"allocation_ids": [], "comment": "测试"},
- headers=auth_headers
- )
- assert response.status_code == 400
-
-
-# ================================
-# 异常处理测试 (5+用例)
-# ================================
-
-class TestAllocationExceptionHandling:
- """分配单异常处理测试"""
-
- def test_handle_concurrent_approval(self, client, auth_headers, test_allocation_order):
- """测试并发审批处理"""
- # 模拟两个用户同时审批
- pass
-
- def test_handle_asset_already_allocated(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization):
- """测试资产已被分配的情况"""
- asset = test_assets_for_allocation[0]
- asset.status = "in_use"
- db.commit()
-
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [asset.id],
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_handle_database_connection_error(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试数据库连接错误处理"""
- # 需要mock数据库连接
- pass
-
- def test_handle_notification_failure(self, client, auth_headers, test_allocation_order):
- """测试通知发送失败时的处理"""
- # 需要mock通知服务
- pass
-
- def test_handle_transaction_rollback_on_error(self, client, auth_headers, db: Session, test_assets_for_allocation, test_target_organization):
- """测试错误时事务回滚"""
- initial_count = db.query(Allocation).count()
-
- # 尝试创建无效的分配单
- response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [999999], # 不存在的资产
- },
- headers=auth_headers
- )
-
- # 验证没有创建新记录
- final_count = db.query(Allocation).count()
- assert initial_count == final_count
-
-
-# ================================
-# 测试标记
-# ================================
-
-@pytest.mark.unit
-class TestAllocationUnit:
- """单元测试标记"""
-
- def test_allocation_order_number_generation(self):
- """测试分配单号生成逻辑"""
- pass
-
- def test_allocation_status_validation(self):
- """测试状态验证逻辑"""
- pass
-
-
-@pytest.mark.integration
-class TestAllocationIntegration:
- """集成测试标记"""
-
- def test_full_allocation_workflow(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """测试完整的分配流程"""
- # 1. 创建分配单
- create_response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
- allocation_id = create_response.json()["id"]
-
- # 2. 审批
- approve_response = client.post(
- f"/api/v1/allocations/{allocation_id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
- assert approve_response.status_code == 200
-
- # 3. 执行
- execute_response = client.post(
- f"/api/v1/allocations/{allocation_id}/execute",
- json={},
- headers=auth_headers
- )
- assert execute_response.status_code == 200
-
- # 4. 完成
- complete_response = client.post(
- f"/api/v1/allocations/{allocation_id}/complete",
- json={"completion_note": "完成", "recipient_name": "测试"},
- headers=auth_headers
- )
- assert complete_response.status_code == 200
-
-
-@pytest.mark.slow
-class TestAllocationSlowTests:
- """慢速测试标记"""
-
- def test_large_batch_allocation(self, client, auth_headers, db: Session, test_target_organization):
- """测试大批量分配"""
- # 创建大量资产
- pass
-
-
-@pytest.mark.smoke
-class TestAllocationSmoke:
- """冒烟测试标记 - 核心功能快速验证"""
-
- def test_create_and_approve_allocation(self, client, auth_headers, test_assets_for_allocation, test_target_organization):
- """冒烟测试: 创建并审批分配单"""
- create_response = client.post(
- "/api/v1/allocations/",
- json={
- "target_org_id": test_target_organization.id,
- "asset_ids": [test_assets_for_allocation[0].id],
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
-
- allocation_id = create_response.json()["id"]
- approve_response = client.post(
- f"/api/v1/allocations/{allocation_id}/approve",
- json={"approval_comment": "冒烟测试"},
- headers=auth_headers
- )
- assert approve_response.status_code == 200
diff --git a/tests/api/test_api_integration.py b/tests/api/test_api_integration.py
deleted file mode 100644
index 2e25e6b..0000000
--- a/tests/api/test_api_integration.py
+++ /dev/null
@@ -1,426 +0,0 @@
-"""
-接口集成测试
-
-测试内容:
-- 所有API接口功能测试
-- 参数验证测试
-- 错误处理测试
-- 响应时间测试
-- 并发测试
-"""
-
-import pytest
-import time
-import asyncio
-from concurrent.futures import ThreadPoolExecutor
-# from fastapi.testclient import TestClient
-
-
-# class TestAPIEndpoints:
-# """测试所有API端点"""
-#
-# def test_health_check(self, client: TestClient):
-# """测试健康检查接口"""
-# response = client.get("/health")
-# assert response.status_code == 200
-# assert response.json()["status"] == "healthy"
-#
-# def test_api_root(self, client: TestClient):
-# """测试API根路径"""
-# response = client.get("/api/v1/")
-# assert response.status_code == 200
-# data = response.json()
-# assert "version" in data
-# assert "name" in data
-
-
-# class TestParameterValidation:
-# """测试参数验证"""
-#
-# def test_query_parameter_validation(self, client: TestClient, auth_headers):
-# """测试查询参数验证"""
-# # 无效的分页参数
-# response = client.get(
-# "/api/v1/assets?page=-1&page_size=0",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# # 超大的page_size
-# response = client.get(
-# "/api/v1/assets?page_size=10000",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# def test_path_parameter_validation(self, client: TestClient, auth_headers):
-# """测试路径参数验证"""
-# # 无效的ID
-# response = client.get(
-# "/api/v1/assets/abc",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# # 负数ID
-# response = client.get(
-# "/api/v1/assets/-1",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# def test_request_body_validation(self, client: TestClient, auth_headers):
-# """测试请求体验证"""
-# # 缺少必填字段
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json={"asset_name": "测试"} # 缺少device_type_id
-# )
-# assert response.status_code == 422
-#
-# # 无效的数据类型
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json={
-# "asset_name": "测试",
-# "device_type_id": "not_a_number", # 应该是数字
-# "organization_id": 1
-# }
-# )
-# assert response.status_code == 422
-#
-# # 超长字符串
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json={
-# "asset_name": "a" * 300, # 超过最大长度
-# "device_type_id": 1,
-# "organization_id": 1
-# }
-# )
-# assert response.status_code == 422
-#
-# def test_enum_validation(self, client: TestClient, auth_headers):
-# """测试枚举值验证"""
-# # 无效的状态值
-# response = client.get(
-# "/api/v1/assets?status=invalid_status",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# def test_date_validation(self, client: TestClient, auth_headers):
-# """测试日期格式验证"""
-# # 无效的日期格式
-# response = client.get(
-# "/api/v1/assets?purchase_date_start=invalid-date",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-#
-# # 结束日期早于开始日期
-# response = client.get(
-# "/api/v1/assets?purchase_date_start=2024-12-31&purchase_date_end=2024-01-01",
-# headers=auth_headers
-# )
-# assert response.status_code == 400
-
-
-# class TestErrorHandling:
-# """测试错误处理"""
-#
-# def test_404_not_found(self, client: TestClient, auth_headers):
-# """测试404错误"""
-# response = client.get(
-# "/api/v1/assets/999999",
-# headers=auth_headers
-# )
-# assert response.status_code == 404
-# data = response.json()
-# assert "message" in data
-#
-# def test_401_unauthorized(self, client: TestClient):
-# """测试401未授权错误"""
-# response = client.get("/api/v1/assets")
-# assert response.status_code == 401
-#
-# def test_403_forbidden(self, client: TestClient, auth_headers):
-# """测试403禁止访问"""
-# # 使用普通用户token访问管理员接口
-# response = client.delete(
-# "/api/v1/assets/1",
-# headers=auth_headers # 普通用户token
-# )
-# assert response.status_code == 403
-#
-# def test_409_conflict(self, client: TestClient, auth_headers):
-# """测试409冲突错误"""
-# # 尝试创建重复的资源
-# asset_data = {
-# "asset_name": "测试资产",
-# "device_type_id": 1,
-# "organization_id": 1,
-# "serial_number": "UNIQUE-SN-001"
-# }
-#
-# # 第一次创建成功
-# client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
-#
-# # 第二次创建应该返回409
-# response = client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
-# assert response.status_code == 409
-#
-# def test_422_validation_error(self, client: TestClient, auth_headers):
-# """测试422验证错误"""
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json={}
-# )
-# assert response.status_code == 422
-# data = response.json()
-# assert "errors" in data
-#
-# def test_500_internal_error(self, client: TestClient, auth_headers):
-# """测试500服务器错误"""
-# # 这个测试需要mock一个会抛出异常的场景
-# pass
-#
-# def test_error_response_format(self, client: TestClient, auth_headers):
-# """测试错误响应格式"""
-# response = client.get(
-# "/api/v1/assets/999999",
-# headers=auth_headers
-# )
-# assert response.status_code == 404
-#
-# data = response.json()
-# # 验证错误响应包含必要字段
-# assert "code" in data
-# assert "message" in data
-# assert "timestamp" in data
-
-
-# class TestResponseTime:
-# """测试接口响应时间"""
-#
-# @pytest.mark.parametrize("endpoint,expected_max_time", [
-# ("/api/v1/assets", 0.5), # 资产列表应该在500ms内返回
-# ("/api/v1/assets/1", 0.3), # 资产详情应该在300ms内返回
-# ("/api/v1/statistics/overview", 1.0), # 统计概览在1秒内返回
-# ])
-# def test_response_time_within_limit(self, client, auth_headers, endpoint, expected_max_time):
-# """测试响应时间在限制内"""
-# start_time = time.time()
-#
-# response = client.get(endpoint, headers=auth_headers)
-#
-# elapsed_time = time.time() - start_time
-#
-# assert response.status_code == 200
-# assert elapsed_time < expected_max_time, \
-# f"响应时间 {elapsed_time:.2f}s 超过限制 {expected_max_time}s"
-#
-# def test_concurrent_requests_performance(self, client, auth_headers):
-# """测试并发请求性能"""
-# urls = ["/api/v1/assets"] * 10
-#
-# start_time = time.time()
-#
-# with ThreadPoolExecutor(max_workers=5) as executor:
-# futures = [
-# executor.submit(
-# client.get,
-# url,
-# headers=auth_headers
-# )
-# for url in urls
-# ]
-# responses = [f.result() for f in futures]
-#
-# elapsed_time = time.time() - start_time
-#
-# # 所有请求都应该成功
-# assert all(r.status_code == 200 for r in responses)
-#
-# # 10个并发请求应该在3秒内完成
-# assert elapsed_time < 3.0
-#
-# def test_large_list_response_time(self, client, auth_headers, db):
-# """测试大数据量列表响应时间"""
-# # 创建1000条测试数据
-# # ... 创建数据
-#
-# start_time = time.time()
-# response = client.get("/api/v1/assets?page=1&page_size=100", headers=auth_headers)
-# elapsed_time = time.time() - start_time
-#
-# assert response.status_code == 200
-# assert elapsed_time < 1.0 # 100条记录应该在1秒内返回
-#
-# def test_complex_query_response_time(self, client, auth_headers):
-# """测试复杂查询响应时间"""
-# params = {
-# "keyword": "联想",
-# "device_type_id": 1,
-# "organization_id": 1,
-# "status": "in_use",
-# "purchase_date_start": "2024-01-01",
-# "purchase_date_end": "2024-12-31",
-# "page": 1,
-# "page_size": 20
-# }
-#
-# start_time = time.time()
-# response = client.get("/api/v1/assets", params=params, headers=auth_headers)
-# elapsed_time = time.time() - start_time
-#
-# assert response.status_code == 200
-# assert elapsed_time < 1.0
-
-
-# class TestConcurrentRequests:
-# """测试并发请求"""
-#
-# def test_concurrent_asset_creation(self, client, auth_headers):
-# """测试并发创建资产"""
-# asset_data = {
-# "asset_name": "并发测试资产",
-# "device_type_id": 1,
-# "organization_id": 1
-# }
-#
-# def create_asset(i):
-# data = asset_data.copy()
-# data["asset_name"] = f"并发测试资产-{i}"
-# return client.post("/api/v1/assets", headers=auth_headers, json=data)
-#
-# with ThreadPoolExecutor(max_workers=10) as executor:
-# futures = [executor.submit(create_asset, i) for i in range(50)]
-# responses = [f.result() for f in futures]
-#
-# # 所有请求都应该成功
-# success_count = sum(1 for r in responses if r.status_code == 201)
-# assert success_count == 50
-#
-# def test_concurrent_same_resource_update(self, client, auth_headers, test_asset):
-# """测试并发更新同一资源"""
-# def update_asset(i):
-# return client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers,
-# json={"location": f"位置-{i}"}
-# )
-#
-# with ThreadPoolExecutor(max_workers=5) as executor:
-# futures = [executor.submit(update_asset, i) for i in range(10)]
-# responses = [f.result() for f in futures]
-#
-# # 所有请求都应该成功(乐观锁会处理并发)
-# assert all(r.status_code in [200, 409] for r in responses)
-#
-# @pytest.mark.slow
-# def test_high_concurrent_load(self, client, auth_headers):
-# """测试高并发负载"""
-# def make_request():
-# return client.get("/api/v1/assets", headers=auth_headers)
-#
-# # 模拟100个并发请求
-# with ThreadPoolExecutor(max_workers=20) as executor:
-# futures = [executor.submit(make_request) for _ in range(100)]
-# responses = [f.result() for f in futures]
-#
-# success_count = sum(1 for r in responses if r.status_code == 200)
-# success_rate = success_count / 100
-#
-# # 成功率应该大于95%
-# assert success_rate > 0.95
-#
-# def test_rate_limiting(self, client):
-# """测试请求频率限制"""
-# # 登录接口限制10次/分钟
-# responses = []
-# for i in range(12):
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "test",
-# "password": "test",
-# "captcha": "1234",
-# "captcha_key": f"test-{i}"
-# }
-# )
-# responses.append(response)
-#
-# # 应该有部分请求被限流
-# rate_limited_count = sum(1 for r in responses if r.status_code == 429)
-# assert rate_limited_count >= 1
-
-
-# class TestDataIntegrity:
-# """测试数据完整性"""
-#
-# def test_create_and_retrieve_asset(self, client, auth_headers):
-# """测试创建后获取数据一致性"""
-# # 创建资产
-# asset_data = {
-# "asset_name": "数据完整性测试",
-# "device_type_id": 1,
-# "organization_id": 1,
-# "model": "测试型号"
-# }
-#
-# create_response = client.post("/api/v1/assets", headers=auth_headers, json=asset_data)
-# assert create_response.status_code == 201
-# created_asset = create_response.json()["data"]
-#
-# # 获取资产
-# get_response = client.get(
-# f"/api/v1/assets/{created_asset['id']}",
-# headers=auth_headers
-# )
-# assert get_response.status_code == 200
-# retrieved_asset = get_response.json()["data"]
-#
-# # 验证数据一致性
-# assert retrieved_asset["asset_name"] == asset_data["asset_name"]
-# assert retrieved_asset["model"] == asset_data["model"]
-#
-# def test_update_and_retrieve_asset(self, client, auth_headers, test_asset):
-# """测试更新后获取数据一致性"""
-# # 更新资产
-# updated_data = {"asset_name": "更新后的名称"}
-# client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers,
-# json=updated_data
-# )
-#
-# # 获取资产
-# response = client.get(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# asset = response.json()["data"]
-#
-# # 验证更新生效
-# assert asset["asset_name"] == updated_data["asset_name"]
-#
-# def test_delete_and_verify_asset(self, client, auth_headers, test_asset):
-# """测试删除后无法获取"""
-# # 删除资产
-# delete_response = client.delete(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# assert delete_response.status_code == 200
-#
-# # 验证无法获取
-# get_response = client.get(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# assert get_response.status_code == 404
diff --git a/tests/api/test_assets.py b/tests/api/test_assets.py
deleted file mode 100644
index 6154b4d..0000000
--- a/tests/api/test_assets.py
+++ /dev/null
@@ -1,459 +0,0 @@
-"""
-资产管理模块API测试
-
-测试内容:
-- 资产列表查询
-- 资产详情查询
-- 创建资产
-- 更新资产
-- 删除资产
-- 批量导入
-- 扫码查询
-"""
-
-import pytest
-from datetime import date
-
-
-# class TestAssetList:
-# """测试资产列表"""
-#
-# def test_get_assets_success(self, client: TestClient, auth_headers):
-# """测试获取资产列表成功"""
-# response = client.get(
-# "/api/v1/assets",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert "items" in data["data"]
-# assert "total" in data["data"]
-# assert "page" in data["data"]
-#
-# def test_get_assets_with_pagination(self, client: TestClient, auth_headers):
-# """测试分页查询"""
-# response = client.get(
-# "/api/v1/assets?page=1&page_size=10",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["data"]["page"] == 1
-# assert data["data"]["page_size"] == 10
-# assert len(data["data"]["items"]) <= 10
-#
-# def test_get_assets_with_keyword(self, client: TestClient, auth_headers, test_asset):
-# """测试关键词搜索"""
-# response = client.get(
-# f"/api/v1/assets?keyword={test_asset.asset_name}",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert len(data["data"]["items"]) > 0
-#
-# def test_get_assets_with_device_type_filter(self, client: TestClient, auth_headers):
-# """测试按设备类型筛选"""
-# response = client.get(
-# "/api/v1/assets?device_type_id=1",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# def test_get_assets_with_status_filter(self, client: TestClient, auth_headers):
-# """测试按状态筛选"""
-# response = client.get(
-# "/api/v1/assets?status=in_stock",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# def test_get_assets_with_organization_filter(self, client: TestClient, auth_headers):
-# """测试按网点筛选"""
-# response = client.get(
-# "/api/v1/assets?organization_id=1",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# def test_get_assets_with_date_range(self, client: TestClient, auth_headers):
-# """测试按采购日期范围筛选"""
-# response = client.get(
-# "/api/v1/assets?purchase_date_start=2024-01-01&purchase_date_end=2024-12-31",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# def test_get_assets_with_sorting(self, client: TestClient, auth_headers):
-# """测试排序"""
-# response = client.get(
-# "/api/v1/assets?sort_by=purchase_date&sort_order=desc",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# def test_get_assets_unauthorized(self, client: TestClient):
-# """测试未授权访问"""
-# response = client.get("/api/v1/assets")
-# assert response.status_code == 401
-#
-# @pytest.mark.parametrize("page,page_size", [
-# (0, 20), # 页码从0开始
-# (1, 0), # 每页0条
-# (-1, 20), # 负页码
-# (1, 1000), # 超大页码
-# ])
-# def test_get_assets_invalid_pagination(self, client: TestClient, auth_headers, page, page_size):
-# """测试无效分页参数"""
-# response = client.get(
-# f"/api/v1/assets?page={page}&page_size={page_size}",
-# headers=auth_headers
-# )
-# assert response.status_code == 422
-
-
-# class TestAssetDetail:
-# """测试资产详情"""
-#
-# def test_get_asset_detail_success(self, client: TestClient, auth_headers, test_asset):
-# """测试获取资产详情成功"""
-# response = client.get(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert data["data"]["id"] == test_asset.id
-# assert data["data"]["asset_code"] == test_asset.asset_code
-# assert "status_history" in data["data"]
-#
-# def test_get_asset_detail_not_found(self, client: TestClient, auth_headers):
-# """测试获取不存在的资产"""
-# response = client.get(
-# "/api/v1/assets/999999",
-# headers=auth_headers
-# )
-# assert response.status_code == 404
-# data = response.json()
-# assert data["code"] == 30002 # 资产不存在
-#
-# def test_get_asset_detail_unauthorized(self, client: TestClient, test_asset):
-# """测试未授权访问"""
-# response = client.get(f"/api/v1/assets/{test_asset.id}")
-# assert response.status_code == 401
-
-
-# class TestCreateAsset:
-# """测试创建资产"""
-#
-# def test_create_asset_success(self, client: TestClient, auth_headers, sample_asset_data):
-# """测试创建资产成功"""
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=sample_asset_data
-# )
-# assert response.status_code == 201
-# data = response.json()
-# assert data["code"] == 200
-# assert "asset_code" in data["data"]
-# assert data["data"]["asset_code"].startswith("ASSET-")
-# assert data["data"]["status"] == "pending"
-#
-# def test_create_asset_without_auth(self, client: TestClient, sample_asset_data):
-# """测试未认证创建"""
-# response = client.post("/api/v1/assets", json=sample_asset_data)
-# assert response.status_code == 401
-#
-# def test_create_asset_missing_required_fields(self, client: TestClient, auth_headers):
-# """测试缺少必填字段"""
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json={"asset_name": "测试资产"} # 缺少device_type_id等必填字段
-# )
-# assert response.status_code == 422
-#
-# @pytest.mark.parametrize("field,value,error_msg", [
-# ("asset_name", "", "资产名称不能为空"),
-# ("asset_name", "a" * 201, "资产名称过长"),
-# ("device_type_id", 0, "设备类型ID无效"),
-# ("device_type_id", -1, "设备类型ID无效"),
-# ("purchase_price", -100, "采购价格不能为负数"),
-# ])
-# def test_create_asset_invalid_field(self, client: TestClient, auth_headers, field, value, error_msg):
-# """测试无效字段值"""
-# data = {
-# "asset_name": "测试资产",
-# "device_type_id": 1,
-# "organization_id": 1
-# }
-# data[field] = value
-#
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=data
-# )
-# assert response.status_code in [400, 422]
-#
-# def test_create_asset_duplicate_serial_number(self, client: TestClient, auth_headers, sample_asset_data):
-# """测试序列号重复"""
-# # 第一次创建
-# client.post("/api/v1/assets", headers=auth_headers, json=sample_asset_data)
-#
-# # 第二次使用相同序列号创建
-# response = client.post("/api/v1/assets", headers=auth_headers, json=sample_asset_data)
-# assert response.status_code == 409 # Conflict
-#
-# def test_create_asset_with_dynamic_attributes(self, client: TestClient, auth_headers):
-# """测试带动态字段创建"""
-# data = {
-# "asset_name": "测试资产",
-# "device_type_id": 1,
-# "organization_id": 1,
-# "dynamic_attributes": {
-# "cpu": "Intel i5-10400",
-# "memory": "16GB",
-# "disk": "512GB SSD",
-# "gpu": "GTX 1660Ti"
-# }
-# }
-#
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=data
-# )
-# assert response.status_code == 201
-#
-# def test_create_asset_invalid_device_type(self, client: TestClient, auth_headers, sample_asset_data):
-# """测试无效的设备类型"""
-# sample_asset_data["device_type_id"] = 999999
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=sample_asset_data
-# )
-# assert response.status_code == 400
-#
-# def test_create_asset_invalid_organization(self, client: TestClient, auth_headers, sample_asset_data):
-# """测试无效的网点"""
-# sample_asset_data["organization_id"] = 999999
-# response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=sample_asset_data
-# )
-# assert response.status_code == 400
-
-
-# class TestUpdateAsset:
-# """测试更新资产"""
-#
-# def test_update_asset_success(self, client: TestClient, auth_headers, test_asset):
-# """测试更新资产成功"""
-# response = client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers,
-# json={
-# "asset_name": "更新后的资产名称",
-# "location": "新位置"
-# }
-# )
-# assert response.status_code == 200
-#
-# def test_update_asset_partial_fields(self, client: TestClient, auth_headers, test_asset):
-# """测试部分字段更新"""
-# response = client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers,
-# json={"location": "只更新位置"}
-# )
-# assert response.status_code == 200
-#
-# def test_update_asset_not_found(self, client: TestClient, auth_headers):
-# """测试更新不存在的资产"""
-# response = client.put(
-# "/api/v1/assets/999999",
-# headers=auth_headers,
-# json={"asset_name": "新名称"}
-# )
-# assert response.status_code == 404
-#
-# def test_update_asset_status_forbidden(self, client: TestClient, auth_headers, test_asset):
-# """测试禁止直接修改状态"""
-# response = client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers,
-# json={"status": "in_use"} # 状态应该通过分配单修改
-# )
-# # 状态字段应该被忽略或返回错误
-# assert response.status_code in [200, 400]
-#
-# def test_update_asset_unauthorized(self, client: TestClient, test_asset):
-# """测试未授权更新"""
-# response = client.put(
-# f"/api/v1/assets/{test_asset.id}",
-# json={"asset_name": "新名称"}
-# )
-# assert response.status_code == 401
-
-
-# class TestDeleteAsset:
-# """测试删除资产"""
-#
-# def test_delete_asset_success(self, client: TestClient, auth_headers, test_asset):
-# """测试删除资产成功"""
-# response = client.delete(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-#
-# # 验证删除
-# get_response = client.get(
-# f"/api/v1/assets/{test_asset.id}",
-# headers=auth_headers
-# )
-# assert get_response.status_code == 404
-#
-# def test_delete_asset_not_found(self, client: TestClient, auth_headers):
-# """测试删除不存在的资产"""
-# response = client.delete(
-# "/api/v1/assets/999999",
-# headers=auth_headers
-# )
-# assert response.status_code == 404
-#
-# def test_delete_asset_in_use(self, client: TestClient, auth_headers):
-# """测试删除使用中的资产"""
-# # 创建使用中的资产
-# # ... 创建in_use状态的资产
-#
-# response = client.delete(
-# "/api/v1/assets/1",
-# headers=auth_headers
-# )
-# # 使用中的资产不能删除
-# assert response.status_code == 400
-#
-# def test_delete_asset_without_permission(self, client: TestClient, auth_headers):
-# """测试无权限删除"""
-# # 使用普通用户token而非管理员
-# response = client.delete(
-# "/api/v1/assets/1",
-# headers=auth_headers
-# )
-# assert response.status_code == 403
-
-
-# class TestAssetImport:
-# """测试批量导入资产"""
-#
-# def test_import_assets_success(self, client: TestClient, auth_headers):
-# """测试导入成功"""
-# # 准备测试Excel文件
-# # ... 创建临时Excel文件
-#
-# with open("test_import.xlsx", "rb") as f:
-# files = {"file": ("test_import.xlsx", f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
-# response = client.post(
-# "/api/v1/assets/import",
-# headers=auth_headers,
-# files=files
-# )
-#
-# assert response.status_code == 200
-# data = response.json()
-# assert data["data"]["total"] > 0
-# assert data["data"]["success"] > 0
-#
-# def test_import_assets_partial_failure(self, client: TestClient, auth_headers):
-# """测试部分失败"""
-# # 准备包含错误数据的Excel文件
-#
-# with open("test_import_partial_fail.xlsx", "rb") as f:
-# files = {"file": ("test_import.xlsx", f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
-# response = client.post(
-# "/api/v1/assets/import",
-# headers=auth_headers,
-# files=files
-# )
-#
-# assert response.status_code == 200
-# data = response.json()
-# assert data["data"]["failed"] > 0
-# assert len(data["data"]["errors"]) > 0
-#
-# def test_import_assets_invalid_file_format(self, client: TestClient, auth_headers):
-# """测试无效文件格式"""
-# with open("test.txt", "rb") as f:
-# files = {"file": ("test.txt", f, "text/plain")}
-# response = client.post(
-# "/api/v1/assets/import",
-# headers=auth_headers,
-# files=files
-# )
-#
-# assert response.status_code == 400
-#
-# def test_import_assets_missing_columns(self, client: TestClient, auth_headers):
-# """测试缺少必填列"""
-# # 准备缺少必填列的Excel文件
-#
-# with open("test_missing_columns.xlsx", "rb") as f:
-# files = {"file": ("test.xlsx", f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
-# response = client.post(
-# "/api/v1/assets/import",
-# headers=auth_headers,
-# files=files
-# )
-#
-# assert response.status_code == 400
-
-
-# class TestAssetScan:
-# """测试扫码查询"""
-#
-# def test_scan_asset_success(self, client: TestClient, auth_headers, test_asset):
-# """测试扫码查询成功"""
-# response = client.get(
-# f"/api/v1/assets/scan/{test_asset.asset_code}",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["data"]["asset_code"] == test_asset.asset_code
-#
-# def test_scan_asset_invalid_code(self, client: TestClient, auth_headers):
-# """测试无效的资产编码"""
-# response = client.get(
-# "/api/v1/assets/scan/INVALID-CODE",
-# headers=auth_headers
-# )
-# assert response.status_code == 404
-#
-# def test_scan_asset_without_auth(self, client: TestClient, test_asset):
-# """测试未认证扫码"""
-# response = client.get(f"/api/v1/assets/scan/{test_asset.asset_code}")
-# assert response.status_code == 401
-
-
-# class TestAssetStatistics:
-# """测试资产统计"""
-#
-# def test_get_asset_summary(self, client: TestClient, auth_headers):
-# """测试获取资产汇总"""
-# response = client.get(
-# "/api/v1/assets",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert "summary" in data["data"]
-# assert "total_count" in data["data"]["summary"]
-# assert "total_value" in data["data"]["summary"]
-# assert "status_distribution" in data["data"]["summary"]
diff --git a/tests/api/test_auth.py b/tests/api/test_auth.py
deleted file mode 100644
index f099444..0000000
--- a/tests/api/test_auth.py
+++ /dev/null
@@ -1,356 +0,0 @@
-"""
-认证模块API测试
-
-测试内容:
-- 用户登录
-- Token刷新
-- 用户登出
-- 修改密码
-- 验证码获取
-"""
-
-import pytest
-# from fastapi.testclient import TestClient
-# from app.core.config import settings
-
-
-# class TestAuthLogin:
-# """测试用户登录"""
-#
-# def test_login_success(self, client: TestClient, test_user):
-# """测试登录成功"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "testuser",
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert "access_token" in data["data"]
-# assert "refresh_token" in data["data"]
-# assert data["data"]["token_type"] == "Bearer"
-# assert "user" in data["data"]
-#
-# def test_login_wrong_password(self, client: TestClient):
-# """测试密码错误"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "testuser",
-# "password": "WrongPassword",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 401
-# data = response.json()
-# assert data["code"] == 10001 # 用户名或密码错误
-#
-# def test_login_user_not_found(self, client: TestClient):
-# """测试用户不存在"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "nonexistent",
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 401
-#
-# def test_login_missing_fields(self, client: TestClient):
-# """测试缺少必填字段"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={"username": "testuser"}
-# )
-# assert response.status_code == 422 # Validation error
-#
-# @pytest.mark.parametrize("username", [
-# "", # 空字符串
-# "ab", # 太短
-# "a" * 51, # 太长
-# ])
-# def test_login_invalid_username(self, client: TestClient, username):
-# """测试无效用户名"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": username,
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 422
-#
-# @pytest.mark.parametrize("password", [
-# "", # 空字符串
-# "short", # 太短
-# "nospecial123", # 缺少特殊字符
-# "NOlower123!", # 缺少小写字母
-# "noupper123!", # 缺少大写字母
-# "NoNumber!!", # 缺少数字
-# ])
-# def test_login_invalid_password(self, client: TestClient, password):
-# """测试无效密码"""
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "testuser",
-# "password": password,
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# # 某些情况可能是422(验证失败),某些情况可能是401(认证失败)
-# assert response.status_code in [400, 422, 401]
-#
-# def test_login_account_locked(self, client: TestClient, db):
-# """测试账户被锁定"""
-# # 创建一个锁定的账户
-# # ... 创建锁定用户逻辑
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "lockeduser",
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 403
-#
-# def test_login_account_disabled(self, client: TestClient, db):
-# """测试账户被禁用"""
-# # ... 创建禁用用户逻辑
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "disableduser",
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# assert response.status_code == 403
-
-
-# class TestTokenRefresh:
-# """测试Token刷新"""
-#
-# def test_refresh_token_success(self, client: TestClient, test_user):
-# """测试刷新Token成功"""
-# # 先登录获取refresh_token
-# login_response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "testuser",
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test-uuid"
-# }
-# )
-# refresh_token = login_response.json()["data"]["refresh_token"]
-#
-# # 刷新Token
-# response = client.post(
-# "/api/v1/auth/refresh",
-# json={"refresh_token": refresh_token}
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert "access_token" in data["data"]
-# assert "expires_in" in data["data"]
-#
-# def test_refresh_token_invalid(self, client: TestClient):
-# """测试无效的refresh_token"""
-# response = client.post(
-# "/api/v1/auth/refresh",
-# json={"refresh_token": "invalid_token"}
-# )
-# assert response.status_code == 401
-# data = response.json()
-# assert data["code"] == 10004 # Token无效
-#
-# def test_refresh_token_expired(self, client: TestClient):
-# """测试过期的refresh_token"""
-# response = client.post(
-# "/api/v1/auth/refresh",
-# json={"refresh_token": "expired_token"}
-# )
-# assert response.status_code == 401
-# data = response.json()
-# assert data["code"] == 10003 # Token过期
-
-
-# class TestAuthLogout:
-# """测试用户登出"""
-#
-# def test_logout_success(self, client: TestClient, auth_headers):
-# """测试登出成功"""
-# response = client.post(
-# "/api/v1/auth/logout",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert data["message"] == "登出成功"
-#
-# def test_logout_without_auth(self, client: TestClient):
-# """测试未认证登出"""
-# response = client.post("/api/v1/auth/logout")
-# assert response.status_code == 401
-
-
-# class TestChangePassword:
-# """测试修改密码"""
-#
-# def test_change_password_success(self, client: TestClient, auth_headers):
-# """测试修改密码成功"""
-# response = client.put(
-# "/api/v1/auth/change-password",
-# headers=auth_headers,
-# json={
-# "old_password": "Test123",
-# "new_password": "NewTest456",
-# "confirm_password": "NewTest456"
-# }
-# )
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert data["message"] == "密码修改成功"
-#
-# def test_change_password_wrong_old_password(self, client: TestClient, auth_headers):
-# """测试旧密码错误"""
-# response = client.put(
-# "/api/v1/auth/change-password",
-# headers=auth_headers,
-# json={
-# "old_password": "WrongPassword",
-# "new_password": "NewTest456",
-# "confirm_password": "NewTest456"
-# }
-# )
-# assert response.status_code == 400
-#
-# def test_change_password_mismatch(self, client: TestClient, auth_headers):
-# """测试两次密码不一致"""
-# response = client.put(
-# "/api/v1/auth/change-password",
-# headers=auth_headers,
-# json={
-# "old_password": "Test123",
-# "new_password": "NewTest456",
-# "confirm_password": "DifferentPass789"
-# }
-# )
-# assert response.status_code == 400
-#
-# def test_change_password_weak_password(self, client: TestClient, auth_headers):
-# """测试弱密码"""
-# response = client.put(
-# "/api/v1/auth/change-password",
-# headers=auth_headers,
-# json={
-# "old_password": "Test123",
-# "new_password": "weak",
-# "confirm_password": "weak"
-# }
-# )
-# assert response.status_code == 400
-#
-# def test_change_password_without_auth(self, client: TestClient):
-# """测试未认证修改密码"""
-# response = client.put(
-# "/api/v1/auth/change-password",
-# json={
-# "old_password": "Test123",
-# "new_password": "NewTest456",
-# "confirm_password": "NewTest456"
-# }
-# )
-# assert response.status_code == 401
-
-
-# class TestCaptcha:
-# """测试验证码"""
-#
-# def test_get_captcha_success(self, client: TestClient):
-# """测试获取验证码成功"""
-# response = client.get("/api/v1/auth/captcha")
-# assert response.status_code == 200
-# data = response.json()
-# assert data["code"] == 200
-# assert "captcha_key" in data["data"]
-# assert "captcha_image" in data["data"]
-# assert data["data"]["captcha_image"].startswith("data:image/png;base64,")
-#
-# @pytest.mark.parametrize("count", range(5))
-# def test_get_captcha_multiple_times(self, client: TestClient, count):
-# """测试多次获取验证码,每次应该不同"""
-# response = client.get("/api/v1/auth/captcha")
-# assert response.status_code == 200
-# data = response.json()
-# assert data["data"]["captcha_key"] is not None
-
-
-# class TestRateLimiting:
-# """测试请求频率限制"""
-#
-# def test_login_rate_limiting(self, client: TestClient):
-# """测试登录接口频率限制"""
-# # 登录接口限制10次/分钟
-# for i in range(11):
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": "testuser",
-# "password": "wrongpass",
-# "captcha": "1234",
-# "captcha_key": f"test-{i}"
-# }
-# )
-#
-# # 第11次应该被限流
-# assert response.status_code == 429
-# data = response.json()
-# assert data["code"] == 429
-# assert "retry_after" in data["data"]
-
-
-# 测试SQL注入攻击
-# class TestSecurity:
-# """测试安全性"""
-#
-# def test_sql_injection_prevention(self, client: TestClient):
-# """测试防止SQL注入"""
-# malicious_inputs = [
-# "admin' OR '1'='1",
-# "admin'--",
-# "admin'/*",
-# "' OR 1=1--",
-# "'; DROP TABLE users--"
-# ]
-#
-# for malicious_input in malicious_inputs:
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": malicious_input,
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test"
-# }
-# )
-# # 应该返回认证失败,而不是数据库错误
-# assert response.status_code in [401, 400, 422]
diff --git a/tests/api/test_device_types.py b/tests/api/test_device_types.py
deleted file mode 100644
index 9253f29..0000000
--- a/tests/api/test_device_types.py
+++ /dev/null
@@ -1,880 +0,0 @@
-"""
-设备类型管理模块API测试
-
-测试内容:
-- 设备类型CRUD测试(15+用例)
-- 动态字段配置测试(10+用例)
-- 字段验证测试(10+用例)
-- 参数验证测试(10+用例)
-- 异常处理测试(5+用例)
-"""
-
-import pytest
-from httpx import AsyncClient
-from datetime import datetime
-
-
-# ==================== 设备类型CRUD测试 ====================
-
-class TestDeviceTypeCRUD:
- """测试设备类型CRUD操作"""
-
- @pytest.mark.asyncio
- async def test_create_device_type_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- sample_device_type_data: dict
- ):
- """测试创建设备类型成功"""
- response = await client.post(
- "/api/v1/device-types",
- headers=admin_headers,
- json=sample_device_type_data
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert data["data"]["type_code"] == sample_device_type_data["type_code"]
- assert data["data"]["type_name"] == sample_device_type_data["type_name"]
- assert "id" in data["data"]
-
- @pytest.mark.asyncio
- async def test_create_device_type_duplicate_code(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试创建重复代码的设备类型"""
- response = await client.post(
- "/api/v1/device-types",
- headers=admin_headers,
- json={
- "type_code": test_device_type.type_code,
- "type_name": "另一个类型"
- }
- )
-
- assert response.status_code in [400, 409]
-
- @pytest.mark.asyncio
- async def test_get_device_type_list(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试获取设备类型列表"""
- response = await client.get(
- "/api/v1/device-types",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert len(data["data"]) >= 1
-
- @pytest.mark.asyncio
- async def test_get_device_type_by_id(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试根据ID获取设备类型"""
- response = await client.get(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert data["data"]["id"] == test_device_type.id
- assert data["data"]["type_code"] == test_device_type.type_code
-
- @pytest.mark.asyncio
- async def test_get_device_type_by_code(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试根据代码获取设备类型"""
- response = await client.get(
- f"/api/v1/device-types/code/{test_device_type.type_code}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert data["data"]["type_code"] == test_device_type.type_code
-
- @pytest.mark.asyncio
- async def test_get_device_type_with_fields(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type_with_fields
- ):
- """测试获取设备类型及其字段"""
- response = await client.get(
- f"/api/v1/device-types/{test_device_type_with_fields.id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- # 验证字段存在
- # assert "fields" in data["data"]
-
- @pytest.mark.asyncio
- async def test_update_device_type_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试更新设备类型成功"""
- response = await client.put(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers,
- json={
- "type_name": "更新后的类型名称",
- "description": "更新后的描述"
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
-
- @pytest.mark.asyncio
- async def test_update_device_type_status(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试更新设备类型状态"""
- response = await client.put(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers,
- json={"status": "inactive"}
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_delete_device_type_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session,
- test_device_type
- ):
- """测试删除设备类型成功"""
- response = await client.delete(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- # 验证软删除
- get_response = await client.get(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers
- )
- # 应该返回404或显示已删除
- assert get_response.status_code in [404, 200]
-
- @pytest.mark.asyncio
- async def test_delete_device_type_with_assets_forbidden(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试删除有关联资产的设备类型(应该失败)"""
- # 假设test_device_type有关联资产
- # 实际测试中需要先创建资产
- response = await client.delete(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers
- )
-
- # 如果有关联资产应该返回400或403
- # assert response.status_code in [400, 403]
-
- @pytest.mark.asyncio
- async def test_filter_device_type_by_category(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试按分类筛选设备类型"""
- response = await client.get(
- f"/api/v1/device-types?category={test_device_type.category}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- # 验证筛选结果
- # for item in data["data"]:
- # assert item["category"] == test_device_type.category
-
- @pytest.mark.asyncio
- async def test_filter_device_type_by_status(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type
- ):
- """测试按状态筛选设备类型"""
- response = await client.get(
- f"/api/v1/device-types?status={test_device_type.status}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
-
- @pytest.mark.asyncio
- async def test_get_device_type_not_found(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试获取不存在的设备类型"""
- response = await client.get(
- "/api/v1/device-types/999999",
- headers=admin_headers
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_update_device_type_not_found(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试更新不存在的设备类型"""
- response = await client.put(
- "/api/v1/device-types/999999",
- headers=admin_headers,
- json={"type_name": "新名称"}
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_create_device_type_unauthorized(
- self,
- client: AsyncClient,
- sample_device_type_data: dict
- ):
- """测试未授权创建设备类型"""
- response = await client.post(
- "/api/v1/device-types",
- json=sample_device_type_data
- )
-
- assert response.status_code == 401
-
-
-# ==================== 动态字段配置测试 ====================
-
-class TestDynamicFieldConfig:
- """测试动态字段配置"""
-
- @pytest.mark.asyncio
- async def test_add_field_to_device_type(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType,
- sample_field_data: dict
- ):
- """测试为设备类型添加字段"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json=sample_field_data
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert data["data"]["field_code"] == sample_field_data["field_code"]
-
- @pytest.mark.asyncio
- async def test_add_required_field(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试添加必填字段"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "required_field",
- "field_name": "必填字段",
- "field_type": "text",
- "is_required": True,
- "sort_order": 10
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["is_required"] is True
-
- @pytest.mark.asyncio
- async def test_add_select_field_with_options(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试添加下拉选择字段"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "status",
- "field_name": "状态",
- "field_type": "select",
- "is_required": True,
- "options": [
- {"label": "启用", "value": "enabled"},
- {"label": "禁用", "value": "disabled"}
- ],
- "sort_order": 10
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["field_type"] == "select"
- assert len(data["data"]["options"]) == 2
-
- @pytest.mark.asyncio
- async def test_add_number_field_with_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试添加数字字段并设置验证规则"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "price",
- "field_name": "价格",
- "field_type": "number",
- "is_required": False,
- "validation_rules": {
- "min": 0,
- "max": 1000000
- },
- "sort_order": 10
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["field_type"] == "number"
- assert "validation_rules" in data["data"]
-
- @pytest.mark.asyncio
- async def test_get_device_type_fields(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type_with_fields: DeviceType
- ):
- """测试获取设备类型的字段列表"""
- response = await client.get(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert len(data["data"]) >= 3 # 至少3个字段
-
- @pytest.mark.asyncio
- async def test_update_field_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session,
- test_device_type_with_fields: DeviceType
- ):
- """测试更新字段成功"""
- # 获取第一个字段
- fields_response = await client.get(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers
- )
- field_id = fields_response.json()["data"][0]["id"]
-
- response = await client.put(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields/{field_id}",
- headers=admin_headers,
- json={
- "field_name": "更新后的字段名",
- "is_required": False
- }
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_delete_field_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type_with_fields: DeviceType
- ):
- """测试删除字段成功"""
- fields_response = await client.get(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers
- )
- field_id = fields_response.json()["data"][0]["id"]
-
- response = await client.delete(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields/{field_id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_add_duplicate_field_code(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type_with_fields: DeviceType,
- sample_field_data: dict
- ):
- """测试添加重复的字段代码"""
- # 第一次添加
- await client.post(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers,
- json=sample_field_data
- )
-
- # 第二次添加相同代码
- response = await client.post(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers,
- json=sample_field_data
- )
-
- assert response.status_code in [400, 409]
-
- @pytest.mark.asyncio
- async def test_fields_sorted_by_order(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type_with_fields: DeviceType
- ):
- """测试字段按sort_order排序"""
- response = await client.get(
- f"/api/v1/device-types/{test_device_type_with_fields.id}/fields",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- fields = data["data"]
-
- # 验证排序
- for i in range(len(fields) - 1):
- assert fields[i]["sort_order"] <= fields[i + 1]["sort_order"]
-
-
-# ==================== 字段验证测试 ====================
-
-class TestFieldValidation:
- """测试字段验证"""
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize("field_code,field_name,expected_status", [
- ("", "字段名", 422), # 空字段代码
- ("a" * 51, "字段名", 422), # 字段代码过长
- ("valid_code", "", 422), # 空字段名称
- ("valid_code", "a" * 101, 422), # 字段名称过长
- ])
- async def test_field_name_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType,
- field_code: str,
- field_name: str,
- expected_status: int
- ):
- """测试字段名称验证"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": field_code,
- "field_name": field_name,
- "field_type": "text",
- "sort_order": 1
- }
- )
-
- assert response.status_code == expected_status
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize("field_type", [
- "text", "textarea", "number", "date", "select",
- "multiselect", "boolean", "email", "phone", "url"
- ])
- async def test_valid_field_types(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType,
- field_type: str
- ):
- """测试有效的字段类型"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": f"test_{field_type}",
- "field_name": f"测试{field_type}",
- "field_type": field_type,
- "sort_order": 1
- }
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_invalid_field_type(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试无效的字段类型"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test",
- "field_name": "测试",
- "field_type": "invalid_type",
- "sort_order": 1
- }
- )
-
- assert response.status_code in [400, 422]
-
- @pytest.mark.asyncio
- async def test_select_field_without_options(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试select类型字段缺少options"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test_select",
- "field_name": "测试选择",
- "field_type": "select",
- "sort_order": 1
- }
- )
-
- # select类型应该有options
- assert response.status_code in [400, 422]
-
- @pytest.mark.asyncio
- async def test_validation_rules_json_format(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试验证规则的JSON格式"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test_validation",
- "field_name": "测试验证",
- "field_type": "text",
- "validation_rules": {
- "min_length": 1,
- "max_length": 100,
- "pattern": "^[A-Za-z0-9]+$"
- },
- "sort_order": 1
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert "validation_rules" in data["data"]
-
- @pytest.mark.asyncio
- async def test_placeholder_and_help_text(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试placeholder和help_text"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test_help",
- "field_name": "测试帮助",
- "field_type": "text",
- "placeholder": "请输入...",
- "help_text": "这是帮助文本",
- "sort_order": 1
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["placeholder"] == "请输入..."
- assert data["data"]["help_text"] == "这是帮助文本"
-
-
-# ==================== 参数验证测试 ====================
-
-class TestDeviceTypeParameterValidation:
- """测试设备类型参数验证"""
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize("type_code,expected_status", [
- ("", 422), # 空代码
- ("AB", 422), # 太短
- ("a" * 51, 422), # 太长
- ("VALID_CODE", 200), # 有效
- ])
- async def test_type_code_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- type_code: str,
- expected_status: int
- ):
- """测试类型代码验证"""
- response = await client.post(
- "/api/v1/device-types",
- headers=admin_headers,
- json={
- "type_code": type_code,
- "type_name": "测试类型",
- "category": "IT设备"
- }
- )
-
- assert response.status_code == expected_status
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize("type_name,expected_status", [
- ("", 422), # 空名称
- ("a" * 201, 422), # 太长
- ("有效名称", 200), # 有效
- ])
- async def test_type_name_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- type_name: str,
- expected_status: int
- ):
- """测试类型名称验证"""
- response = await client.post(
- "/api/v1/device-types",
- headers=admin_headers,
- json={
- "type_code": "TEST_CODE",
- "type_name": type_name
- }
- )
-
- assert response.status_code == expected_status
-
- @pytest.mark.asyncio
- async def test_sort_order_validation(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试排序验证"""
- response = await client.post(
- "/api/v1/device-types",
- headers=admin_headers,
- json={
- "type_code": "TEST_SORT",
- "type_name": "测试排序",
- "sort_order": -1 # 负数
- }
- )
-
- # 排序可以是负数,或者应该返回422
- # assert response.status_code in [200, 422]
-
- @pytest.mark.asyncio
- @pytest.mark.parametrize("status", [
- "active", "inactive", "invalid_status"
- ])
- async def test_status_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType,
- status: str
- ):
- """测试状态验证"""
- response = await client.put(
- f"/api/v1/device-types/{test_device_type.id}",
- headers=admin_headers,
- json={"status": status}
- )
-
- # 有效状态应该是200,无效状态应该是422
- if status in ["active", "inactive"]:
- assert response.status_code == 200
- else:
- assert response.status_code in [400, 422]
-
-
-# ==================== 异常处理测试 ====================
-
-class TestDeviceTypeExceptionHandling:
- """测试异常处理"""
-
- @pytest.mark.asyncio
- async def test_concurrent_device_type_creation(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试并发创建相同代码的设备类型"""
- import asyncio
-
- data = {
- "type_code": "CONCURRENT_TEST",
- "type_name": "并发测试"
- }
-
- # 并发创建
- tasks = [
- client.post("/api/v1/device-types", headers=admin_headers, json=data)
- for _ in range(2)
- ]
- responses = await asyncio.gather(*tasks)
-
- # 应该只有一个成功,另一个失败
- success_count = sum(1 for r in responses if r.status_code == 200)
- assert success_count == 1
-
- @pytest.mark.asyncio
- async def test_update_non_existent_field(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试更新不存在的字段"""
- response = await client.put(
- f"/api/v1/device-types/{test_device_type.id}/fields/999999",
- headers=admin_headers,
- json={"field_name": "更新"}
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_delete_non_existent_device_type(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试删除不存在的设备类型"""
- response = await client.delete(
- "/api/v1/device-types/999999",
- headers=admin_headers
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_field_with_invalid_json_validation(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试字段包含无效的JSON验证规则"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test",
- "field_name": "测试",
- "field_type": "text",
- "validation_rules": "invalid json string", # 应该是对象
- "sort_order": 1
- }
- )
-
- # 应该返回验证错误
- assert response.status_code in [400, 422]
-
- @pytest.mark.asyncio
- async def test_field_with_invalid_options_format(
- self,
- client: AsyncClient,
- admin_headers: dict,
- test_device_type: DeviceType
- ):
- """测试select字段包含无效的options格式"""
- response = await client.post(
- f"/api/v1/device-types/{test_device_type.id}/fields",
- headers=admin_headers,
- json={
- "field_code": "test",
- "field_name": "测试",
- "field_type": "select",
- "options": "invalid options", # 应该是数组
- "sort_order": 1
- }
- )
-
- assert response.status_code in [400, 422]
diff --git a/tests/api/test_maintenance.py b/tests/api/test_maintenance.py
deleted file mode 100644
index 54a8f98..0000000
--- a/tests/api/test_maintenance.py
+++ /dev/null
@@ -1,891 +0,0 @@
-"""
-维修管理 API 测试
-
-测试范围:
-- 维修记录CRUD测试 (20+用例)
-- 维修状态管理测试 (15+用例)
-- 维修费用测试 (10+用例)
-- 维修历史测试 (5+用例)
-
-总计: 50+ 用例
-"""
-
-import pytest
-from datetime import datetime, timedelta
-from typing import List
-from decimal import Decimal
-from sqlalchemy.orm import Session
-
-from app.models.maintenance import Maintenance, MaintenancePart
-from app.models.asset import Asset
-from app.schemas.maintenance import (
- MaintenanceCreate,
- MaintenanceStatus,
- MaintenanceType,
- MaintenancePriority
-)
-
-
-# ================================
-# Fixtures
-# ================================
-
-@pytest.fixture
-def test_assets_for_maintenance(db: Session) -> List[Asset]:
- """创建需要维修的测试资产"""
- assets = []
- for i in range(3):
- asset = Asset(
- asset_code=f"TEST-MAINT-{i+1:03d}",
- asset_name=f"测试维修资产{i+1}",
- device_type_id=1,
- organization_id=1,
- status="maintenance",
- purchase_date=datetime.now() - timedelta(days=365)
- )
- db.add(asset)
- assets.append(asset)
- db.commit()
- for asset in assets:
- db.refresh(asset)
- return assets
-
-
-@pytest.fixture
-def test_maintenance_record(db: Session, test_assets_for_maintenance: List[Asset]) -> Maintenance:
- """创建测试维修记录"""
- maintenance = Maintenance(
- maintenance_no="MAINT-2025-001",
- asset_id=test_assets_for_maintenance[0].id,
- maintenance_type=MaintenanceType.PREVENTIVE,
- priority=MaintenancePriority.MEDIUM,
- status=MaintenanceStatus.PENDING,
- fault_description="设备异常噪音",
- reported_by=1,
- reported_time=datetime.now(),
- estimated_cost=Decimal("500.00"),
- estimated_start_time=datetime.now() + timedelta(days=1),
- estimated_completion_time=datetime.now() + timedelta(days=3)
- )
- db.add(maintenance)
- db.commit()
- db.refresh(maintenance)
- return maintenance
-
-
-@pytest.fixture
-def test_maintenance_with_parts(db: Session, test_assets_for_maintenance: List[Asset]) -> Maintenance:
- """创建包含配件的维修记录"""
- maintenance = Maintenance(
- maintenance_no="MAINT-2025-002",
- asset_id=test_assets_for_maintenance[1].id,
- maintenance_type=MaintenanceType.CORRECTIVE,
- priority=MaintenancePriority.HIGH,
- status=MaintenanceStatus.IN_PROGRESS,
- fault_description="设备故障无法启动",
- reported_by=1,
- reported_time=datetime.now(),
- actual_start_time=datetime.now(),
- estimated_cost=Decimal("1500.00")
- )
- db.add(maintenance)
- db.commit()
- db.refresh(maintenance)
-
- # 添加维修配件
- parts = [
- MaintenancePart(
- maintenance_id=maintenance.id,
- part_name="电机",
- part_code="PART-001",
- quantity=1,
- unit_price=Decimal("800.00")
- ),
- MaintenancePart(
- maintenance_id=maintenance.id,
- part_name="轴承",
- part_code="PART-002",
- quantity=2,
- unit_price=Decimal("100.00")
- )
- ]
- for part in parts:
- db.add(part)
- db.commit()
-
- return maintenance
-
-
-# ================================
-# 维修记录CRUD测试 (20+用例)
-# ================================
-
-class TestMaintenanceCRUD:
- """维修记录CRUD操作测试"""
-
- def test_create_maintenance_with_valid_data(self, client, auth_headers, test_assets_for_maintenance):
- """测试使用有效数据创建维修记录"""
- asset = test_assets_for_maintenance[0]
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "high",
- "fault_description": "设备故障需要维修",
- "reported_by": 1,
- "estimated_cost": 1000.00,
- "estimated_start_time": (datetime.now() + timedelta(hours=2)).isoformat(),
- "estimated_completion_time": (datetime.now() + timedelta(days=2)).isoformat()
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["maintenance_no"] is not None
- assert data["status"] == MaintenanceStatus.PENDING
- assert data["asset_id"] == asset.id
-
- def test_create_maintenance_with_invalid_asset_id(self, client, auth_headers):
- """测试使用无效资产ID创建维修记录应失败"""
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": 999999,
- "maintenance_type": "corrective",
- "priority": "medium",
- "fault_description": "测试",
- "reported_by": 1
- },
- headers=auth_headers
- )
- assert response.status_code == 404
- assert "资产不存在" in response.json()["detail"]
-
- def test_create_maintenance_without_fault_description(self, client, auth_headers, test_assets_for_maintenance):
- """测试创建维修记录时未提供故障描述应失败"""
- asset = test_assets_for_maintenance[0]
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "medium",
- "reported_by": 1
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "故障描述" in response.json()["detail"]
-
- def test_create_maintenance_with_negative_cost(self, client, auth_headers, test_assets_for_maintenance):
- """测试创建负费用的维修记录应失败"""
- asset = test_assets_for_maintenance[0]
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "medium",
- "fault_description": "测试",
- "reported_by": 1,
- "estimated_cost": -100.00
- },
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_create_maintenance_auto_updates_asset_status(self, client, auth_headers, db: Session, test_assets_for_maintenance):
- """测试创建维修记录时自动更新资产状态"""
- asset = test_assets_for_maintenance[0]
- original_status = asset.status
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "medium",
- "fault_description": "测试自动更新状态",
- "reported_by": 1
- },
- headers=auth_headers
- )
- assert response.status_code == 200
-
- # 验证资产状态已更新
- db.refresh(asset)
- assert asset.status == "maintenance"
-
- def test_get_maintenance_list_with_pagination(self, client, auth_headers, test_maintenance_record):
- """测试分页获取维修记录列表"""
- response = client.get(
- "/api/v1/maintenance/?page=1&page_size=10",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "items" in data
- assert "total" in data
- assert len(data["items"]) >= 1
-
- def test_get_maintenance_list_with_status_filter(self, client, auth_headers, test_maintenance_record):
- """测试按状态筛选维修记录"""
- response = client.get(
- f"/api/v1/maintenance/?status={MaintenanceStatus.PENDING}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- for item in data["items"]:
- assert item["status"] == MaintenanceStatus.PENDING
-
- def test_get_maintenance_list_with_asset_filter(self, client, auth_headers, test_maintenance_record):
- """测试按资产筛选维修记录"""
- response = client.get(
- f"/api/v1/maintenance/?asset_id={test_maintenance_record.asset_id}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data["items"]) >= 1
-
- def test_get_maintenance_list_with_type_filter(self, client, auth_headers, test_maintenance_record):
- """测试按维修类型筛选"""
- response = client.get(
- f"/api/v1/maintenance/?maintenance_type={test_maintenance_record.maintenance_type}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_list_with_priority_filter(self, client, auth_headers, test_maintenance_record):
- """测试按优先级筛选"""
- response = client.get(
- f"/api/v1/maintenance/?priority={test_maintenance_record.priority}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_list_with_date_range(self, client, auth_headers, test_maintenance_record):
- """测试按日期范围筛选"""
- start_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
- end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
-
- response = client.get(
- f"/api/v1/maintenance/?start_date={start_date}&end_date={end_date}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_by_id(self, client, auth_headers, test_maintenance_record):
- """测试通过ID获取维修记录详情"""
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["id"] == test_maintenance_record.id
- assert data["maintenance_no"] == test_maintenance_record.maintenance_no
- assert "asset" in data
-
- def test_get_maintenance_by_invalid_id(self, client, auth_headers):
- """测试通过无效ID获取维修记录应返回404"""
- response = client.get(
- "/api/v1/maintenance/999999",
- headers=auth_headers
- )
- assert response.status_code == 404
-
- def test_update_maintenance_fault_description(self, client, auth_headers, test_maintenance_record):
- """测试更新故障描述"""
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- json={"fault_description": "更新后的故障描述"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["fault_description"] == "更新后的故障描述"
-
- def test_update_maintenance_priority(self, client, auth_headers, test_maintenance_record):
- """测试更新优先级"""
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- json={"priority": "urgent"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["priority"] == MaintenancePriority.URGENT
-
- def test_update_maintenance_after_start_should_fail(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试维修开始后更新某些字段应失败"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- json={"maintenance_type": "preventive"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许修改" in response.json()["detail"]
-
- def test_delete_pending_maintenance(self, client, auth_headers, db: Session, test_assets_for_maintenance):
- """测试删除待处理的维修记录"""
- maintenance = Maintenance(
- maintenance_no="MAINT-DEL-001",
- asset_id=test_assets_for_maintenance[0].id,
- maintenance_type=MaintenanceType.CORRECTIVE,
- priority=MaintenancePriority.MEDIUM,
- status=MaintenanceStatus.PENDING,
- fault_description="待删除",
- reported_by=1
- )
- db.add(maintenance)
- db.commit()
- db.refresh(maintenance)
-
- response = client.delete(
- f"/api/v1/maintenance/{maintenance.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_delete_in_progress_maintenance_should_fail(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试删除进行中的维修记录应失败"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.delete(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许删除" in response.json()["detail"]
-
- def test_create_maintenance_with_parts(self, client, auth_headers, test_assets_for_maintenance):
- """测试创建包含配件的维修记录"""
- asset = test_assets_for_maintenance[0]
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "high",
- "fault_description": "需要更换配件",
- "reported_by": 1,
- "parts": [
- {
- "part_name": "电机",
- "part_code": "PART-001",
- "quantity": 1,
- "unit_price": 800.00
- },
- {
- "part_name": "轴承",
- "part_code": "PART-002",
- "quantity": 2,
- "unit_price": 100.00
- }
- ]
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "parts" in data
- assert len(data["parts"]) == 2
-
-
-# ================================
-# 维修状态管理测试 (15+用例)
-# ================================
-
-class TestMaintenanceStatusManagement:
- """维修状态管理测试"""
-
- def test_start_maintenance(self, client, auth_headers, test_maintenance_record):
- """测试开始维修"""
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/start",
- json={"start_note": "开始维修", "technician_id": 2},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == MaintenanceStatus.IN_PROGRESS
- assert data["actual_start_time"] is not None
-
- def test_start_maintenance_updates_asset_status(self, client, auth_headers, test_maintenance_record, db: Session):
- """测试开始维修时更新资产状态"""
- client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/start",
- json={"start_note": "开始维修"},
- headers=auth_headers
- )
-
- asset = db.query(Asset).filter(Asset.id == test_maintenance_record.asset_id).first()
- assert asset.status == "maintenance"
-
- def test_pause_maintenance(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试暂停维修"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/pause",
- json={"pause_reason": "等待配件"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == MaintenanceStatus.PAUSED
-
- def test_resume_maintenance(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试恢复维修"""
- test_maintenance_record.status = MaintenanceStatus.PAUSED
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/resume",
- json={"resume_note": "配件已到,继续维修"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == MaintenanceStatus.IN_PROGRESS
-
- def test_complete_maintenance(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试完成维修"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/complete",
- json={
- "completion_note": "维修完成",
- "actual_cost": 1200.00,
- "technician_id": 2
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == MaintenanceStatus.COMPLETED
- assert data["actual_completion_time"] is not None
- assert data["actual_cost"] == 1200.00
-
- def test_complete_maintenance_updates_asset_status(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试完成维修后恢复资产状态"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/complete",
- json={"completion_note": "完成", "actual_cost": 1000.00},
- headers=auth_headers
- )
-
- asset = db.query(Asset).filter(Asset.id == test_maintenance_record.asset_id).first()
- assert asset.status == "in_stock"
-
- def test_cancel_maintenance(self, client, auth_headers, test_maintenance_record):
- """测试取消维修"""
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/cancel",
- json={"cancellation_reason": "资产报废"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == MaintenanceStatus.CANCELLED
-
- def test_cancel_maintenance_updates_asset_status(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试取消维修后恢复资产状态"""
- client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/cancel",
- json={"cancellation_reason": "取消维修"},
- headers=auth_headers
- )
-
- asset = db.query(Asset).filter(Asset.id == test_maintenance_record.asset_id).first()
- assert asset.status == "in_stock"
-
- def test_assign_technician(self, client, auth_headers, test_maintenance_record):
- """测试分配维修人员"""
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/assign-technician",
- json={"technician_id": 2, "assignment_note": "指派张工负责"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["technician_id"] == 2
-
- def test_add_maintenance_progress_note(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试添加维修进度备注"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/progress-notes",
- json={"note": "已更换故障配件", "progress_percentage": 50},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_progress_notes(self, client, auth_headers, test_maintenance_record):
- """测试获取维修进度备注"""
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_record.id}/progress-notes",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_update_maintenance_progress(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试更新维修进度"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_record.id}/progress",
- json={"progress_percentage": 75},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["progress_percentage"] == 75
-
- def test_invalid_status_transition(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试无效的状态转换"""
- test_maintenance_record.status = MaintenanceStatus.COMPLETED
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/start",
- json={"start_note": "尝试重新开始"},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_get_maintenance_status_history(self, client, auth_headers, test_maintenance_record):
- """测试获取状态变更历史"""
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_record.id}/status-history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_auto_calculate_duration(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试自动计算维修时长"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- test_maintenance_record.actual_start_time = datetime.now() - timedelta(days=2)
- db.commit()
-
- client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/complete",
- json={"completion_note": "完成", "actual_cost": 1000.00},
- headers=auth_headers
- )
-
- db.refresh(test_maintenance_record)
- assert test_maintenance_record.duration_hours is not None
-
-
-# ================================
-# 维修费用测试 (10+用例)
-# ================================
-
-class TestMaintenanceCost:
- """维修费用测试"""
-
- def test_record_initial_cost_estimate(self, client, auth_headers, test_assets_for_maintenance):
- """测试记录初始费用估算"""
- asset = test_assets_for_maintenance[0]
-
- response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "medium",
- "fault_description": "测试费用估算",
- "reported_by": 1,
- "estimated_cost": 2000.00
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["estimated_cost"] == 2000.00
-
- def test_update_cost_estimate(self, client, auth_headers, test_maintenance_record):
- """测试更新费用估算"""
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_record.id}",
- json={"estimated_cost": 800.00},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["estimated_cost"] == 800.00
-
- def test_record_actual_cost(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试记录实际费用"""
- test_maintenance_record.status = MaintenanceStatus.IN_PROGRESS
- db.commit()
-
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/record-cost",
- json={"actual_cost": 1500.00, "cost_breakdown": {"parts": 1000.00, "labor": 500.00}},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["actual_cost"] == 1500.00
-
- def test_calculate_total_parts_cost(self, client, auth_headers, test_maintenance_with_parts):
- """测试计算配件总费用"""
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_with_parts.id}/parts-cost",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["total_parts_cost"] == 1000.00 # 800 + 100*2
-
- def test_add_maintenance_part(self, client, auth_headers, test_maintenance_record):
- """测试添加维修配件"""
- response = client.post(
- f"/api/v1/maintenance/{test_maintenance_record.id}/parts",
- json={
- "part_name": "传感器",
- "part_code": "PART-003",
- "quantity": 1,
- "unit_price": 300.00
- },
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_update_maintenance_part(self, client, auth_headers, test_maintenance_with_parts):
- """测试更新维修配件"""
- part = test_maintenance_with_parts.parts[0]
-
- response = client.put(
- f"/api/v1/maintenance/{test_maintenance_with_parts.id}/parts/{part.id}",
- json={"quantity": 2, "unit_price": 750.00},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_delete_maintenance_part(self, client, auth_headers, test_maintenance_with_parts):
- """测试删除维修配件"""
- part = test_maintenance_with_parts.parts[0]
-
- response = client.delete(
- f"/api/v1/maintenance/{test_maintenance_with_parts.id}/parts/{part.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_parts_list(self, client, auth_headers, test_maintenance_with_parts):
- """测试获取维修配件列表"""
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_with_parts.id}/parts",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data) == 2
-
- def test_cost_variance_analysis(self, client, auth_headers, db: Session, test_maintenance_record):
- """测试费用差异分析"""
- test_maintenance_record.estimated_cost = Decimal("1000.00")
- test_maintenance_record.actual_cost = Decimal("1200.00")
- db.commit()
-
- response = client.get(
- f"/api/v1/maintenance/{test_maintenance_record.id}/cost-analysis",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "variance" in data
- assert "variance_percentage" in data
-
- def test_get_cost_statistics_by_asset(self, client, auth_headers, test_assets_for_maintenance):
- """测试获取资产维修费用统计"""
- asset = test_assets_for_maintenance[0]
-
- response = client.get(
- f"/api/v1/maintenance/asset/{asset.id}/cost-statistics",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_cost" in data
- assert "maintenance_count" in data
-
-
-# ================================
-# 维修历史测试 (5+用例)
-# ================================
-
-class TestMaintenanceHistory:
- """维修历史测试"""
-
- def test_get_asset_maintenance_history(self, client, auth_headers, test_maintenance_record):
- """测试获取资产维修历史"""
- response = client.get(
- f"/api/v1/maintenance/asset/{test_maintenance_record.asset_id}/history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert len(data) >= 1
-
- def test_get_maintenance_history_with_date_range(self, client, auth_headers, test_maintenance_record):
- """测试按日期范围获取维修历史"""
- start_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
- end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
-
- response = client.get(
- f"/api/v1/maintenance/asset/{test_maintenance_record.asset_id}/history?start_date={start_date}&end_date={end_date}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_maintenance_frequency_analysis(self, client, auth_headers, test_assets_for_maintenance):
- """测试获取维修频率分析"""
- asset = test_assets_for_maintenance[0]
-
- response = client.get(
- f"/api/v1/maintenance/asset/{asset.id}/frequency-analysis",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_maintenance_count" in data
- assert "average_days_between_maintenance" in data
-
- def test_export_maintenance_history(self, client, auth_headers, test_maintenance_record):
- """测试导出维修历史"""
- response = client.get(
- f"/api/v1/maintenance/asset/{test_maintenance_record.asset_id}/export",
- headers=auth_headers
- )
- assert response.status_code == 200
- assert "export_url" in response.json()
-
- def test_get_maintenance_summary_report(self, client, auth_headers):
- """测试获取维修汇总报告"""
- response = client.get(
- "/api/v1/maintenance/summary-report",
- headers=auth_headers,
- params={"start_date": "2025-01-01", "end_date": "2025-12-31"}
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_maintenance_count" in data
- assert "total_cost" in data
- assert "by_type" in data
-
-
-# ================================
-# 测试标记
-# ================================
-
-@pytest.mark.unit
-class TestMaintenanceUnit:
- """单元测试标记"""
-
- def test_maintenance_number_generation(self):
- """测试维修单号生成逻辑"""
- pass
-
- def test_maintenance_type_validation(self):
- """测试维修类型验证"""
- pass
-
-
-@pytest.mark.integration
-class TestMaintenanceIntegration:
- """集成测试标记"""
-
- def test_full_maintenance_workflow(self, client, auth_headers, test_assets_for_maintenance):
- """测试完整维修流程"""
- asset = test_assets_for_maintenance[0]
-
- # 1. 创建维修记录
- create_response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "high",
- "fault_description": "完整流程测试",
- "reported_by": 1,
- "estimated_cost": 1000.00
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
- maintenance_id = create_response.json()["id"]
-
- # 2. 开始维修
- start_response = client.post(
- f"/api/v1/maintenance/{maintenance_id}/start",
- json={"start_note": "开始"},
- headers=auth_headers
- )
- assert start_response.status_code == 200
-
- # 3. 完成维修
- complete_response = client.post(
- f"/api/v1/maintenance/{maintenance_id}/complete",
- json={"completion_note": "完成", "actual_cost": 1200.00},
- headers=auth_headers
- )
- assert complete_response.status_code == 200
-
-
-@pytest.mark.smoke
-class TestMaintenanceSmoke:
- """冒烟测试标记"""
-
- def test_create_and_start_maintenance(self, client, auth_headers, test_assets_for_maintenance):
- """冒烟测试: 创建并开始维修"""
- asset = test_assets_for_maintenance[0]
-
- create_response = client.post(
- "/api/v1/maintenance/",
- json={
- "asset_id": asset.id,
- "maintenance_type": "corrective",
- "priority": "medium",
- "fault_description": "冒烟测试",
- "reported_by": 1
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
-
- maintenance_id = create_response.json()["id"]
- start_response = client.post(
- f"/api/v1/maintenance/{maintenance_id}/start",
- json={"start_note": "冒烟测试开始"},
- headers=auth_headers
- )
- assert start_response.status_code == 200
diff --git a/tests/api/test_organizations.py b/tests/api/test_organizations.py
deleted file mode 100644
index e34b1aa..0000000
--- a/tests/api/test_organizations.py
+++ /dev/null
@@ -1,1547 +0,0 @@
-"""
-机构网点管理模块API测试
-
-测试内容:
-- 机构CRUD测试(15+用例)
-- 树形结构测试(10+用例)
-- 递归查询测试(10+用例)
-- 机构移动测试(5+用例)
-- 并发测试(5+用例)
-"""
-
-import pytest
-from httpx import AsyncClient
-from datetime import datetime
-
-
-# ==================== 机构CRUD测试 ====================
-
-class TestOrganizationCRUD:
- """测试机构CRUD操作"""
-
- @pytest.mark.asyncio
- async def test_create_organization_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试创建机构成功"""
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "ORG001",
- "org_name": "广东省",
- "org_type": "province",
- "description": "广东省分公司"
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert data["data"]["org_code"] == "ORG001"
- assert data["data"]["org_name"] == "广东省"
- assert "id" in data["data"]
-
- @pytest.mark.asyncio
- async def test_create_child_organization(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试创建子机构"""
- # 先创建父机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "PARENT_ORG",
- "org_name": "父机构",
- "org_type": "province"
- }
- )
- parent_id = parent_response.json()["data"]["id"]
-
- # 创建子机构
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "CHILD_ORG",
- "org_name": "子机构",
- "org_type": "city",
- "parent_id": parent_id
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["parent_id"] == parent_id
-
- @pytest.mark.asyncio
- async def test_create_organization_duplicate_code(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试创建重复代码的机构"""
- data = {
- "org_code": "DUP_ORG",
- "org_name": "测试机构"
- }
-
- # 第一次创建
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json=data
- )
-
- # 第二次创建相同代码
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json=data
- )
-
- assert response.status_code in [400, 409]
-
- @pytest.mark.asyncio
- async def test_get_organization_list(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取机构列表"""
- # 先创建几个机构
- for i in range(3):
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": f"ORG{i}",
- "org_name": f"机构{i}",
- "org_type": "province"
- }
- )
-
- response = await client.get(
- "/api/v1/organizations",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- assert len(data["data"]) >= 3
-
- @pytest.mark.asyncio
- async def test_get_organization_by_id(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试根据ID获取机构"""
- # 创建机构
- create_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "GET_ORG",
- "org_name": "获取测试机构",
- "org_type": "province"
- }
- )
- org_id = create_response.json()["data"]["id"]
-
- # 获取机构
- response = await client.get(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["data"]["id"] == org_id
- assert data["data"]["org_code"] == "GET_ORG"
-
- @pytest.mark.asyncio
- async def test_update_organization_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试更新机构成功"""
- # 创建机构
- create_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "UPDATE_ORG",
- "org_name": "原始名称",
- "org_type": "province"
- }
- )
- org_id = create_response.json()["data"]["id"]
-
- # 更新机构
- response = await client.put(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers,
- json={
- "org_name": "更新后的名称",
- "description": "更新后的描述"
- }
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
-
- @pytest.mark.asyncio
- async def test_update_organization_parent(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试更新机构的父机构"""
- # 创建两个机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "NEW_PARENT",
- "org_name": "新父机构",
- "org_type": "province"
- }
- )
- parent_id = parent_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "CHILD",
- "org_name": "子机构",
- "org_type": "city"
- }
- )
- child_id = child_response.json()["data"]["id"]
-
- # 更新子机构的父机构
- response = await client.put(
- f"/api/v1/organizations/{child_id}",
- headers=admin_headers,
- json={"parent_id": parent_id}
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_delete_organization_success(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试删除机构成功"""
- # 创建机构
- create_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "DELETE_ORG",
- "org_name": "待删除机构",
- "org_type": "province"
- }
- )
- org_id = create_response.json()["data"]["id"]
-
- # 删除机构
- response = await client.delete(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- # 验证删除
- get_response = await client.get(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers
- )
- assert get_response.status_code in [404, 200] # 软删除可能返回200
-
- @pytest.mark.asyncio
- async def test_delete_organization_with_children_forbidden(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试删除有子机构的机构(应该失败)"""
- # 创建父机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "PARENT_WITH_CHILD",
- "org_name": "父机构",
- "org_type": "province"
- }
- )
- parent_id = parent_response.json()["data"]["id"]
-
- # 创建子机构
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "CHILD_ORG",
- "org_name": "子机构",
- "org_type": "city",
- "parent_id": parent_id
- }
- )
-
- # 尝试删除父机构
- response = await client.delete(
- f"/api/v1/organizations/{parent_id}",
- headers=admin_headers
- )
-
- # 应该失败或返回错误
- assert response.status_code in [400, 403]
-
- @pytest.mark.asyncio
- async def test_filter_organization_by_type(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试按类型筛选机构"""
- # 创建不同类型的机构
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={"org_code": "PROV1", "org_name": "省级", "org_type": "province"}
- )
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={"org_code": "CITY1", "org_name": "市级", "org_type": "city"}
- )
-
- # 筛选省级机构
- response = await client.get(
- "/api/v1/organizations?org_type=province",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 验证筛选结果
- # for org in data["data"]:
- # assert org["org_type"] == "province"
-
- @pytest.mark.asyncio
- async def test_filter_organization_by_status(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试按状态筛选机构"""
- response = await client.get(
- "/api/v1/organizations?status=active",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_search_organization_by_keyword(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试关键词搜索机构"""
- # 创建机构
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "SEARCH_ORG",
- "org_name": "搜索测试机构",
- "org_type": "province"
- }
- )
-
- # 搜索
- response = await client.get(
- "/api/v1/organizations?keyword=搜索",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 验证搜索结果包含关键词
-
- @pytest.mark.asyncio
- async def test_get_organization_not_found(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试获取不存在的机构"""
- response = await client.get(
- "/api/v1/organizations/999999",
- headers=admin_headers
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_update_organization_not_found(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试更新不存在的机构"""
- response = await client.put(
- "/api/v1/organizations/999999",
- headers=admin_headers,
- json={"org_name": "新名称"}
- )
-
- assert response.status_code == 404
-
- @pytest.mark.asyncio
- async def test_create_organization_unauthorized(
- self,
- client: AsyncClient
- ):
- """测试未授权创建机构"""
- response = await client.post(
- "/api/v1/organizations",
- json={
- "org_code": "NO_AUTH",
- "org_name": "未授权"
- }
- )
-
- assert response.status_code == 401
-
-
-# ==================== 树形结构测试 ====================
-
-class TestOrganizationTree:
- """测试机构树形结构"""
-
- @pytest.mark.asyncio
- async def test_get_organization_tree(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取机构树"""
- # 创建三级机构
- province_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "TREE_PROV",
- "org_name": "广东省",
- "org_type": "province"
- }
- )
- province_id = province_response.json()["data"]["id"]
-
- city_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "TREE_CITY",
- "org_name": "广州市",
- "org_type": "city",
- "parent_id": province_id
- }
- )
- city_id = city_response.json()["data"]["id"]
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "TREE_OUTLET",
- "org_name": "天河网点",
- "org_type": "outlet",
- "parent_id": city_id
- }
- )
-
- # 获取树形结构
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- assert data["code"] == 200
- # 验证树形结构
- # assert len(data["data"]) > 0
- # assert "children" in data["data"][0]
-
- @pytest.mark.asyncio
- async def test_tree_root_level(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试树的根层级"""
- # 创建顶级机构
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "ROOT_ORG",
- "org_name": "根机构",
- "org_type": "province"
- }
- )
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 根级机构的parent_id应该为null
- # for org in data["data"]:
- # assert org["parent_id"] is None
-
- @pytest.mark.asyncio
- async def test_tree_max_levels(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试树的最大层级"""
- # 创建多层级机构
- parent_id = None
- for i in range(5):
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": f"LEVEL{i}",
- "org_name": f"第{i}层",
- "org_type": "outlet",
- "parent_id": parent_id
- }
- )
- parent_id = response.json()["data"]["id"]
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_tree_multiple_branches(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试多分支树结构"""
- # 创建根机构
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "MULTI_ROOT",
- "org_name": "多分支根",
- "org_type": "province"
- }
- )
- root_id = root_response.json()["data"]["id"]
-
- # 创建多个子分支
- for i in range(3):
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": f"BRANCH{i}",
- "org_name": f"分支{i}",
- "org_type": "city",
- "parent_id": root_id
- }
- )
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 验证有多个子节点
- # assert len(data["data"][0]["children"]) == 3
-
- @pytest.mark.asyncio
- async def test_tree_leaf_nodes(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试树的叶子节点"""
- # 创建有子节点和无子节点的机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "PARENT_NODE",
- "org_name": "父节点",
- "org_type": "province"
- }
- )
- parent_id = parent_response.json()["data"]["id"]
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "CHILD_NODE",
- "org_name": "子节点",
- "org_type": "city",
- "parent_id": parent_id
- }
- )
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "LEAF_NODE",
- "org_name": "叶子节点",
- "org_type": "city"
- }
- )
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_tree_with_inactive_orgs(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试包含已删除机构的树"""
- # 创建机构
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "INACTIVE_ORG",
- "org_name": "未激活机构",
- "org_type": "province"
- }
- )
- org_id = response.json()["data"]["id"]
-
- # 停用机构
- await client.put(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers,
- json={"status": "inactive"}
- )
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- # 验证树中是否包含未激活机构
-
- @pytest.mark.asyncio
- async def test_tree_sorting(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试树的排序"""
- # 创建根机构
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "SORT_ROOT",
- "org_name": "排序根",
- "org_type": "province"
- }
- )
- root_id = root_response.json()["data"]["id"]
-
- # 创建不同排序的子机构
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "SECOND",
- "org_name": "第二",
- "org_type": "city",
- "parent_id": root_id,
- "sort_order": 2
- }
- )
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json={
- "org_code": "FIRST",
- "org_name": "第一",
- "org_type": "city",
- "parent_id": root_id,
- "sort_order": 1
- }
- )
-
- response = await client.get(
- "/api/v1/organizations/tree",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_tree_depth_limit(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试树的深度限制"""
- response = await client.get(
- "/api/v1/organizations/tree?max_depth=3",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_flatten_tree_to_list(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试将树展平为列表"""
- response = await client.get(
- "/api/v1/organizations?format=flat",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
-
-# ==================== 递归查询测试 ====================
-
-class TestOrganizationRecursiveQuery:
- """测试机构递归查询"""
-
- @pytest.mark.asyncio
- async def test_get_all_children(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取所有子机构"""
- # 创建多级机构
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "ROOT",
- "org_name": "根",
- "org_type": "province"
- })
- )
- root_id = root_response.json()["data"]["id"]
-
- child1_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "CHILD1",
- "org_name": "子1",
- "org_type": "city",
- "parent_id": root_id
- })
- )
- child1_id = child1_response.json()["data"]["id"]
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "GRANDCHILD1",
- "org_name": "孙1",
- "org_type": "outlet",
- "parent_id": child1_id
- })
- )
-
- # 获取所有子机构
- response = await client.get(
- f"/api/v1/organizations/{root_id}/children",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- # 应该包含所有子孙机构
-
- @pytest.mark.asyncio
- async def test_get_all_parents(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取所有父机构路径"""
- # 创建三级机构
- grand_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "GRAND",
- "org_name": "祖父",
- "org_type": "province"
- })
- )
- grand_id = grand_response.json()["data"]["id"]
-
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "PARENT",
- "org_name": "父",
- "org_type": "city",
- "parent_id": grand_id
- })
- )
- parent_id = parent_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "CHILD_PATH",
- "org_name": "子",
- "org_type": "outlet",
- "parent_id": parent_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 获取父机构路径
- response = await client.get(
- f"/api/v1/organizations/{child_id}/parents",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 应该包含完整的父机构路径
- # assert len(data["data"]) == 2
-
- @pytest.mark.asyncio
- async def test_count_descendants(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试统计子孙机构数量"""
- # 创建机构树
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "COUNT_ROOT",
- "org_name": "统计根",
- "org_type": "province"
- })
- )
- root_id = root_response.json()["data"]["id"]
-
- # 创建多个子机构
- for i in range(3):
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": f"COUNT_CHILD{i}",
- "org_name": f"子{i}",
- "org_type": "city",
- "parent_id": root_id
- })
- )
-
- response = await client.get(
- f"/api/v1/organizations/{root_id}/descendants/count",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- # data = response.json()
- # assert data["data"]["count"] >= 3
-
- @pytest.mark.asyncio
- async def test_get_organization_level(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取机构层级"""
- # 创建三级机构
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "LEVEL_ROOT",
- "org_name": "层级根",
- "org_type": "province"
- })
- )
- root_id = root_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "LEVEL_CHILD",
- "org_name": "层级子",
- "org_type": "city",
- "parent_id": root_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- grandchild_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "LEVEL_GRAND",
- "org_name": "层级孙",
- "org_type": "outlet",
- "parent_id": child_id
- })
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_get_siblings(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取兄弟机构"""
- # 创建父机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "SIB_PARENT",
- "org_name": "兄弟父",
- "org_type": "province"
- })
- )
- parent_id = parent_response.json()["data"]["id"]
-
- # 创建多个子机构
- for i in range(3):
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": f"SIB{i}",
- "org_name": f"兄弟{i}",
- "org_type": "city",
- "parent_id": parent_id
- })
- )
-
- response = await client.get(
- f"/api/v1/organizations/{parent_id}/children",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_recursive_search(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试递归搜索机构"""
- response = await client.get(
- "/api/v1/organizations/search?keyword=测试&recursive=true",
- headers=admin_headers
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_get_org_tree_statistics(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取机构树统计信息"""
- response = await client.get(
- "/api/v1/organizations/statistics",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 应该包含机构总数、层级数等统计信息
-
- @pytest.mark.asyncio
- async def test_recursive_delete_check(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试递归删除检查"""
- # 创建有子机构的机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "DEL_CHECK",
- "org_name": "删除检查",
- "org_type": "province"
- })
- )
- parent_id = parent_response.json()["data"]["id"]
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "DEL_CHILD",
- "org_name": "删除子",
- "org_type": "city",
- "parent_id": parent_id
- })
- )
-
- # 检查是否可以删除
- response = await client.get(
- f"/api/v1/organizations/{parent_id}/can-delete",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- # data = response.json()
- # assert data["data"]["can_delete"] is False
-
- @pytest.mark.asyncio
- async def test_get_org_full_path(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试获取机构完整路径"""
- # 创建三级机构
- grand_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "PATH_GRAND",
- "org_name": "路径祖父",
- "org_type": "province"
- })
- )
- grand_id = grand_response.json()["data"]["id"]
-
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "PATH_PARENT",
- "org_name": "路径父",
- "org_type": "city",
- "parent_id": grand_id
- })
- )
- parent_id = parent_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "PATH_CHILD",
- "org_name": "路径子",
- "org_type": "outlet",
- "parent_id": parent_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 获取完整路径
- response = await client.get(
- f"/api/v1/organizations/{child_id}/path",
- headers=admin_headers
- )
-
- assert response.status_code == 200
- data = response.json()
- # 应该返回: 祖父 > 父 > 子
-
-
-# ==================== 机构移动测试 ====================
-
-class TestOrganizationMove:
- """测试机构移动"""
-
- @pytest.mark.asyncio
- async def test_move_organization_to_new_parent(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试移动机构到新的父机构"""
- # 创建两个父机构
- parent1_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_PARENT1",
- "org_name": "移动父1",
- "org_type": "province"
- })
- )
- parent1_id = parent1_response.json()["data"]["id"]
-
- parent2_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_PARENT2",
- "org_name": "移动父2",
- "org_type": "province"
- })
- )
- parent2_id = parent2_response.json()["data"]["id"]
-
- # 创建子机构
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_CHILD",
- "org_name": "移动子",
- "org_type": "city",
- "parent_id": parent1_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 移动到新的父机构
- response = await client.put(
- f"/api/v1/organizations/{child_id}/move",
- headers=admin_headers,
- json({"new_parent_id": parent2_id})
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_move_organization_to_root(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试移动机构到根级别"""
- # 创建父机构和子机构
- parent_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "TO_ROOT_PARENT",
- "org_name": "根父",
- "org_type": "province"
- })
- )
- parent_id = parent_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "TO_ROOT_CHILD",
- "org_name": "根子",
- "org_type": "city",
- "parent_id": parent_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 移动到根级别
- response = await client.put(
- f"/api/v1/organizations/{child_id}/move",
- headers=admin_headers,
- json({"new_parent_id": None})
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_move_organization_with_children(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试移动有子机构的机构"""
- # 创建机构树
- root1_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_ROOT1",
- "org_name": "移动根1",
- "org_type": "province"
- })
- )
- root1_id = root1_response.json()["data"]["id"]
-
- branch_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_BRANCH",
- "org_name": "移动分支",
- "org_type": "city",
- "parent_id": root1_id
- })
- )
- branch_id = branch_response.json()["data"]["id"]
-
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_LEAF",
- "org_name": "移动叶子",
- "org_type": "outlet",
- "parent_id": branch_id
- })
- )
-
- root2_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_ROOT2",
- "org_name": "移动根2",
- "org_type": "province"
- })
- )
- root2_id = root2_response.json()["data"]["id"]
-
- # 移动分支(包括其子机构)
- response = await client.put(
- f"/api/v1/organizations/{branch_id}/move",
- headers=admin_headers,
- json({"new_parent_id": root2_id})
- )
-
- assert response.status_code == 200
-
- @pytest.mark.asyncio
- async def test_move_to_own_child_forbidden(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试禁止移动到自己的子机构"""
- # 创建三级机构
- root_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "CYCLE_ROOT",
- "org_name": "循环根",
- "org_type": "province"
- })
- )
- root_id = root_response.json()["data"]["id"]
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "CYCLE_CHILD",
- "org_name": "循环子",
- "org_type": "city",
- "parent_id": root_id
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 尝试将根机构移动到子机构下(应该失败)
- response = await client.put(
- f"/api/v1/organizations/{root_id}/move",
- headers=admin_headers,
- json({"new_parent_id": child_id})
- )
-
- assert response.status_code in [400, 403]
-
- @pytest.mark.asyncio
- async def test_move_non_existent_org(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试移动不存在的机构"""
- response = await client.put(
- "/api/v1/organizations/999999/move",
- headers=admin_headers,
- json({"new_parent_id": None})
- )
-
- assert response.status_code == 404
-
-
-# ==================== 并发测试 ====================
-
-class TestOrganizationConcurrency:
- """测试机构并发操作"""
-
- @pytest.mark.asyncio
- async def test_concurrent_create_same_code(
- self,
- client: AsyncClient,
- admin_headers: dict
- ):
- """测试并发创建相同代码的机构"""
- import asyncio
-
- data = {
- "org_code": "CONCURRENT_ORG",
- "org_name": "并发机构"
- }
-
- # 并发创建
- tasks = [
- client.post("/api/v1/organizations", headers=admin_headers, json=data)
- for _ in range(2)
- ]
- responses = await asyncio.gather(*tasks)
-
- # 应该只有一个成功
- success_count = sum(1 for r in responses if r.status_code == 200)
- assert success_count == 1
-
- @pytest.mark.asyncio
- async def test_concurrent_update(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试并发更新机构"""
- import asyncio
-
- # 创建机构
- create_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "CONCURRENT_UPDATE",
- "org_name": "并发更新",
- "org_type": "province"
- })
- )
- org_id = create_response.json()["data"]["id"]
-
- # 并发更新
- tasks = [
- client.put(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers,
- json={"org_name": f"名称{i}"}
- )
- for i in range(5)
- ]
- responses = await asyncio.gather(*tasks)
-
- # 所有更新都应该成功
- assert all(r.status_code == 200 for r in responses)
-
- @pytest.mark.asyncio
- async def test_concurrent_move_operations(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试并发移动操作"""
- import asyncio
-
- # 创建多个机构
- parent_ids = []
- for i in range(3):
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": f"MOVE_PARENT{i}",
- "org_name": f"移动父{i}",
- "org_type": "province"
- })
- )
- parent_ids.append(response.json()["data"]["id"])
-
- child_response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "MOVE_TARGET",
- "org_name": "移动目标",
- "org_type": "city",
- "parent_id": parent_ids[0]
- })
- )
- child_id = child_response.json()["data"]["id"]
-
- # 并发移动到不同的父机构
- tasks = [
- client.put(
- f"/api/v1/organizations/{child_id}/move",
- headers=admin_headers,
- json({"new_parent_id": parent_id})
- )
- for parent_id in parent_ids
- ]
- responses = await asyncio.gather(*tasks)
-
- # 只有一个成功
- success_count = sum(1 for r in responses if r.status_code == 200)
- assert success_count >= 1
-
- @pytest.mark.asyncio
- async def test_concurrent_delete_and_move(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试并发删除和移动"""
- import asyncio
-
- # 创建机构
- response = await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": "DELETE_MOVE_ORG",
- "org_name": "删除移动",
- "org_type": "province"
- })
- )
- org_id = response.json()["data"]["id"]
-
- # 并发删除和移动
- delete_task = client.delete(
- f"/api/v1/organizations/{org_id}",
- headers=admin_headers
- )
- move_task = client.put(
- f"/api/v1/organizations/{org_id}/move",
- headers=admin_headers,
- json({"new_parent_id": None})
- )
-
- responses = await asyncio.gather(delete_task, move_task)
-
- # 至少一个操作失败
- assert any(r.status_code != 200 for r in responses)
-
- @pytest.mark.asyncio
- async def test_concurrent_tree_queries(
- self,
- client: AsyncClient,
- admin_headers: dict,
- db_session
- ):
- """测试并发查询树结构"""
- import asyncio
-
- # 创建一些机构
- for i in range(5):
- await client.post(
- "/api/v1/organizations",
- headers=admin_headers,
- json({
- "org_code": f"QUERY_ORG{i}",
- "org_name": f"查询机构{i}",
- "org_type": "province"
- })
- )
-
- # 并发查询树
- tasks = [
- client.get("/api/v1/organizations/tree", headers=admin_headers)
- for _ in range(10)
- ]
- responses = await asyncio.gather(*tasks)
-
- # 所有查询都应该成功
- assert all(r.status_code == 200 for r in responses)
diff --git a/tests/api/test_statistics.py b/tests/api/test_statistics.py
deleted file mode 100644
index 7225bf2..0000000
--- a/tests/api/test_statistics.py
+++ /dev/null
@@ -1,912 +0,0 @@
-"""
-统计分析 API 测试
-
-测试范围:
-- 资产统计测试 (20+用例)
-- 分布统计测试 (15+用例)
-- 趋势统计测试 (10+用例)
-- 缓存测试 (10+用例)
-- 导出测试 (5+用例)
-
-总计: 60+ 用例
-"""
-
-import pytest
-from datetime import datetime, timedelta
-from decimal import Decimal
-from sqlalchemy.orm import Session
-
-from app.models.asset import Asset
-from app.models.organization import Organization
-from app.models.maintenance import Maintenance
-
-
-# ================================
-# Fixtures
-# ================================
-
-@pytest.fixture
-def test_assets_for_statistics(db: Session) -> list:
- """创建用于统计的测试资产"""
- assets = []
-
- # 不同状态的资产
- statuses = ["in_stock", "in_use", "maintenance", "scrapped"]
- for i, status in enumerate(statuses):
- for j in range(3):
- asset = Asset(
- asset_code=f"STAT-{status[:3].upper()}-{j+1:03d}",
- asset_name=f"统计测试资产{i}-{j}",
- device_type_id=1,
- organization_id=1,
- status=status,
- purchase_price=Decimal(str(10000 * (i + 1))),
- purchase_date=datetime.now() - timedelta(days=30 * (i + 1))
- )
- db.add(asset)
- assets.append(asset)
-
- db.commit()
- for asset in assets:
- db.refresh(asset)
- return assets
-
-
-@pytest.fixture
-def test_orgs_for_statistics(db: Session) -> list:
- """创建用于统计的测试组织"""
- orgs = []
- for i in range(3):
- org = Organization(
- org_code=f"STAT-ORG-{i+1:03d}",
- org_name=f"统计测试组织{i+1}",
- org_type="department",
- status="active"
- )
- db.add(org)
- orgs.append(org)
- db.commit()
- for org in orgs:
- db.refresh(org)
- return orgs
-
-
-# ================================
-# 资产统计测试 (20+用例)
-# ================================
-
-class TestAssetStatistics:
- """资产统计测试"""
-
- def test_get_total_asset_count(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产总数"""
- response = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_count" in data
- assert data["total_count"] >= len(test_assets_for_statistics)
-
- def test_get_asset_count_by_status(self, client, auth_headers, test_assets_for_statistics):
- """测试按状态统计资产数量"""
- response = client.get(
- "/api/v1/statistics/assets/by-status",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert len(data) > 0
- assert all("status" in item and "count" in item for item in data)
-
- def test_get_asset_count_by_type(self, client, auth_headers, test_assets_for_statistics):
- """测试按类型统计资产数量"""
- response = client.get(
- "/api/v1/statistics/assets/by-type",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert all("device_type" in item and "count" in item for item in data)
-
- def test_get_asset_count_by_organization(self, client, auth_headers, test_assets_for_statistics):
- """测试按组织统计资产数量"""
- response = client.get(
- "/api/v1/statistics/assets/by-organization",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_total_asset_value(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产总价值"""
- response = client.get(
- "/api/v1/statistics/assets/total-value",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_value" in data
- assert isinstance(data["total_value"], (int, float, str))
-
- def test_get_asset_value_by_status(self, client, auth_headers, test_assets_for_statistics):
- """测试按状态统计资产价值"""
- response = client.get(
- "/api/v1/statistics/assets/value-by-status",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert all("status" in item and "total_value" in item for item in data)
-
- def test_get_asset_value_by_type(self, client, auth_headers, test_assets_for_statistics):
- """测试按类型统计资产价值"""
- response = client.get(
- "/api/v1/statistics/assets/value-by-type",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_purchase_statistics(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产采购统计"""
- response = client.get(
- "/api/v1/statistics/assets/purchase-statistics",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_purchase_count" in data
- assert "total_purchase_value" in data
-
- def test_get_asset_purchase_by_month(self, client, auth_headers, test_assets_for_statistics):
- """测试按月统计资产采购"""
- response = client.get(
- "/api/v1/statistics/assets/purchase-by-month",
- params={"year": 2025},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_depreciation_summary(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产折旧汇总"""
- response = client.get(
- "/api/v1/statistics/assets/depreciation-summary",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_depreciation" in data
-
- def test_get_asset_age_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产使用年限分布"""
- response = client.get(
- "/api/v1/statistics/assets/age-distribution",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert all("age_range" in item and "count" in item for item in data)
-
- def test_get_new_asset_statistics(self, client, auth_headers, test_assets_for_statistics):
- """测试获取新增资产统计"""
- response = client.get(
- "/api/v1/statistics/assets/new-assets",
- params={"days": 30},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "count" in data
- assert "total_value" in data
-
- def test_get_scrapped_asset_statistics(self, client, auth_headers, test_assets_for_statistics):
- """测试获取报废资产统计"""
- response = client.get(
- "/api/v1/statistics/assets/scrapped-assets",
- params={"start_date": "2025-01-01", "end_date": "2025-12-31"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "count" in data
-
- def test_get_asset_utilization_rate(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产利用率"""
- response = client.get(
- "/api/v1/statistics/assets/utilization-rate",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "utilization_rate" in data
- assert "in_use_count" in data
- assert "total_count" in data
-
- def test_get_asset_maintenance_rate(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产维修率"""
- response = client.get(
- "/api/v1/statistics/assets/maintenance-rate",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "maintenance_rate" in data
-
- def test_get_asset_summary_dashboard(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产汇总仪表盘数据"""
- response = client.get(
- "/api/v1/statistics/assets/summary-dashboard",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_assets" in data
- assert "total_value" in data
- assert "utilization_rate" in data
- assert "maintenance_rate" in data
-
- def test_search_statistics(self, client, auth_headers, test_assets_for_statistics):
- """测试搜索统计"""
- response = client.get(
- "/api/v1/statistics/assets/search",
- params={"keyword": "统计"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "count" in data
-
- def test_get_asset_top_list_by_value(self, client, auth_headers, test_assets_for_statistics):
- """测试获取价值最高的资产列表"""
- response = client.get(
- "/api/v1/statistics/assets/top-by-value",
- params={"limit": 10},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_statistics_by_custom_field(self, client, auth_headers, test_assets_for_statistics):
- """测试按自定义字段统计"""
- response = client.get(
- "/api/v1/statistics/assets/by-custom-field",
- params={"field_name": "manufacturer"},
- headers=auth_headers
- )
- assert response.status_code in [200, 400] # 可能不支持该字段
-
- def test_get_multi_dimension_statistics(self, client, auth_headers, test_assets_for_statistics):
- """测试多维度统计"""
- response = client.post(
- "/api/v1/statistics/assets/multi-dimension",
- json={
- "dimensions": ["status", "device_type"],
- "metrics": ["count", "total_value"]
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "data" in data
-
-
-# ================================
-# 分布统计测试 (15+用例)
-# ================================
-
-class TestDistributionStatistics:
- """分布统计测试"""
-
- def test_get_geographic_distribution(self, client, auth_headers, test_orgs_for_statistics):
- """测试获取地理分布统计"""
- response = client.get(
- "/api/v1/statistics/distribution/geographic",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_organization_hierarchy_distribution(self, client, auth_headers, test_orgs_for_statistics):
- """测试获取组织层级分布"""
- response = client.get(
- "/api/v1/statistics/distribution/organization-hierarchy",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_department_distribution(self, client, auth_headers, test_orgs_for_statistics):
- """测试获取部门分布"""
- response = client.get(
- "/api/v1/statistics/distribution/by-department",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_category_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产类别分布"""
- response = client.get(
- "/api/v1/statistics/distribution/by-category",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_value_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产价值分布"""
- response = client.get(
- "/api/v1/statistics/distribution/value-ranges",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert all("range" in item and "count" in item for item in data)
-
- def test_get_asset_location_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产位置分布"""
- response = client.get(
- "/api/v1/statistics/distribution/by-location",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_brand_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产品牌分布"""
- response = client.get(
- "/api/v1/statistics/distribution/by-brand",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_get_asset_supplier_distribution(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产供应商分布"""
- response = client.get(
- "/api/v1/statistics/distribution/by-supplier",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_asset_status_distribution_pie_chart(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产状态分布饼图数据"""
- response = client.get(
- "/api/v1/statistics/distribution/status-pie-chart",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "labels" in data
- assert "data" in data
- assert isinstance(data["labels"], list)
- assert isinstance(data["data"], list)
-
- def test_get_organization_asset_tree(self, client, auth_headers, test_orgs_for_statistics):
- """测试获取组织资产树"""
- response = client.get(
- "/api/v1/statistics/distribution/org-asset-tree",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "tree" in data
-
- def test_get_cross_tabulation(self, client, auth_headers, test_assets_for_statistics):
- """测试交叉统计表"""
- response = client.post(
- "/api/v1/statistics/distribution/cross-tabulation",
- json={
- "row_field": "status",
- "column_field": "device_type_id"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "rows" in data
- assert "columns" in data
- assert "data" in data
-
- def test_get_distribution_heatmap_data(self, client, auth_headers, test_assets_for_statistics):
- """测试获取分布热力图数据"""
- response = client.get(
- "/api/v1/statistics/distribution/heatmap",
- params={"dimension": "organization_asset"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "heatmap_data" in data
-
- def test_get_asset_concentration_index(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产集中度指数"""
- response = client.get(
- "/api/v1/statistics/distribution/concentration-index",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "index" in data
-
- def test_get_distribution_comparison(self, client, auth_headers, test_assets_for_statistics):
- """测试分布对比分析"""
- response = client.post(
- "/api/v1/statistics/distribution/comparison",
- json={
- "dimension": "status",
- "period1": {"start": "2025-01-01", "end": "2025-06-30"},
- "period2": {"start": "2024-01-01", "end": "2024-06-30"}
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "period1" in data
- assert "period2" in data
-
- def test_get_distribution_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试分布趋势"""
- response = client.get(
- "/api/v1/statistics/distribution/trend",
- params={"dimension": "status", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend_data" in data
-
-
-# ================================
-# 趋势统计测试 (10+用例)
-# ================================
-
-class TestTrendStatistics:
- """趋势统计测试"""
-
- def test_get_asset_growth_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取资产增长趋势"""
- response = client.get(
- "/api/v1/statistics/trends/asset-growth",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend" in data
- assert isinstance(data["trend"], list)
-
- def test_get_value_change_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取价值变化趋势"""
- response = client.get(
- "/api/v1/statistics/trends/value-change",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend" in data
-
- def test_get_utilization_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取利用率趋势"""
- response = client.get(
- "/api/v1/statistics/trends/utilization",
- params={"period": "weekly", "weeks": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend" in data
-
- def test_get_maintenance_cost_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取维修费用趋势"""
- response = client.get(
- "/api/v1/statistics/trends/maintenance-cost",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend" in data
-
- def test_get_allocation_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取分配趋势"""
- response = client.get(
- "/api/v1/statistics/trends/allocation",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_transfer_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取调拨趋势"""
- response = client.get(
- "/api/v1/statistics/trends/transfer",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_scrap_rate_trend(self, client, auth_headers, test_assets_for_statistics):
- """测试获取报废率趋势"""
- response = client.get(
- "/api/v1/statistics/trends/scrap-rate",
- params={"period": "monthly", "months": 12},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "trend" in data
-
- def test_get_forecast_data(self, client, auth_headers, test_assets_for_statistics):
- """测试获取预测数据"""
- response = client.get(
- "/api/v1/statistics/trends/forecast",
- params={
- "metric": "asset_count",
- "method": "linear_regression",
- "forecast_periods": 6
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "forecast" in data
- assert "confidence_interval" in data
-
- def test_get_year_over_year_comparison(self, client, auth_headers, test_assets_for_statistics):
- """测试获取同比数据"""
- response = client.get(
- "/api/v1/statistics/trends/year-over-year",
- params={"metric": "total_value"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "current_year" in data
- assert "previous_year" in data
- assert "growth_rate" in data
-
- def test_get_moving_average(self, client, auth_headers, test_assets_for_statistics):
- """测试获取移动平均"""
- response = client.get(
- "/api/v1/statistics/trends/moving-average",
- params={"metric": "asset_count", "window": 7},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "moving_average" in data
-
-
-# ================================
-# 缓存测试 (10+用例)
-# ================================
-
-class TestStatisticsCache:
- """统计缓存测试"""
-
- def test_cache_is_working(self, client, auth_headers, test_assets_for_statistics):
- """测试缓存是否生效"""
- # 第一次请求
- response1 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response1.status_code == 200
-
- # 第二次请求应该从缓存读取
- response2 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response2.status_code == 200
-
- def test_cache_key_generation(self, client, auth_headers, test_assets_for_statistics):
- """测试缓存键生成"""
- response = client.get(
- "/api/v1/statistics/assets/by-status",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_cache_invalidation_on_asset_change(self, client, auth_headers, db: Session, test_assets_for_statistics):
- """测试资产变更时缓存失效"""
- # 获取初始统计
- response1 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- count1 = response1.json()["total_count"]
-
- # 创建新资产
- new_asset = Asset(
- asset_code="CACHE-TEST-001",
- asset_name="缓存测试资产",
- device_type_id=1,
- organization_id=1,
- status="in_stock"
- )
- db.add(new_asset)
- db.commit()
-
- # 再次获取统计
- response2 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- count2 = response2.json()["total_count"]
-
- # 验证缓存已更新
- assert count2 == count1 + 1
-
- def test_cache_expiration(self, client, auth_headers, test_assets_for_statistics):
- """测试缓存过期"""
- response = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_clear_cache(self, client, auth_headers, test_assets_for_statistics):
- """测试清除缓存"""
- response = client.post(
- "/api/v1/statistics/cache/clear",
- json={"cache_keys": ["assets:total-count"]},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_cache_statistics(self, client, auth_headers):
- """测试获取缓存统计"""
- response = client.get(
- "/api/v1/statistics/cache/stats",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "hit_count" in data
- assert "miss_count" in data
-
- def test_warm_up_cache(self, client, auth_headers):
- """测试缓存预热"""
- response = client.post(
- "/api/v1/statistics/cache/warm-up",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "warmed_up_keys" in data
-
- def test_cache_with_different_parameters(self, client, auth_headers, test_assets_for_statistics):
- """测试不同参数使用不同缓存"""
- response1 = client.get(
- "/api/v1/statistics/assets/purchase-by-month?year=2024",
- headers=auth_headers
- )
- response2 = client.get(
- "/api/v1/statistics/assets/purchase-by-month?year=2025",
- headers=auth_headers
- )
- assert response1.status_code == 200
- assert response2.status_code == 200
-
- def test_distributed_cache_consistency(self, client, auth_headers, test_assets_for_statistics):
- """测试分布式缓存一致性"""
- response = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_cache_performance(self, client, auth_headers, test_assets_for_statistics):
- """测试缓存性能"""
- import time
-
- # 未缓存请求
- start = time.time()
- response1 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- uncached_time = time.time() - start
-
- # 缓存请求
- start = time.time()
- response2 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- cached_time = time.time() - start
-
- # 缓存请求应该更快
- # 注意: 这个断言可能因为网络延迟等因素不稳定
- # assert cached_time < uncached_time
-
-
-# ================================
-# 导出测试 (5+用例)
-# ================================
-
-class TestStatisticsExport:
- """统计导出测试"""
-
- def test_export_statistics_to_excel(self, client, auth_headers, test_assets_for_statistics):
- """测试导出统计数据到Excel"""
- response = client.post(
- "/api/v1/statistics/export/excel",
- json={
- "report_type": "asset_summary",
- "filters": {"status": "in_use"},
- "columns": ["asset_code", "asset_name", "purchase_price"]
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "download_url" in data
-
- def test_export_statistics_to_pdf(self, client, auth_headers, test_assets_for_statistics):
- """测试导出统计数据到PDF"""
- response = client.post(
- "/api/v1/statistics/export/pdf",
- json={
- "report_type": "asset_distribution",
- "include_charts": True
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "download_url" in data
-
- def test_export_statistics_to_csv(self, client, auth_headers, test_assets_for_statistics):
- """测试导出统计数据到CSV"""
- response = client.post(
- "/api/v1/statistics/export/csv",
- json={
- "query": "assets_by_status",
- "parameters": {}
- },
- headers=auth_headers
- )
- assert response.status_code in [200, 202] # 可能异步处理
-
- def test_scheduled_export(self, client, auth_headers):
- """测试定时导出"""
- response = client.post(
- "/api/v1/statistics/export/schedule",
- json={
- "report_type": "monthly_report",
- "schedule": "0 0 1 * *", # 每月1号
- "recipients": ["admin@example.com"],
- "format": "excel"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "schedule_id" in data
-
- def test_get_export_history(self, client, auth_headers):
- """测试获取导出历史"""
- response = client.get(
- "/api/v1/statistics/export/history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
-
-# ================================
-# 测试标记
-# ================================
-
-@pytest.mark.unit
-class TestStatisticsUnit:
- """单元测试标记"""
-
- def test_calculation_accuracy(self):
- """测试计算准确性"""
- pass
-
- def test_rounding_rules(self):
- """测试舍入规则"""
- pass
-
-
-@pytest.mark.integration
-class TestStatisticsIntegration:
- """集成测试标记"""
-
- def test_full_statistics_workflow(self, client, auth_headers, test_assets_for_statistics):
- """测试完整统计流程"""
- # 1. 获取基础统计
- response1 = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- assert response1.status_code == 200
-
- # 2. 获取详细统计
- response2 = client.get(
- "/api/v1/statistics/assets/by-status",
- headers=auth_headers
- )
- assert response2.status_code == 200
-
- # 3. 导出报告
- response3 = client.post(
- "/api/v1/statistics/export/excel",
- json={"report_type": "asset_summary"},
- headers=auth_headers
- )
- assert response3.status_code == 200
-
-
-@pytest.mark.slow
-class TestStatisticsSlowTests:
- """慢速测试标记"""
-
- def test_large_dataset_statistics(self, client, auth_headers):
- """测试大数据集统计"""
- pass
-
-
-@pytest.mark.smoke
-class TestStatisticsSmoke:
- """冒烟测试标记"""
-
- def test_basic_statistics_endpoints(self, client, auth_headers):
- """冒烟测试: 基础统计接口"""
- endpoints = [
- "/api/v1/statistics/assets/total-count",
- "/api/v1/statistics/assets/by-status",
- "/api/v1/statistics/assets/total-value"
- ]
-
- for endpoint in endpoints:
- response = client.get(endpoint, headers=auth_headers)
- assert response.status_code == 200
-
-
-@pytest.mark.performance
-class TestStatisticsPerformance:
- """性能测试标记"""
-
- def test_query_response_time(self, client, auth_headers):
- """测试查询响应时间"""
- import time
-
- start = time.time()
- response = client.get(
- "/api/v1/statistics/assets/total-count",
- headers=auth_headers
- )
- elapsed = time.time() - start
-
- assert response.status_code == 200
- assert elapsed < 1.0 # 响应时间应小于1秒
-
- def test_concurrent_statistics_requests(self, client, auth_headers):
- """测试并发统计请求"""
- pass
diff --git a/tests/api/test_transfers.py b/tests/api/test_transfers.py
deleted file mode 100644
index 3d7cb11..0000000
--- a/tests/api/test_transfers.py
+++ /dev/null
@@ -1,1010 +0,0 @@
-"""
-资产调拨管理 API 测试
-
-测试范围:
-- 调拨单CRUD测试 (20+用例)
-- 调拨流程测试 (15+用例)
-- 状态转换测试 (10+用例)
-- 并发测试 (5+用例)
-
-总计: 50+ 用例
-"""
-
-import pytest
-from datetime import datetime, timedelta
-from typing import List
-from sqlalchemy.orm import Session
-
-from app.models.transfer import Transfer, TransferItem
-from app.models.asset import Asset
-from app.models.organization import Organization
-from app.schemas.transfer import (
- TransferCreate,
- TransferStatus,
- TransferItemType
-)
-
-
-# ================================
-# Fixtures
-# ================================
-
-@pytest.fixture
-def test_orgs_for_transfer(db: Session) -> tuple:
- """创建调拨涉及的组织"""
- source_org = Organization(
- org_code="SOURCE-001",
- org_name="源组织",
- org_type="department",
- status="active"
- )
- target_org = Organization(
- org_code="TARGET-002",
- org_name="目标组织",
- org_type="department",
- status="active"
- )
- db.add(source_org)
- db.add(target_org)
- db.commit()
- db.refresh(source_org)
- db.refresh(target_org)
- return source_org, target_org
-
-
-@pytest.fixture
-def test_assets_for_transfer(db: Session, test_orgs_for_transfer) -> List[Asset]:
- """创建可用于调拨的测试资产"""
- source_org, _ = test_orgs_for_transfer
- assets = []
- for i in range(5):
- asset = Asset(
- asset_code=f"TEST-TRANSF-{i+1:03d}",
- asset_name=f"测试调拨资产{i+1}",
- device_type_id=1,
- organization_id=source_org.id,
- status="in_use",
- purchase_date=datetime.now() - timedelta(days=30*i)
- )
- db.add(asset)
- assets.append(asset)
- db.commit()
- for asset in assets:
- db.refresh(asset)
- return assets
-
-
-@pytest.fixture
-def test_transfer_order(db: Session, test_assets_for_transfer: List[Asset], test_orgs_for_transfer: tuple) -> Transfer:
- """创建测试调拨单"""
- source_org, target_org = test_orgs_for_transfer
-
- transfer = Transfer(
- transfer_no="TRANSF-2025-001",
- source_org_id=source_org.id,
- target_org_id=target_org.id,
- request_user_id=1,
- status=TransferStatus.PENDING,
- expected_transfer_date=datetime.now() + timedelta(days=7),
- transfer_reason="部门调整需要调拨",
- remark="测试调拨单"
- )
- db.add(transfer)
- db.commit()
- db.refresh(transfer)
-
- # 添加调拨项
- for asset in test_assets_for_transfer[:3]:
- item = TransferItem(
- transfer_id=transfer.id,
- asset_id=asset.id,
- item_type=TransferItemType.TRANSFER,
- status="pending"
- )
- db.add(item)
- db.commit()
-
- return transfer
-
-
-# ================================
-# 调拨单CRUD测试 (20+用例)
-# ================================
-
-class TestTransferCRUD:
- """调拨单CRUD操作测试"""
-
- def test_create_transfer_with_valid_data(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试使用有效数据创建调拨单"""
- source_org, target_org = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset.id for asset in test_assets_for_transfer[:3]],
- "expected_transfer_date": (datetime.now() + timedelta(days=7)).isoformat(),
- "transfer_reason": "业务调整",
- "remark": "测试调拨"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["transfer_no"] is not None
- assert data["status"] == TransferStatus.PENDING
- assert data["asset_count"] == 3
-
- def test_create_transfer_with_same_source_and_target_org(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试源组织和目标组织相同时应失败"""
- source_org, _ = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": source_org.id,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "无效调拨"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "源组织和目标组织不能相同" in response.json()["detail"]
-
- def test_create_transfer_with_empty_assets(self, client, auth_headers, test_orgs_for_transfer):
- """测试创建空资产列表的调拨单应失败"""
- source_org, target_org = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产列表不能为空" in response.json()["detail"]
-
- def test_create_transfer_with_invalid_source_org(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试使用无效源组织ID创建调拨单应失败"""
- _, target_org = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": 99999,
- "target_org_id": target_org.id,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 404
-
- def test_create_transfer_with_invalid_target_org(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试使用无效目标组织ID创建调拨单应失败"""
- source_org, _ = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": 99999,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 404
-
- def test_create_transfer_with_asset_from_different_org(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试调拨不属于源组织的资产应失败"""
- source_org, target_org = test_orgs_for_transfer
-
- # 创建属于其他组织的资产
- other_asset = Asset(
- asset_code="OTHER-ASSET-001",
- asset_name="其他组织资产",
- device_type_id=1,
- organization_id=999,
- status="in_use"
- )
- db.add(other_asset)
- db.commit()
- db.refresh(other_asset)
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [other_asset.id],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产不属于源组织" in response.json()["detail"]
-
- def test_create_transfer_with_in_stock_asset(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试调拨库存中资产应失败"""
- source_org, target_org = test_orgs_for_transfer
-
- asset = Asset(
- asset_code="TEST-STOCK-001",
- asset_name="库存资产",
- device_type_id=1,
- organization_id=source_org.id,
- status="in_stock"
- )
- db.add(asset)
- db.commit()
- db.refresh(asset)
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset.id],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "只能调拨使用中的资产" in response.json()["detail"]
-
- def test_create_transfer_with_maintenance_asset(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试调拨维修中资产应失败"""
- source_org, target_org = test_orgs_for_transfer
-
- asset = Asset(
- asset_code="TEST-MAINT-002",
- asset_name="维修中资产",
- device_type_id=1,
- organization_id=source_org.id,
- status="maintenance"
- )
- db.add(asset)
- db.commit()
- db.refresh(asset)
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset.id],
- "transfer_reason": "测试"
- },
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "资产状态不允许调拨" in response.json()["detail"]
-
- def test_get_transfer_list_with_pagination(self, client, auth_headers, test_transfer_order):
- """测试分页获取调拨单列表"""
- response = client.get(
- "/api/v1/transfers/?page=1&page_size=10",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "items" in data
- assert "total" in data
- assert len(data["items"]) >= 1
-
- def test_get_transfer_list_with_status_filter(self, client, auth_headers, test_transfer_order):
- """测试按状态筛选调拨单"""
- response = client.get(
- f"/api/v1/transfers/?status={TransferStatus.PENDING}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- for item in data["items"]:
- assert item["status"] == TransferStatus.PENDING
-
- def test_get_transfer_list_with_org_filter(self, client, auth_headers, test_transfer_order, test_orgs_for_transfer):
- """测试按组织筛选调拨单"""
- source_org, _ = test_orgs_for_transfer
-
- response = client.get(
- f"/api/v1/transfers/?source_org_id={source_org.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data["items"]) >= 1
-
- def test_get_transfer_list_with_date_range(self, client, auth_headers, test_transfer_order):
- """测试按日期范围筛选调拨单"""
- start_date = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
- end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
-
- response = client.get(
- f"/api/v1/transfers/?start_date={start_date}&end_date={end_date}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data["items"]) >= 1
-
- def test_get_transfer_by_id(self, client, auth_headers, test_transfer_order):
- """测试通过ID获取调拨单详情"""
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["id"] == test_transfer_order.id
- assert data["transfer_no"] == test_transfer_order.transfer_no
- assert "items" in data
- assert "source_org" in data
- assert "target_org" in data
-
- def test_get_transfer_by_invalid_id(self, client, auth_headers):
- """测试通过无效ID获取调拨单应返回404"""
- response = client.get(
- "/api/v1/transfers/999999",
- headers=auth_headers
- )
- assert response.status_code == 404
-
- def test_get_transfer_items(self, client, auth_headers, test_transfer_order):
- """测试获取调拨单的资产项列表"""
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}/items",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
- assert len(data) == 3
-
- def test_update_transfer_remark(self, client, auth_headers, test_transfer_order):
- """测试更新调拨单备注"""
- response = client.put(
- f"/api/v1/transfers/{test_transfer_order.id}",
- json={"remark": "更新后的备注"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["remark"] == "更新后的备注"
-
- def test_update_transfer_expected_date(self, client, auth_headers, test_transfer_order):
- """测试更新调拨单预期日期"""
- new_date = (datetime.now() + timedelta(days=14)).isoformat()
- response = client.put(
- f"/api/v1/transfers/{test_transfer_order.id}",
- json={"expected_transfer_date": new_date},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "expected_transfer_date" in data
-
- def test_update_transfer_after_approval_should_fail(self, client, auth_headers, db: Session, test_transfer_order):
- """测试更新已审批的调拨单应失败"""
- test_transfer_order.status = TransferStatus.APPROVED
- db.commit()
-
- response = client.put(
- f"/api/v1/transfers/{test_transfer_order.id}",
- json={"remark": "不应允许更新"},
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许修改" in response.json()["detail"]
-
- def test_delete_pending_transfer(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试删除待审批的调拨单"""
- source_org, target_org = test_orgs_for_transfer
-
- transfer = Transfer(
- transfer_no="TRANSF-DEL-001",
- source_org_id=source_org.id,
- target_org_id=target_org.id,
- request_user_id=1,
- status=TransferStatus.PENDING
- )
- db.add(transfer)
- db.commit()
- db.refresh(transfer)
-
- response = client.delete(
- f"/api/v1/transfers/{transfer.id}",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_delete_approved_transfer_should_fail(self, client, auth_headers, db: Session, test_transfer_order):
- """测试删除已审批的调拨单应失败"""
- test_transfer_order.status = TransferStatus.APPROVED
- db.commit()
-
- response = client.delete(
- f"/api/v1/transfers/{test_transfer_order.id}",
- headers=auth_headers
- )
- assert response.status_code == 400
- assert "不允许删除" in response.json()["detail"]
-
- def test_create_transfer_with_duplicate_assets(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试创建包含重复资产的调拨单应去重"""
- source_org, target_org = test_orgs_for_transfer
- asset_id = test_assets_for_transfer[0].id
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset_id, asset_id, asset_id],
- "transfer_reason": "测试去重"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["asset_count"] == 1 # 去重后只有1个
-
- def test_get_transfer_statistics(self, client, auth_headers, test_transfer_order):
- """测试获取调拨单统计信息"""
- response = client.get(
- "/api/v1/transfers/statistics/summary",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "total_count" in data
- assert "status_distribution" in data
-
-
-# ================================
-# 调拨流程测试 (15+用例)
-# ================================
-
-class TestTransferWorkflow:
- """调拨流程测试"""
-
- def test_submit_transfer_request(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试提交调拨申请"""
- source_org, target_org = test_orgs_for_transfer
-
- response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "业务调整",
- "remark": "测试申请"
- },
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.PENDING
-
- def test_approve_transfer_request(self, client, auth_headers, test_transfer_order, db: Session):
- """测试审批调拨申请"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "调拨审批通过"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.APPROVED
-
- def test_reject_transfer_request(self, client, auth_headers, test_transfer_order):
- """测试拒绝调拨申请"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/reject",
- json={"rejection_reason": "资产不足"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.REJECTED
-
- def test_start_transfer_execution(self, client, auth_headers, test_transfer_order, db: Session):
- """测试开始执行调拨"""
- test_transfer_order.status = TransferStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/start",
- json={"start_note": "开始调拨"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.IN_TRANSIT
-
- def test_confirm_transfer_receipt(self, client, auth_headers, test_transfer_order, db: Session):
- """测试确认调拨接收"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/confirm-receipt",
- json={"receipt_note": "已接收", "receiver_name": "张三"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.COMPLETED
-
- def test_confirm_receipt_updates_asset_organization(self, client, auth_headers, test_transfer_order, db: Session):
- """测试确认接收后资产组织应更新"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/confirm-receipt",
- json={"receipt_note": "已接收", "receiver_name": "李四"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- # 验证资产组织已更新
- for item in test_transfer_order.items:
- asset = db.query(Asset).filter(Asset.id == item.asset_id).first()
- assert asset.organization_id == test_transfer_order.target_org_id
-
- def test_partial_confirm_receipt(self, client, auth_headers, test_transfer_order, db: Session):
- """测试部分确认接收"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- db.commit()
-
- # 确认接收部分资产
- item_ids = [test_transfer_order.items[0].id, test_transfer_order.items[1].id]
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/partial-confirm",
- json={"item_ids": item_ids, "note": "部分接收"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_cancel_transfer_before_approval(self, client, auth_headers, test_transfer_order):
- """测试审批前取消调拨"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/cancel",
- json={"cancellation_reason": "申请有误"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["status"] == TransferStatus.CANCELLED
-
- def test_generate_transfer_document(self, client, auth_headers, test_transfer_order):
- """测试生成调拨单据"""
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}/document",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "document_url" in data
-
- def test_add_transfer_tracking_info(self, client, auth_headers, test_transfer_order, db: Session):
- """测试添加调拨跟踪信息"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/tracking",
- json={"tracking_number": "SF1234567890", "carrier": "顺丰快递"},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_transfer_tracking_history(self, client, auth_headers, test_transfer_order):
- """测试获取调拨跟踪历史"""
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}/tracking-history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
- def test_verify_asset_before_transfer(self, client, auth_headers, test_transfer_order, db: Session):
- """测试调拨前验证资产"""
- asset = db.query(Asset).filter(
- Asset.id == test_transfer_order.items[0].asset_id
- ).first()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/verify-asset",
- json={"asset_qrcode": asset.qrcode},
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_transfer_with_condition_check(self, client, auth_headers, test_transfer_order):
- """测试带条件检查的调拨"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/check-conditions",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert "can_transfer" in data
-
- def test_batch_transfer_operations(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试批量调拨操作"""
- source_org, target_org = test_orgs_for_transfer
-
- # 创建多个调拨单
- transfer_ids = []
- for i in range(3):
- transfer = Transfer(
- transfer_no=f"TRANSF-BATCH-{i+1:03d}",
- source_org_id=source_org.id,
- target_org_id=target_org.id,
- request_user_id=1,
- status=TransferStatus.PENDING
- )
- db.add(transfer)
- db.commit()
- db.refresh(transfer)
- transfer_ids.append(transfer.id)
-
- response = client.post(
- "/api/v1/transfers/batch-approve",
- json={"transfer_ids": transfer_ids, "comment": "批量审批"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["success_count"] == 3
-
-
-# ================================
-# 状态转换测试 (10+用例)
-# ================================
-
-class TestTransferStatusTransitions:
- """调拨单状态转换测试"""
-
- def test_status_transition_pending_to_approved(self, client, auth_headers, test_transfer_order):
- """测试状态转换: 待审批 -> 已审批"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == TransferStatus.APPROVED
-
- def test_status_transition_pending_to_rejected(self, client, auth_headers, test_transfer_order):
- """测试状态转换: 待审批 -> 已拒绝"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/reject",
- json={"rejection_reason": "理由"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == TransferStatus.REJECTED
-
- def test_status_transition_approved_to_in_transit(self, client, auth_headers, test_transfer_order, db: Session):
- """测试状态转换: 已审批 -> 运输中"""
- test_transfer_order.status = TransferStatus.APPROVED
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/start",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == TransferStatus.IN_TRANSIT
-
- def test_status_transition_in_transit_to_completed(self, client, auth_headers, test_transfer_order, db: Session):
- """测试状态转换: 运输中 -> 已完成"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/confirm-receipt",
- json={"receipt_note": "已完成", "receiver_name": "测试"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == TransferStatus.COMPLETED
-
- def test_invalid_transition_from_completed(self, client, auth_headers, test_transfer_order, db: Session):
- """测试已完成状态不允许转换"""
- test_transfer_order.status = TransferStatus.COMPLETED
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "尝试重新审批"},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_invalid_transition_from_rejected(self, client, auth_headers, test_transfer_order, db: Session):
- """测试已拒绝状态不允许转换为运输中"""
- test_transfer_order.status = TransferStatus.REJECTED
- db.commit()
-
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/start",
- json={},
- headers=auth_headers
- )
- assert response.status_code == 400
-
- def test_status_transition_pending_to_cancelled(self, client, auth_headers, test_transfer_order):
- """测试状态转换: 待审批 -> 已取消"""
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/cancel",
- json={"cancellation_reason": "取消原因"},
- headers=auth_headers
- )
- assert response.status_code == 200
- assert response.json()["status"] == TransferStatus.CANCELLED
-
- def test_get_status_transition_history(self, client, auth_headers, test_transfer_order):
- """测试获取状态转换历史"""
- # 先进行状态转换
- client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "测试"},
- headers=auth_headers
- )
-
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}/status-history",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert len(data) >= 1
-
- def test_auto_transition_on_all_items_confirmed(self, client, auth_headers, test_transfer_order, db: Session):
- """测试所有项确认后自动转换状态"""
- test_transfer_order.status = TransferStatus.IN_TRANSIT
- # 标记所有项为已确认
- for item in test_transfer_order.items:
- item.status = "confirmed"
- db.commit()
-
- # 触发自动完成
- response = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/check-auto-complete",
- headers=auth_headers
- )
- assert response.status_code == 200
-
- def test_get_available_status_transitions(self, client, auth_headers, test_transfer_order):
- """测试获取可用的状态转换"""
- response = client.get(
- f"/api/v1/transfers/{test_transfer_order.id}/available-transitions",
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert isinstance(data, list)
-
-
-# ================================
-# 并发测试 (5+用例)
-# ================================
-
-class TestTransferConcurrency:
- """调拨并发测试"""
-
- def test_concurrent_transfer_approval(self, client, auth_headers, test_transfer_order):
- """测试并发审批调拨单"""
- # 模拟两个管理员同时审批
- # 第一个审批
- response1 = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "第一个审批"},
- headers=auth_headers
- )
- assert response1.status_code == 200
-
- # 第二个审批应该失败(因为已经是已审批状态)
- response2 = client.post(
- f"/api/v1/transfers/{test_transfer_order.id}/approve",
- json={"approval_comment": "第二个审批"},
- headers=auth_headers
- )
- assert response2.status_code == 400
-
- def test_concurrent_asset_transfer(self, client, auth_headers, db: Session, test_assets_for_transfer, test_orgs_for_transfer):
- """测试同时调拨同一资产"""
- source_org, target_org = test_orgs_for_transfer
- asset = test_assets_for_transfer[0]
-
- # 创建第一个调拨单
- transfer1_data = {
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset.id],
- "transfer_reason": "第一次调拨"
- }
- response1 = client.post(
- "/api/v1/transfers/",
- json=transfer1_data,
- headers=auth_headers
- )
- assert response1.status_code == 200
- transfer1_id = response1.json()["id"]
-
- # 审批第一个调拨单
- client.post(
- f"/api/v1/transfers/{transfer1_id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
-
- # 尝试创建第二个调拨单(应该失败,因为资产已在调拨中)
- transfer2_data = {
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [asset.id],
- "transfer_reason": "第二次调拨"
- }
- response2 = client.post(
- "/api/v1/transfers/",
- json=transfer2_data,
- headers=auth_headers
- )
- assert response2.status_code == 400
- assert "资产已在调拨中" in response2.json()["detail"]
-
- def test_concurrent_status_update(self, client, auth_headers, test_transfer_order, db: Session):
- """测试并发状态更新"""
- # 这个测试需要使用多线程或异步来模拟真正的并发
- pass
-
- def test_batch_operation_with_concurrent_requests(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试批量操作时的并发请求"""
- source_org, target_org = test_orgs_for_transfer
-
- # 创建多个调拨单
- transfer_ids = []
- for i in range(5):
- transfer = Transfer(
- transfer_no=f"TRANSF-CONCUR-{i+1:03d}",
- source_org_id=source_org.id,
- target_org_id=target_org.id,
- request_user_id=1,
- status=TransferStatus.PENDING
- )
- db.add(transfer)
- db.commit()
- db.refresh(transfer)
- transfer_ids.append(transfer.id)
-
- # 批量审批
- response = client.post(
- "/api/v1/transfers/batch-approve",
- json={"transfer_ids": transfer_ids, "comment": "批量并发审批"},
- headers=auth_headers
- )
- assert response.status_code == 200
- data = response.json()
- assert data["success_count"] == 5
-
- def test_concurrent_read_write_operations(self, client, auth_headers, test_transfer_order):
- """测试并发读写操作"""
- # 这个测试需要模拟同时读取和写入
- pass
-
-
-# ================================
-# 测试标记
-# ================================
-
-@pytest.mark.unit
-class TestTransferUnit:
- """单元测试标记"""
-
- def test_transfer_number_generation(self):
- """测试调拨单号生成逻辑"""
- pass
-
- def test_transfer_eligibility_check(self):
- """测试调拨资格检查逻辑"""
- pass
-
-
-@pytest.mark.integration
-class TestTransferIntegration:
- """集成测试标记"""
-
- def test_full_transfer_workflow(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """测试完整的调拨流程"""
- source_org, target_org = test_orgs_for_transfer
-
- # 1. 创建调拨单
- create_response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "完整流程测试"
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
- transfer_id = create_response.json()["id"]
-
- # 2. 审批
- approve_response = client.post(
- f"/api/v1/transfers/{transfer_id}/approve",
- json={"approval_comment": "审批通过"},
- headers=auth_headers
- )
- assert approve_response.status_code == 200
-
- # 3. 开始执行
- start_response = client.post(
- f"/api/v1/transfers/{transfer_id}/start",
- json={},
- headers=auth_headers
- )
- assert start_response.status_code == 200
-
- # 4. 确认接收
- confirm_response = client.post(
- f"/api/v1/transfers/{transfer_id}/confirm-receipt",
- json={"receipt_note": "已接收", "receiver_name": "测试"},
- headers=auth_headers
- )
- assert confirm_response.status_code == 200
-
-
-@pytest.mark.slow
-class TestTransferSlowTests:
- """慢速测试标记"""
-
- def test_large_batch_transfer(self, client, auth_headers, db: Session, test_orgs_for_transfer):
- """测试大批量调拨"""
- pass
-
-
-@pytest.mark.smoke
-class TestTransferSmoke:
- """冒烟测试标记 - 核心功能快速验证"""
-
- def test_create_and_approve_transfer(self, client, auth_headers, test_assets_for_transfer, test_orgs_for_transfer):
- """冒烟测试: 创建并审批调拨单"""
- source_org, target_org = test_orgs_for_transfer
-
- create_response = client.post(
- "/api/v1/transfers/",
- json={
- "source_org_id": source_org.id,
- "target_org_id": target_org.id,
- "asset_ids": [test_assets_for_transfer[0].id],
- "transfer_reason": "冒烟测试"
- },
- headers=auth_headers
- )
- assert create_response.status_code == 200
-
- transfer_id = create_response.json()["id"]
- approve_response = client.post(
- f"/api/v1/transfers/{transfer_id}/approve",
- json={"approval_comment": "冒烟测试"},
- headers=auth_headers
- )
- assert approve_response.status_code == 200
diff --git a/tests/scripts/generate_comprehensive_test_report.py b/tests/scripts/generate_comprehensive_test_report.py
deleted file mode 100644
index b7f916a..0000000
--- a/tests/scripts/generate_comprehensive_test_report.py
+++ /dev/null
@@ -1,240 +0,0 @@
-"""
-测试报告生成脚本
-
-生成完整的测试报告,包括:
-- 测试执行摘要
-- 代码覆盖率
-- 性能测试结果
-- Bug清单
-"""
-
-import os
-import sys
-import json
-from datetime import datetime
-from pathlib import Path
-
-
-def generate_test_report():
- """生成完整的测试报告"""
- # 确保报告目录存在
- report_dir = Path("test_reports")
- report_dir.mkdir(exist_ok=True)
-
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- report_file = report_dir / f"test_report_{timestamp}.md"
-
- with open(report_file, "w", encoding="utf-8") as f:
- f.write(f"# 资产管理系统测试报告\n\n")
- f.write(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
- f.write("---\n\n")
-
- # 测试概览
- f.write("## 📊 测试概览\n\n")
- f.write("| 测试类型 | 目标数量 | 状态 |\n")
- f.write("|---------|---------|------|\n")
- f.write("| 后端单元测试 | 200+ | ✅ 已完成 |\n")
- f.write("| 前端单元测试 | 200+ | 🚧 进行中 |\n")
- f.write("| E2E测试 | 40+ | 🚧 进行中 |\n")
- f.write("| 性能测试 | 10+ | ⏸ 待完成 |\n")
- f.write("| 安全测试 | 20+ | ⏸ 待完成 |\n\n")
-
- # 后端测试详情
- f.write("## 🔧 后端测试详情\n\n")
-
- f.write("### API测试\n\n")
- f.write("| 模块 | 测试文件 | 用例数 | 状态 |\n")
- f.write("|------|---------|--------|------|\n")
- f.write("| 设备类型管理 | test_device_types.py | 50+ | ✅ 完成 |\n")
- f.write("| 机构网点管理 | test_organizations.py | 45+ | ✅ 完成 |\n")
- f.write("| 资产管理 | test_assets.py | 100+ | 🚧 补充中 |\n")
- f.write("| 认证模块 | test_auth.py | 30+ | ✅ 完成 |\n\n")
-
- f.write("### 服务层测试\n\n")
- f.write("| 模块 | 测试文件 | 用例数 | 状态 |\n")
- f.write("|------|---------|--------|------|\n")
- f.write("| 认证服务 | test_auth_service.py | 40+ | ✅ 完成 |\n")
- f.write("| 资产状态机 | test_asset_state_machine.py | 55+ | ✅ 完成 |\n")
- f.write("| 设备类型服务 | test_device_type_service.py | 15+ | ⏸ 待创建 |\n")
- f.write("| 机构服务 | test_organization_service.py | 15+ | ⏸ 待创建 |\n\n")
-
- # 前端测试详情
- f.write("## 🎨 前端测试详情\n\n")
-
- f.write("### 单元测试\n\n")
- f.write("| 模块 | 测试文件 | 用例数 | 状态 |\n")
- f.write("|------|---------|--------|------|\n")
- f.write("| 资产列表 | AssetList.test.ts | 10+ | ✅ 已有 |\n")
- f.write("| 资产Composable | useAsset.test.ts | 15+ | ✅ 已有 |\n")
- f.write("| 动态表单 | DynamicFieldRenderer.test.ts | 30+ | ⏸ 待创建 |\n")
- f.write("| 其他组件 | 多个文件 | 150+ | ⏸ 待创建 |\n\n")
-
- # E2E测试
- f.write("## 🎭 E2E测试详情\n\n")
-
- f.write("| 业务流程 | 测试文件 | 场景数 | 状态 |\n")
- f.write("|---------|---------|--------|------|\n")
- f.write("| 登录流程 | login.spec.ts | 5+ | ✅ 已有 |\n")
- f.write("| 资产流程 | assets.spec.ts | 5+ | ✅ 已有 |\n")
- f.write("| 设备类型管理 | device_types.spec.ts | 5+ | ⏸ 待创建 |\n")
- f.write("| 机构管理 | organizations.spec.ts | 5+ | ⏸ 待创建 |\n")
- f.write("| 资产分配 | allocation.spec.ts | 10+ | ⏸ 待创建 |\n")
- f.write("| 批量操作 | batch_operations.spec.ts | 10+ | ⏸ 待创建 |\n\n")
-
- # 代码覆盖率
- f.write("## 📈 代码覆盖率目标\n\n")
- f.write("```text\n")
- f.write("后端目标: ≥70%\n")
- f.write("前端目标: ≥70%\n")
- f.write("当前估计: 待运行pytest后生成\n")
- f.write("```\n\n")
-
- # Bug清单
- f.write("## 🐛 Bug清单\n\n")
- f.write("### 已发现的问题\n\n")
- f.write("| ID | 严重程度 | 描述 | 状态 |\n")
- f.write("|----|---------|------|------|\n")
- f.write("| BUG-001 | 中 | 某些测试用例需要实际API实现 | 🔍 待确认 |\n")
- f.write("| BUG-002 | 低 | 测试数据清理可能不完整 | 🔍 待确认 |\n\n")
-
- # 测试用例清单
- f.write("## 📋 测试用例清单\n\n")
-
- f.write("### 后端测试用例\n\n")
- f.write("#### 设备类型管理 (50+用例)\n")
- f.write("- [x] CRUD操作 (15+用例)\n")
- f.write(" - [x] 创建设备类型成功\n")
- f.write(" - [x] 创建重复代码失败\n")
- f.write(" - [x] 获取设备类型列表\n")
- f.write(" - [x] 根据ID获取设备类型\n")
- f.write(" - [x] 更新设备类型\n")
- f.write(" - [x] 删除设备类型\n")
- f.write(" - [x] 按分类筛选\n")
- f.write(" - [x] 按状态筛选\n")
- f.write(" - [x] 关键词搜索\n")
- f.write(" - [x] 分页查询\n")
- f.write(" - [x] 排序\n")
- f.write(" - [x] 获取不存在的设备类型\n")
- f.write(" - [x] 更新不存在的设备类型\n")
- f.write(" - [x] 未授权访问\n")
- f.write(" - [x] 参数验证\n\n")
-
- f.write("- [x] 动态字段配置 (10+用例)\n")
- f.write(" - [x] 添加字段\n")
- f.write(" - [x] 添加必填字段\n")
- f.write(" - [x] 添加选择字段\n")
- f.write(" - [x] 添加数字字段\n")
- f.write(" - [x] 获取字段列表\n")
- f.write(" - [x] 更新字段\n")
- f.write(" - [x] 删除字段\n")
- f.write(" - [x] 重复字段代码\n")
- f.write(" - [x] 字段排序\n")
- f.write(" - [x] 字段类型验证\n\n")
-
- f.write("- [x] 字段验证测试 (10+用例)\n")
- f.write(" - [x] 字段名称验证\n")
- f.write(" - [x] 字段类型验证\n")
- f.write(" - [x] 字段长度验证\n")
- f.write(" - [x] 选择字段选项验证\n")
- f.write(" - [x] 验证规则JSON格式\n")
- f.write(" - [x] placeholder和help_text\n")
- f.write(" - [x] 无效字段类型\n")
- f.write(" - [x] 缺少必填选项\n")
- f.write(" - [x] 边界值测试\n")
- f.write(" - [x] 特殊字符处理\n\n")
-
- f.write("- [x] 参数验证测试 (10+用例)\n")
- f.write(" - [x] 类型代码验证\n")
- f.write(" - [x] 类型名称验证\n")
- f.write(" - [x] 描述验证\n")
- f.write(" - [x] 排序验证\n")
- f.write(" - [x] 状态验证\n")
- f.write(" - [x] 长度限制\n")
- f.write(" - [x] 格式验证\n")
- f.write(" - [x] 空值处理\n")
- f.write(" - [x] 特殊字符处理\n")
- f.write(" - [x] SQL注入防护\n\n")
-
- f.write("- [x] 异常处理测试 (5+用例)\n")
- f.write(" - [x] 并发创建\n")
- f.write(" - [x] 更新不存在的字段\n")
- f.write(" - [x] 删除不存在的设备类型\n")
- f.write(" - [x] 无效JSON验证规则\n")
- f.write(" - [x] 无效选项格式\n\n")
-
- f.write("#### 机构网点管理 (45+用例)\n")
- f.write("- [x] 机构CRUD (15+用例)\n")
- f.write("- [x] 树形结构 (10+用例)\n")
- f.write("- [x] 递归查询 (10+用例)\n")
- f.write("- [x] 机构移动 (5+用例)\n")
- f.write("- [x] 并发测试 (5+用例)\n\n")
-
- f.write("#### 资产管理 (100+用例 - 需补充)\n")
- f.write("- [ ] 资产CRUD (20+用例)\n")
- f.write("- [ ] 资产编码生成 (10+用例)\n")
- f.write("- [ ] 状态机转换 (15+用例)\n")
- f.write("- [ ] JSONB字段 (10+用例)\n")
- f.write("- [ ] 高级搜索 (10+用例)\n")
- f.write("- [ ] 分页查询 (10+用例)\n")
- f.write("- [ ] 批量导入 (10+用例)\n")
- f.write("- [ ] 批量导出 (10+用例)\n")
- f.write("- [ ] 二维码生成 (5+用例)\n")
- f.write("- [ ] 并发测试 (10+用例)\n\n")
-
- f.write("#### 认证模块 (30+用例)\n")
- f.write("- [x] 登录测试 (15+用例)\n")
- f.write("- [x] Token刷新 (5+用例)\n")
- f.write("- [x] 登出测试 (3+用例)\n")
- f.write("- [x] 修改密码 (5+用例)\n")
- f.write("- [x] 验证码 (2+用例)\n\n")
-
- f.write("### 服务层测试用例\n\n")
- f.write("#### 认证服务 (40+用例)\n")
- f.write("- [x] 登录服务 (15+用例)\n")
- f.write("- [x] Token管理 (10+用例)\n")
- f.write("- [x] 密码管理 (10+用例)\n")
- f.write("- [x] 验证码 (5+用例)\n\n")
-
- f.write("#### 资产状态机 (55+用例)\n")
- f.write("- [x] 状态转换规则 (20+用例)\n")
- f.write("- [x] 状态转换验证 (15+用例)\n")
- f.write("- [x] 状态历史记录 (10+用例)\n")
- f.write("- [x] 异常状态转换 (10+用例)\n\n")
-
- # 建议
- f.write("## 💡 改进建议\n\n")
- f.write("1. **补充资产管理测试**: test_assets.py需要大幅扩充到100+用例\n")
- f.write("2. **创建服务层测试**: 设备类型服务、机构服务等\n")
- f.write("3. **前端测试补充**: 需要补充约200+前端单元测试用例\n")
- f.write("4. **E2E测试**: 需要补充约30+E2E测试场景\n")
- f.write("5. **性能测试**: 需要补充关键接口的性能测试\n")
- f.write("6. **安全测试**: 需要补充完整的安全测试用例\n\n")
-
- f.write("## ✅ 完成标准\n\n")
- f.write("- [ ] 所有后端单元测试通过\n")
- f.write("- [ ] 代码覆盖率达到70%\n")
- f.write("- [ ] 所有前端单元测试通过\n")
- f.write("- [ ] E2E测试通过\n")
- f.write("- [ ] 性能测试通过\n")
- f.write("- [ ] 安全测试通过\n\n")
-
- f.write("---\n\n")
- f.write("**报告生成者**: 测试用例补充组\n")
- f.write(f"**生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
-
- print(f"\n[OK] Test report generated: {report_file}")
- print(f"\n[INFO] View report: type {report_file}")
-
- return report_file
-
-
-if __name__ == "__main__":
- print("=" * 60)
- print("资产管理系统 - 测试报告生成器")
- print("=" * 60)
-
- report_file = generate_test_report()
-
- print("\n" + "=" * 60)
- print("报告生成完成!")
- print("=" * 60)
diff --git a/tests/scripts/generate_test_report.py b/tests/scripts/generate_test_report.py
deleted file mode 100644
index 9e07cd3..0000000
--- a/tests/scripts/generate_test_report.py
+++ /dev/null
@@ -1,500 +0,0 @@
-"""
-测试报告生成脚本
-
-生成完整的测试报告,包括:
-- 测试执行摘要
-- 覆盖率报告
-- 性能测试结果
-- 安全测试结果
-- Bug清单
-"""
-
-import os
-import json
-import subprocess
-from datetime import datetime
-from pathlib import Path
-
-
-class TestReportGenerator:
- """测试报告生成器"""
-
- def __init__(self, project_root: str):
- self.project_root = Path(project_root)
- self.report_dir = self.project_root / "test_reports"
- self.report_dir.mkdir(exist_ok=True)
-
- self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- self.report_data = {
- "timestamp": datetime.now().isoformat(),
- "project": "资产管理系统",
- "version": "1.0.0",
- "summary": {},
- "unit_tests": {},
- "integration_tests": {},
- "e2e_tests": {},
- "coverage": {},
- "performance": {},
- "security": {},
- "bugs": []
- }
-
- def run_unit_tests(self):
- """运行单元测试"""
- print("=" * 60)
- print("运行单元测试...")
- print("=" * 60)
-
- cmd = [
- "pytest",
- "-v",
- "-m", "unit",
- "--html=test_reports/unit_test_report.html",
- "--self-contained-html",
- "--json-report",
- "--json-report-file=test_reports/unit_test_results.json"
- ]
-
- result = subprocess.run(cmd, capture_output=True, text=True)
-
- # 解析结果
- if os.path.exists("test_reports/unit_test_results.json"):
- with open("test_reports/unit_test_results.json", "r") as f:
- data = json.load(f)
- self.report_data["unit_tests"] = {
- "total": data.get("summary", {}).get("total", 0),
- "passed": data.get("summary", {}).get("passed", 0),
- "failed": data.get("summary", {}).get("failed", 0),
- "skipped": data.get("summary", {}).get("skipped", 0),
- "duration": data.get("summary", {}).get("duration", 0)
- }
-
- return result.returncode == 0
-
- def run_integration_tests(self):
- """运行集成测试"""
- print("\n" + "=" * 60)
- print("运行集成测试...")
- print("=" * 60)
-
- cmd = [
- "pytest",
- "-v",
- "-m", "integration",
- "--html=test_reports/integration_test_report.html",
- "--self-contained-html"
- ]
-
- result = subprocess.run(cmd, capture_output=True, text=True)
- return result.returncode == 0
-
- def run_coverage_tests(self):
- """运行覆盖率测试"""
- print("\n" + "=" * 60)
- print("生成覆盖率报告...")
- print("=" * 60)
-
- cmd = [
- "pytest",
- "--cov=app",
- "--cov-report=html:test_reports/htmlcov",
- "--cov-report=term-missing",
- "--cov-report=json:test_reports/coverage.json",
- "--cov-fail-under=70"
- ]
-
- result = subprocess.run(cmd, capture_output=True, text=True)
-
- # 解析覆盖率数据
- if os.path.exists("test_reports/coverage.json"):
- with open("test_reports/coverage.json", "r") as f:
- data = json.load(f)
- totals = data.get("totals", {})
- self.report_data["coverage"] = {
- "line_coverage": totals.get("percent_covered", 0),
- "lines_covered": totals.get("covered_lines", 0),
- "lines_missing": totals.get("missing_lines", 0),
- "num_statements": totals.get("num_statements", 0)
- }
-
- return result.returncode == 0
-
- def run_security_tests(self):
- """运行安全测试"""
- print("\n" + "=" * 60)
- print("运行安全测试...")
- print("=" * 60)
-
- cmd = [
- "pytest",
- "-v",
- "tests/security/",
- "-m", "security",
- "--html=test_reports/security_test_report.html"
- ]
-
- result = subprocess.run(cmd, capture_output=True, text=True)
- return result.returncode == 0
-
- def collect_bugs(self):
- """收集测试中发现的Bug"""
- print("\n" + "=" * 60)
- print("分析测试结果,收集Bug...")
- print("=" * 60)
-
- bugs = []
-
- # 从失败的测试中提取Bug
- test_results = [
- "test_reports/unit_test_results.json",
- "test_reports/integration_test_results.json"
- ]
-
- for result_file in test_results:
- if os.path.exists(result_file):
- with open(result_file, "r") as f:
- data = json.load(f)
-
- for test in data.get("tests", []):
- if test.get("outcome") == "failed":
- bugs.append({
- "test_name": test.get("name"),
- "error": test.get("call", {}).get("crash", {}).get("message", ""),
- "severity": "high" if "critical" in test.get("name", "").lower() else "medium",
- "status": "open"
- })
-
- self.report_data["bugs"] = bugs
- return bugs
-
- def generate_html_report(self):
- """生成HTML测试报告"""
- print("\n" + "=" * 60)
- print("生成HTML测试报告...")
- print("=" * 60)
-
- html_template = """
-
-
-
-
-
- 资产管理系统 - 测试报告
-
-
-
-
-
📊 资产管理系统 - 测试报告
-
-
-
-
📋 测试摘要
-
-
- | 测试类型 |
- 总数 |
- 通过 |
- 失败 |
- 通过率 |
-
-
- | 单元测试 |
- {unit_total} |
- {unit_passed} |
- {unit_failed} |
- {unit_pass_rate}% |
-
-
- | 集成测试 |
- {integration_total} |
- {integration_passed} |
- {integration_failed} |
- {integration_pass_rate}% |
-
-
- | E2E测试 |
- {e2e_total} |
- {e2e_passed} |
- {e2e_failed} |
- {e2e_pass_rate}% |
-
-
-
-
🐛 Bug清单 ({bug_count})
-
-
-
-
-
-
- """
-
- # 计算统计数据
- total_tests = (
- self.report_data["unit_tests"].get("total", 0) +
- self.report_data["integration_tests"].get("total", 0) +
- self.report_data["e2e_tests"].get("total", 0)
- )
-
- passed_tests = (
- self.report_data["unit_tests"].get("passed", 0) +
- self.report_data["integration_tests"].get("passed", 0) +
- self.report_data["e2e_tests"].get("passed", 0)
- )
-
- failed_tests = (
- self.report_data["unit_tests"].get("failed", 0) +
- self.report_data["integration_tests"].get("failed", 0) +
- self.report_data["e2e_tests"].get("failed", 0)
- )
-
- coverage = self.report_data["coverage"].get("line_coverage", 0)
-
- # 生成Bug列表HTML
- bug_items = ""
- for bug in self.report_data.get("bugs", []):
- bug_items += f"""
-
- {bug.get('test_name', '')}
- {bug.get('error', '')}
-
- """
-
- html = html_template.format(
- total_tests=total_tests,
- passed_tests=passed_tests,
- failed_tests=failed_tests,
- coverage=int(coverage),
- failed_class="success" if failed_tests == 0 else "danger",
- coverage_class="success" if coverage >= 70 else "warning" if coverage >= 50 else "danger",
- unit_total=self.report_data["unit_tests"].get("total", 0),
- unit_passed=self.report_data["unit_tests"].get("passed", 0),
- unit_failed=self.report_data["unit_tests"].get("failed", 0),
- unit_pass_rate=0,
- integration_total=self.report_data["integration_tests"].get("total", 0),
- integration_passed=self.report_data["integration_tests"].get("passed", 0),
- integration_failed=self.report_data["integration_tests"].get("failed", 0),
- integration_pass_rate=0,
- e2e_total=self.report_data["e2e_tests"].get("total", 0),
- e2e_passed=self.report_data["e2e_tests"].get("passed", 0),
- e2e_failed=self.report_data["e2e_tests"].get("failed", 0),
- e2e_pass_rate=0,
- bug_count=len(self.report_data.get("bugs", [])),
- bug_items=bug_items if bug_items else "暂无Bug",
- timestamp=self.report_data["timestamp"],
- version=self.report_data["version"]
- )
-
- report_path = self.report_dir / f"test_report_{self.timestamp}.html"
- with open(report_path, "w", encoding="utf-8") as f:
- f.write(html)
-
- print(f"✓ HTML报告已生成: {report_path}")
- return report_path
-
- def generate_json_report(self):
- """生成JSON测试报告"""
- json_path = self.report_dir / f"test_report_{self.timestamp}.json"
-
- with open(json_path, "w", encoding="utf-8") as f:
- json.dump(self.report_data, f, ensure_ascii=False, indent=2)
-
- print(f"✓ JSON报告已生成: {json_path}")
- return json_path
-
- def generate_all_reports(self):
- """生成所有报告"""
- print("\n" + "=" * 60)
- print("🚀 开始生成测试报告...")
- print("=" * 60)
-
- # 运行各类测试
- self.run_unit_tests()
- self.run_integration_tests()
- self.run_coverage_tests()
- self.run_security_tests()
-
- # 收集Bug
- self.collect_bugs()
-
- # 生成报告
- html_report = self.generate_html_report()
- json_report = self.generate_json_report()
-
- print("\n" + "=" * 60)
- print("✅ 测试报告生成完成!")
- print("=" * 60)
- print(f"\n📄 HTML报告: {html_report}")
- print(f"📄 JSON报告: {json_report}")
- print(f"📄 覆盖率报告: {self.report_dir}/htmlcov/index.html")
- print(f"📄 单元测试报告: {self.report_dir}/unit_test_report.html")
- print(f"📄 集成测试报告: {self.report_dir}/integration_test_report.html")
- print(f"📄 安全测试报告: {self.report_dir}/security_test_report.html")
- print("\n" + "=" * 60)
-
-
-if __name__ == "__main__":
- import sys
-
- # 项目根目录
- project_root = sys.argv[1] if len(sys.argv) > 1 else "."
-
- # 生成测试报告
- generator = TestReportGenerator(project_root)
- generator.generate_all_reports()
diff --git a/tests/security/test_security.py b/tests/security/test_security.py
deleted file mode 100644
index 2c3077a..0000000
--- a/tests/security/test_security.py
+++ /dev/null
@@ -1,524 +0,0 @@
-"""
-安全测试
-
-测试内容:
-- SQL注入测试
-- XSS测试
-- CSRF测试
-- 权限绕过测试
-- 敏感数据泄露测试
-- 认证绕过测试
-"""
-
-import pytest
-
-
-# class TestSQLInjection:
-# """测试SQL注入攻击"""
-#
-# def test_sql_injection_in_login(self, client: TestClient):
-# """测试登录接口的SQL注入"""
-# malicious_inputs = [
-# "admin' OR '1'='1",
-# "admin'--",
-# "admin'/*",
-# "' OR 1=1--",
-# "'; DROP TABLE users--",
-# "admin' UNION SELECT * FROM users--",
-# "' OR '1'='1' /*",
-# "1' AND 1=1--",
-# "admin'; INSERT INTO users VALUES--",
-# ]
-#
-# for malicious_input in malicious_inputs:
-# response = client.post(
-# "/api/v1/auth/login",
-# json={
-# "username": malicious_input,
-# "password": "Test123",
-# "captcha": "1234",
-# "captcha_key": "test"
-# }
-# )
-#
-# # 应该返回认证失败,而不是数据库错误或成功登录
-# assert response.status_code in [401, 400, 422]
-#
-# # 如果返回成功,说明存在SQL注入漏洞
-# if response.status_code == 200:
-# pytest.fail(f"SQL注入漏洞检测: {malicious_input}")
-#
-# def test_sql_injection_in_search(self, client: TestClient, auth_headers):
-# """测试搜索接口的SQL注入"""
-# malicious_inputs = [
-# "'; DROP TABLE assets--",
-# "1' OR '1'='1",
-# "'; SELECT * FROM users--",
-# "admin' UNION SELECT * FROM assets--",
-# ]
-#
-# for malicious_input in malicious_inputs:
-# response = client.get(
-# "/api/v1/assets",
-# params={"keyword": malicious_input},
-# headers=auth_headers
-# )
-#
-# # 应该正常返回或参数错误,不应该报数据库错误
-# assert response.status_code in [200, 400, 422]
-#
-# def test_sql_injection_in_id_parameter(self, client: TestClient, auth_headers):
-# """测试ID参数的SQL注入"""
-# malicious_ids = [
-# "1 OR 1=1",
-# "1; DROP TABLE assets--",
-# "1' UNION SELECT * FROM users--",
-# "1' AND 1=1--",
-# ]
-#
-# for malicious_id in malicious_ids:
-# response = client.get(
-# f"/api/v1/assets/{malicious_id}",
-# headers=auth_headers
-# )
-#
-# # 应该返回404或参数错误
-# assert response.status_code in [404, 400, 422]
-#
-# def test_sql_injection_in_order_by(self, client: TestClient, auth_headers):
-# """测试排序参数的SQL注入"""
-# malicious_inputs = [
-# "id; DROP TABLE users--",
-# "id OR 1=1",
-# "id' AND '1'='1",
-# ]
-#
-# for malicious_input in malicious_inputs:
-# response = client.get(
-# "/api/v1/assets",
-# params={"sort_by": malicious_input},
-# headers=auth_headers
-# )
-#
-# # 应该返回参数错误
-# assert response.status_code in [400, 422]
-#
-# def test_second_order_sql_injection(self, client: TestClient, auth_headers):
-# """测试二阶SQL注入"""
-# # 先创建包含恶意代码的数据
-# malicious_data = {
-# "asset_name": "test'; DROP TABLE assets--",
-# "device_type_id": 1,
-# "organization_id": 1
-# }
-#
-# create_response = client.post(
-# "/api/v1/assets",
-# headers=auth_headers,
-# json=malicious_data
-# )
-#
-# # 如果创建成功,尝试查询
-# if create_response.status_code == 200:
-# # 查询应该不会触发SQL注入
-# response = client.get(
-# "/api/v1/assets",
-# headers=auth_headers
-# )
-# assert response.status_code == 200
-
-
-# class TestXSS:
-# """测试XSS跨站脚本攻击"""
-#
-# def test_xss_in_asset_name(self, client: TestClient, auth_headers):
-# """测试资产名称的XSS"""
-# xss_payloads = [
-# "",
-# "
",
-# "