# Design Document ## Overview 本设计文档描述了从样品库直接生成测试工单的功能实现。该功能将简化原有的"订单→产品→工单"流程,改为"样品→工单"的直接流程。 核心变化: - 前端:在样品管理页面添加"生成工单"功能 - 后端:创建新的API接口,直接从样品信息生成工单 - 数据关联:工单的test_eut_id字段将直接关联warehouse_sample表的sample_id ## Architecture ### 系统架构 ``` 前端层 (Vue.js) └── 样品管理页面 (warehouse/sample/index.vue) ├── 样品列表展示 ├── 样品选择(多选) └── 生成工单对话框 ├── 测试流程选择 ├── 工单名称输入 └── 备注输入 API层 (FastAPI) └── 样品工单控制器 (warehouse_sample_controller.py) └── POST /warehouse/sample/generate_work_orders ├── 接收样品ID列表 ├── 接收测试流程ID └── 返回生成结果 服务层 (Service) └── 样品工单服务 (warehouse_sample_service.py) └── generate_work_orders_from_samples() ├── 验证样品状态 ├── 获取测试流程配置 ├── 为每个样品创建工单 └── 更新样品状态 数据层 (DAO) └── 工单数据访问 (test_work_order_dao.py) └── 批量创建工单记录 ``` ### 数据流 ``` 用户选择样品 → 选择测试流程 → 提交请求 ↓ 验证样品状态 → 获取流程配置 → 查询测试单元 ↓ 创建工单记录 → 更新样品状态 → 返回结果 ``` ## Components and Interfaces ### 前端组件 #### 1. 样品管理页面增强 (warehouse/sample/index.vue) **新增数据字段:** ```javascript { // 工单生成对话框 workOrderDialogVisible: false, // 工单表单 workOrderForm: { sampleIds: [], // 选中的样品ID列表 testFlowId: null, // 测试流程ID workOrderName: '', // 工单名称 memo: '' // 备注 }, // 测试流程选项 testFlowOptions: [], // 测试流程标签 testFlowTags: [] } ``` **新增方法:** ```javascript // 打开生成工单对话框 handleGenerateWorkOrder() // 加载测试流程选项 loadTestFlowOptions() // 获取测试流程标签 fetchTestFlowTags(flowId) // 提交工单生成 submitWorkOrderGeneration() ``` ### 后端接口 #### 1. 样品工单生成API **端点:** `POST /warehouse/sample/generate_work_orders` **请求体:** ```json { "sampleIds": [1, 2, 3], "testFlowId": 1, "workOrderName": "批次测试", "memo": "紧急测试" } ``` **响应:** ```json { "code": 200, "msg": "操作成功", "data": { "successCount": 3, "failedCount": 0, "workOrderIds": [101, 102, 103], "failedSamples": [] } } ``` #### 2. 测试流程查询API(复用现有) **端点:** `GET /system/test_flow/list` **响应:** ```json { "code": 200, "data": [ { "id": 1, "name": "标准测试流程", "tags": [...] } ] } ``` ## Data Models ### 样品工单生成请求模型 ```python class GenerateWorkOrderFromSampleModel(BaseModel): """从样品生成工单的请求模型""" model_config = ConfigDict(alias_generator=to_camel) sample_ids: List[int] = Field(description='样品ID列表') test_flow_id: int = Field(description='测试流程ID') work_order_name: Optional[str] = Field(default=None, description='工单名称') memo: Optional[str] = Field(default=None, description='备注') ``` ### 工单生成响应模型 ```python class WorkOrderGenerationResponseModel(BaseModel): """工单生成响应模型""" model_config = ConfigDict(alias_generator=to_camel) success_count: int = Field(description='成功数量') failed_count: int = Field(description='失败数量') work_order_ids: List[int] = Field(description='生成的工单ID列表') failed_samples: List[dict] = Field(description='失败的样品信息') ``` ### 数据库表关系 ``` warehouse_sample (样品表) ├── sample_id (主键) ├── receipt_id (入库单ID) ├── sample_sn (样品SN号) ├── sample_model (样品型号) ├── hardware_version (硬件版本) └── status (状态: 0-待测试, 1-测试中, 2-已完成, 3-已退回) test_work_order (测试工单表) ├── id (主键) ├── test_eut_id (产品ID) → 关联 warehouse_sample.sample_id ├── test_category_id (测试类别ID) ├── test_item_id (测试单元ID) ├── tester_id (测试人ID) ├── reviewer_id (审核人ID) ├── test_status (测试状态) └── test_step (测试步骤) test_flow (测试流程表) ├── id (主键) └── name (流程名称) test_flow_tags (流程标签关联表) ├── test_flow_id (流程ID) └── test_category_id (测试类别ID) test_item (测试单元表) ├── id (主键) ├── test_category_id (测试类别ID) └── eut_type_id (产品类型ID) ``` ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: 工单创建完整性 *For any* 有效的样品ID列表和测试流程ID,系统应为每个样品创建与测试流程中测试类别数量相等的工单数 **Validates: Requirements 2.3** ### Property 2: 样品状态一致性 *For any* 成功生成工单的样品,其状态应从"待测试"(0)更新为"测试中"(1) **Validates: Requirements 4.4** ### Property 3: 工单数据关联正确性 *For any* 生成的工单,其test_eut_id字段应等于对应样品的sample_id **Validates: Requirements 3.2** ### Property 4: 测试类别映射正确性 *For any* 测试流程,生成的工单的test_category_id应属于该流程的test_flow_tags集合 **Validates: Requirements 2.2** ### Property 5: 批量操作原子性 *For any* 样品列表中的单个样品工单创建失败,不应影响其他样品的工单创建 **Validates: Requirements 2.4** ### Property 6: 工单命名规则一致性 *For any* 未指定工单名称的请求,生成的工单名称应遵循"样品SN + 测试类别"的格式 **Validates: Requirements 5.5** ## Error Handling ### 错误类型 1. **样品不存在错误** - 错误码:404 - 消息:样品ID不存在 - 处理:跳过该样品,继续处理其他样品 2. **样品状态错误** - 错误码:400 - 消息:样品状态不允许生成工单 - 处理:记录失败原因,继续处理其他样品 3. **测试流程不存在错误** - 错误码:404 - 消息:测试流程不存在 - 处理:终止整个操作,返回错误 4. **测试单元未配置错误** - 错误码:400 - 消息:该样品型号未配置测试单元 - 处理:跳过该样品,记录警告 5. **数据库操作错误** - 错误码:500 - 消息:工单创建失败 - 处理:回滚事务,返回错误详情 ### 错误响应格式 ```json { "code": 400, "msg": "部分样品工单生成失败", "data": { "successCount": 2, "failedCount": 1, "workOrderIds": [101, 102], "failedSamples": [ { "sampleId": 3, "sampleSn": "SN003", "reason": "样品状态不允许生成工单" } ] } } ``` ## Testing Strategy ### Unit Tests 1. **样品状态验证测试** - 测试待测试状态样品可以生成工单 - 测试其他状态样品被正确拒绝 2. **测试流程配置查询测试** - 测试正确获取流程的测试类别 - 测试流程不存在时的错误处理 3. **工单数据构造测试** - 测试工单字段正确映射样品信息 - 测试工单命名规则 ### Property-Based Tests 使用Python的`hypothesis`库进行属性测试: 1. **Property 1测试:工单创建完整性** ```python @given( sample_ids=st.lists(st.integers(min_value=1), min_size=1, max_size=10), test_flow_id=st.integers(min_value=1) ) async def test_work_order_creation_completeness(sample_ids, test_flow_id): """测试为每个样品创建正确数量的工单""" # 生成工单 result = await generate_work_orders_from_samples(sample_ids, test_flow_id) # 获取流程的测试类别数量 category_count = await get_test_category_count(test_flow_id) # 验证:成功的样品数 * 测试类别数 = 工单数 assert len(result.work_order_ids) == result.success_count * category_count ``` 2. **Property 2测试:样品状态一致性** ```python @given(sample_ids=st.lists(st.integers(min_value=1), min_size=1)) async def test_sample_status_consistency(sample_ids): """测试成功生成工单后样品状态更新""" # 生成工单 result = await generate_work_orders_from_samples(sample_ids, test_flow_id=1) # 验证:所有成功的样品状态都是"测试中" for sample_id in sample_ids[:result.success_count]: sample = await get_sample_by_id(sample_id) assert sample.status == '1' ``` 3. **Property 3测试:工单数据关联正确性** ```python @given(sample_id=st.integers(min_value=1)) async def test_work_order_sample_association(sample_id): """测试工单正确关联样品""" # 生成工单 result = await generate_work_orders_from_samples([sample_id], test_flow_id=1) # 验证:所有工单的test_eut_id都等于sample_id for work_order_id in result.work_order_ids: work_order = await get_work_order_by_id(work_order_id) assert work_order.test_eut_id == sample_id ``` ### Integration Tests 1. **端到端工单生成测试** - 创建测试样品 - 调用工单生成API - 验证工单创建成功 - 验证样品状态更新 2. **批量操作测试** - 创建多个测试样品 - 批量生成工单 - 验证部分失败场景 3. **前端集成测试** - 测试对话框打开和关闭 - 测试表单验证 - 测试API调用和响应处理