# 按 external_order_id 同步外部订单快照 API ## 概述 用于前端按 `external_order_id` 手动触发一次“外部订单快照重拉并覆盖更新本地数据”的操作。 - **端点**: `POST /api/v2/printing-orders/sync-external-snapshot/` - **认证**: 需要登录 - **权限**: 印染工厂用户 此接口适用于以下场景: - 外部订单已同步过一次,但后续外部系统又修改了订单数据 - 人工发现本地 `PrintingOrder` / `PrintingJob` 与外部系统不一致 - 需要按单号重新拉取该订单当前完整快照并覆盖到本地 ## 同步行为说明 接口会按以下顺序执行: 1. 根据 `external_order_id` 查找本地目标 `PrintingOrder` 2. 先写入一条同步审计记录,保存同步前本地快照 3. 做覆盖前检查 4. 调用外部快照接口拉取该订单当前完整 `records` 5. 复用现有外部同步映射逻辑,更新或创建本地 `PrintingOrder` / `PrintingJob` 6. 删除“本地存在但外部快照已不存在”的旧 `PrintingJob`(仅在安全条件满足时) ## 覆盖前检查规则 ### 1. 关联销售品时立即失败 如果目标 `PrintingOrder` 下任意 `PrintingJob` 已关联销售品,则接口会立即失败,不会请求外部快照接口。 失败时会: - 返回 `409 Conflict` - 写入失败审计记录 - 在失败原因中明确给出涉及的 `printing_job_ids` 和 `sales_item_ids` ### 2. 已有工序记录时默认失败 如果目标 `PrintingOrder` 下存在任意 `PrintingJob` 已有未撤销的工序记录,则默认失败。 失败时会: - 返回 `409 Conflict` - 写入失败审计记录 - 提示前端可使用 `allow_reset_stateflow=true` 重试 ### 3. `allow_reset_stateflow=true` 的含义 当请求体传入: ```json { "allow_reset_stateflow": true } ``` 接口会先撤销目标订单下所有 `PrintingJob` 的未撤销工序记录,然后再执行覆盖同步。 注意: - 默认值是 `false` - 只有在“没有销售品关联”的前提下才会继续执行 - 该撤销操作只处理工序状态,不会保留当前流程执行进度 ## 请求 ### 请求体 ```json { "external_order_id": "KD20432358", "allow_reset_stateflow": false } ``` ### 字段说明 | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `external_order_id` | string | 是 | - | 外部订单号 | | `allow_reset_stateflow` | boolean | 否 | `false` | 是否允许先撤销目标订单下已有工序再执行覆盖同步 | ### 请求示例 ```bash curl -X POST "http://localhost:8000/api/v2/printing-orders/sync-external-snapshot/" \ -H "Authorization: Token " \ -H "Content-Type: application/json" \ -d '{ "external_order_id": "KD20432358", "allow_reset_stateflow": false }' ``` ## 响应 ### 成功响应 (200 OK) ```json { "detail": "外部订单快照同步成功", "audit_id": 12, "external_order_id": "KD20432358", "printing_order_id": 55, "orders_created": 0, "orders_updated": 1, "jobs_created": 1, "jobs_updated": 2, "jobs_deleted": 1, "reset_stateflow_job_count": 0 } ``` ### 成功响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `detail` | string | 结果说明 | | `audit_id` | int | 同步审计记录ID | | `external_order_id` | string | 本次同步的外部订单号 | | `printing_order_id` | int | 本地印染订单ID | | `orders_created` | int | 是否创建了新订单,`0` 或 `1` | | `orders_updated` | int | 是否更新了已有订单,`0` 或 `1` | | `jobs_created` | int | 新创建的 `PrintingJob` 数量 | | `jobs_updated` | int | 被更新的 `PrintingJob` 数量 | | `jobs_deleted` | int | 被删除的旧 `PrintingJob` 数量 | | `reset_stateflow_job_count` | int | 本次先撤销工序的 `PrintingJob` 数量 | ## 错误响应 ### 1. 参数错误 (400 Bad Request) #### `external_order_id` 为空 ```json { "external_order_id": ["external_order_id 不能为空"] } ``` ### 2. 未认证 (401 Unauthorized) ```json { "detail": "Authentication credentials were not provided." } ``` ### 3. 无权限 (403 Forbidden) 当用户不是印染工厂用户时: ```json { "detail": "您没有访问印染订单的权限" } ``` ### 4. 外部订单不存在 (404 Not Found) ```json { "detail": "未找到该 external_order_id 对应的订单", "audit_id": 13, "external_order_id": "KD-NOT-FOUND" } ``` 说明: - 即使外部未命中,也会创建失败审计记录 - 可通过 `audit_id` 关联后端审计信息 ### 5. 目标订单下已有销售品,禁止覆盖 (409 Conflict) ```json { "detail": "目标订单存在已关联销售品的 printing_job,禁止覆盖同步。 printing_job_ids=[101], sales_item_ids=[201]", "audit_id": 14, "external_order_id": "KD20432358" } ``` 说明: - 这是最优先的阻断条件 - 一旦命中,不会继续请求外部快照接口 ### 6. 目标订单下已有工序记录,默认禁止覆盖 (409 Conflict) ```json { "detail": "目标订单存在已执行工序的 printing_job,默认不允许覆盖同步。 如确认要覆盖,请传 allow_reset_stateflow=true。 printing_job_ids=[101]", "audit_id": 15, "external_order_id": "KD20432358" } ``` 说明: - 当前端收到这类错误时,可以提示用户确认是否改用 `allow_reset_stateflow=true` 再次发起请求 ### 7. 旧明细存在业务引用,无法删除 (409 Conflict) 在外部快照同步阶段,如果发现本地“待删除旧明细”仍被其他业务引用,也会失败: ```json { "detail": "以下待删除 printing_job 已关联销售单明细,禁止覆盖同步: [102]", "audit_id": 16, "external_order_id": "KD20432358" } ``` ## 审计说明 每次请求都会生成一条审计记录,无论成功还是失败。 当前审计记录包含: - `external_order_id` - 操作者 `request.user` - 操作员工 `request.user.employee` - 同步前本地订单与明细快照(JSON) - 本次是否允许撤销工序 - 是否成功 - 失败原因 前端当前只会收到: - `audit_id` 如果后续需要展示详细审计记录,需要后端再补充查询接口。 ## 前端使用建议 ### 普通重同步 默认先使用: ```json { "external_order_id": "KD20432358" } ``` ### 当后端提示存在工序记录时 可以提示用户: - 覆盖同步会先撤销当前流程进度 - 是否继续 用户确认后,再次发送: ```json { "external_order_id": "KD20432358", "allow_reset_stateflow": true } ``` ### 不建议自动重试的情况 遇到以下错误时,前端不应自动重试: - 已关联销售品 - 已关联销售单明细 - 外部订单不存在 这些都应提示人工处理。 ## 前端调用示例 ```javascript async function syncExternalSnapshot(externalOrderId, allowResetStateflow = false) { const response = await fetch('/api/v2/printing-orders/sync-external-snapshot/', { method: 'POST', headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ external_order_id: externalOrderId, allow_reset_stateflow: allowResetStateflow, }), }); const data = await response.json(); if (!response.ok) { const error = new Error(data.detail || '同步失败'); error.status = response.status; error.auditId = data.audit_id; error.externalOrderId = data.external_order_id; throw error; } return data; } // 示例:先按默认模式重同步 try { const result = await syncExternalSnapshot('KD20432358'); console.log('同步成功', result); } catch (error) { // 如果提示需要 allow_reset_stateflow=true,再由用户二次确认 console.error(error.message, error.auditId); } ``` ## 相关文件 - 路由: [api_v2/urls.py](/home/f/coding/flower/api_v2/urls.py) - 视图: [api_v2/views/printing.py](/home/f/coding/flower/api_v2/views/printing.py) - 核心同步逻辑: [api_v1/tasks.py](/home/f/coding/flower/api_v1/tasks.py) - 审计模型: [printing/models.py](/home/f/coding/flower/printing/models.py)