# 定制履约关键节点完成时间接口文档

## 1. SQL 变更

SQL 已记录在：

```text
sql/2026-08.sql
```

本次新增表字段：

```text
hg_custom_order_item.frame_ready_plan_time
hg_custom_order_item.frame_ready_finish_time
hg_custom_order_item.lens_finish_plan_time
hg_custom_order_item.lens_finish_time
hg_custom_order_item.glasses_qc_plan_time
hg_custom_order_item.glasses_qc_finish_time
hg_custom_order_item.logistics_ship_plan_time
hg_custom_order_item.logistics_ship_time
hg_custom_order_item.node_time_calculated_at
hg_custom_order_item.node_time_version
hg_custom_order_item.node_time_rule_snapshot
```

## 2. 订单审核接口

### 接口

沿用现有销售订单审核接口：

```text
POST /admin/sale-order/audit
```

批量审核接口：

```text
POST /admin/sale-order/audits
```

### 变更

销售订单审核通过后，系统会在定制履约数据生成完成后自动初始化 4 个关键节点计划完成时间。

执行链路：

```text
SaleOrder::afterAuditData()
  -> CustomFulfillmentPrepareService::prepareBySaleOrder()
  -> CustomFulfillmentNodeTimeService::initializeBySaleOrder()
```

### 写入字段

| 字段 | 说明 |
| --- | --- |
| `frame_ready_plan_time` | 镜框准备计划完成时间 |
| `lens_finish_plan_time` | 镜片完成加工计划完成时间 |
| `glasses_qc_plan_time` | 眼镜完成组装质检计划完成时间 |
| `logistics_ship_plan_time` | 物流发货计划完成时间 |
| `node_time_calculated_at` | 计算时间 |
| `node_time_version` | 时间版本，默认 1 |
| `node_time_rule_snapshot` | 计算时使用的 SLA 快照 |

计划时间已存在时默认不覆盖。

## 3. 定制履约单元列表/详情

### 接口

沿用 `custom_order_item` 管理接口：

```text
GET /admin/custom-order-item/index
GET /admin/custom-order-item/detail
```

### 返回新增字段

| 字段 | 说明 |
| --- | --- |
| `frame_ready_plan_time` | 镜框准备计划完成时间 |
| `frame_ready_finish_time` | 镜框准备实际完成时间 |
| `lens_finish_plan_time` | 镜片完成加工计划完成时间 |
| `lens_finish_time` | 镜片完成加工实际完成时间 |
| `glasses_qc_plan_time` | 眼镜完成组装质检计划完成时间 |
| `glasses_qc_finish_time` | 眼镜完成组装质检实际完成时间 |
| `logistics_ship_plan_time` | 物流发货计划完成时间 |
| `logistics_ship_time` | 物流发货实际完成时间 |
| `node_time_calculated_at` | 节点时间初始化时间 |
| `node_time_version` | 节点时间版本 |
| `node_time_rule_snapshot` | SLA 规则快照 |

前端逾期判断：

```text
当前时间 > 节点计划完成时间
and 节点实际完成时间为空
```

## 4. 质检接口

### 接口

```text
POST /admin/sale-order/customQcPass
```

### 入参

沿用现有参数：

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `id` / `sale_order_id` | 是 | 销售订单 ID |
| `custom_order_item_id` | 否 | 指定某个定制履约单元 |
| `qc_passed` / `is_pass` / `passed` | 否 | 是否质检通过，默认通过 |
| `remark` / `reason` / `abnormal_reason` | 否 | 质检不通过备注；不通过时必填 |
| `auto_stock_in` | 否 | 通过后是否自动成品入库，默认 true |
| `reject_type` | 否 | 不通过处理方式，默认 `rework` |
| `rollback_process_type` | 否 | 返工回退工序，默认 `assemble` |

### 变更

质检通过后，工序完成钩子会同步：

```text
custom_order_item.glasses_qc_finish_time
```

质检不通过时不写入 `glasses_qc_finish_time`。

## 5. 物流扫码发货接口

### 接口

```text
POST /admin/custom-logistics/scanShip
```

### 入参

沿用现有参数：

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `barcode` | 是 | 快递单号、平台订单号、发货计划编码或发货计划快递号 |

### 变更

扫码发货成功后，系统会按销售订单维度回写所有定制履约单元：

```text
custom_order_item.logistics_ship_time
```

回写时间取扫码发货时的系统时间。

除扫码发货外，以下发货路径也会同步该字段：

1. `DeliveryPlan::finish()` 发货计划完成，按发货计划明细中的成品 `product_id` 回写对应履约单元。
2. `SaleOrder::refreshDeliveryStatus()` 刷新后订单达到已发货。

## 6. 自动回写事件

| 节点 | 实际完成时间字段 | 触发事件 |
| --- | --- | --- |
| 镜框准备 | `frame_ready_finish_time` | 镜框库存锁定、镜框采购/补货后部件完成 |
| 镜片完成加工 | `lens_finish_time` | 镜片委外接收入库，左右镜片均完成 |
| 眼镜完成组装质检 | `glasses_qc_finish_time` | `qc` 工序质检通过 |
| 物流发货 | `logistics_ship_time` | 定制物流扫码发货成功、发货计划完成、订单刷新为已发货 |

## 7. SLA 配置

系统默认 SLA 写在代码中，可通过公司配置覆盖：

```text
custom_fulfillment_node_sla_hours
```

配置格式：

```json
{
  "frame_stock_ready_hours": 0,
  "frame_purchase_hours": 72,
  "frame_production_hours": 168,
  "frame_unknown_hours": 120,
  "lens_production_hours": 72,
  "assemble_hours": 24,
  "qc_hours": 8,
  "finished_stock_in_hours": 4,
  "ship_hours": 24,
  "outsource_assemble_accept_hours": 24,
  "outsource_assemble_produce_hours": 48,
  "outsource_assemble_stock_in_hours": 72
}
```

审核时会把实际使用的 SLA 写入 `node_time_rule_snapshot`，后续调整配置不会影响已经初始化过的订单计划时间。
