# 客户处方参数复用与订单定制履约拆解方案

## 一、背景

会议纪要中提到：

1. 红人寄样订单也需要走定制生产过程。
2. 红人寄样意味着 ERP 侧也需要填写处方参数。
3. 订单创建时需要能复用客户已有处方，也允许针对当前订单改写处方。
4. 后续定制履约需要根据订单处方参数拆解成镜框、左镜片、右镜片等履约部件。

现有系统已经具备部分基础能力：

1. `optical_prescription`：客户处方档案表，支持客户、订单、订单明细、定制履约记录关联。
2. `OpticalPrescriptionController`：继承通用 CRUD，可维护处方档案。
3. `sale_order_detail.extra`：订单明细扩展参数，编辑后会自动抽取处方。
4. `custom_order_item.params_snapshot`：定制履约主参数快照，编辑后会自动抽取处方。
5. `custom_item_component.params_snapshot`：部件参数快照，镜片部件会保存左右眼处方参数。
6. `OpticalPrescriptionExtractService`：可从订单明细或定制履约快照抽取处方到 `optical_prescription`。

当前缺口是：还没有一个面向 ERP 人工下单、红人寄样订单的专用流程，把“维护客户处方 -> 订单创建复用或改写 -> 生成订单处方快照 -> 拆解定制履约部件”串起来。

## 二、核心设计原则

1. 客户处方档案是复用来源，不是生产依据。
2. 订单处方快照才是本次订单生产依据。
3. 订单创建后，即使客户处方档案后续修改，也不能自动影响已创建订单。
4. 订单处方修改需要受履约状态约束，已委外、已生产、已入库后不能直接改写生产参数。
5. 红人寄样订单沿用销售订单和定制履约流程，不新增独立订单模型。
6. 镜片部件拆解、规则匹配、委外参数都从订单处方快照读取。

## 三、数据模型复用

### 3.1 客户处方档案

复用 `optical_prescription`。

关键字段：

| 字段 | 说明 |
| --- | --- |
| `customer_id` | 客户或红人客户ID |
| `sale_order_id` | 关联销售订单，可为空 |
| `sale_order_detail_id` | 关联订单明细，可为空 |
| `custom_order_item_id` | 关联定制履约记录，可为空 |
| `source` | 来源：`manual`、`shopify`、`order`、`import` |
| `prescription_time` | 处方时间 |
| `od_sph`、`od_cyl`、`od_axis`、`od_add` | 右眼参数 |
| `os_sph`、`os_cyl`、`os_axis`、`os_add` | 左眼参数 |
| `pd`、`pd_right`、`pd_left` | 瞳距 |
| `prescription_type` | 处方类型 |
| `lens_group`、`lens_option` | 镜片组和镜片选项 |
| `snapshot` | 原始处方快照 |
| `is_latest` | 是否客户最新处方 |

客户处方档案用于：

1. 客户详情页展示历史处方。
2. 下单时选择历史处方。
3. 红人寄样订单下单时快速带出处方。
4. 客户分析和复购提醒。

### 3.2 订单处方快照

订单处方快照建议保存到两个位置：

| 位置 | 用途 |
| --- | --- |
| `sale_order_detail.extra` | 订单明细层保留本次下单参数 |
| `custom_order_item.params_snapshot` | 定制履约层保留生产依据 |

订单处方快照结构建议：

```json
{
  "source": "erp",
  "industry_type": "optical",
  "prescription_source": {
    "type": "customer_prescription",
    "optical_prescription_id": 12,
    "copied_at": "2026-07-31 10:00:00"
  },
  "prescription": {
    "right": {
      "sphere": -1.25,
      "cylinder": -0.5,
      "axis": 90,
      "add_power": null,
      "pd": 31
    },
    "left": {
      "sphere": -1.5,
      "cylinder": -0.25,
      "axis": 80,
      "add_power": null,
      "pd": 31
    },
    "pd": 62,
    "pd_right": 31,
    "pd_left": 31,
    "prescription_type": "single_vision",
    "submission_method": "erp_manual"
  },
  "lens": {
    "group": "防蓝光",
    "option": "1.60",
    "type": "prescription_lens"
  }
}
```

### 3.3 部件参数快照

拆解部件时，镜片部件需要从订单处方快照拆分左右眼参数：

| 部件 | params_snapshot |
| --- | --- |
| 镜框 `frame` | 保存镜框产品、尺寸、颜色等参数 |
| 左镜片 `lens_left` | 保存 `prescription.left` 和镜片选项 |
| 右镜片 `lens_right` | 保存 `prescription.right` 和镜片选项 |
| 包材 `package` | 保存包装、发货所需参数 |

镜片供应商、委外单展示的定制参数，应来自 `custom_item_component.params_snapshot` 和 `entrust.custom_params`。

## 四、业务流程

### 4.1 维护客户处方参数

入口：客户详情页或红人详情页。

复用接口：

```http
POST /admin/optical-prescription/add
POST /admin/optical-prescription/edit
GET /admin/optical-prescription/index?customer_id=xxx
GET /admin/optical-prescription/detail?id=xxx
```

维护内容：

1. 左右眼球镜、柱镜、轴位、ADD。
2. 单眼或双眼瞳距。
3. 处方类型。
4. 镜片组和镜片选项。
5. 处方时间。
6. 原始上传或备注信息可放入 `snapshot`。

说明：

1. 新增客户处方时，`source = manual`。
2. 系统会自动刷新客户最新处方 `is_latest`。
3. 这一步只维护客户档案，不直接生成订单履约部件。

### 4.2 创建订单时复用客户处方

ERP 创建普通订单或红人寄样订单时，前端提供两个选择：

1. 使用客户最新处方。
2. 选择客户历史处方。
3. 手动输入本订单处方。

建议订单创建接口支持参数：

```json
{
  "customer_id": 1001,
  "type": "influencer_sample",
  "detail_list": [
    {
      "product_id": 3,
      "num": 1,
      "optical": {
        "prescription_mode": "use_customer_prescription",
        "optical_prescription_id": 12,
        "override_prescription": null,
        "save_as_customer_prescription": false,
        "lens": {
          "group": "防蓝光",
          "option": "1.60",
          "type": "prescription_lens"
        }
      }
    }
  ]
}
```

`prescription_mode` 说明：

| 值 | 说明 |
| --- | --- |
| `use_customer_latest` | 使用客户最新处方 |
| `use_customer_prescription` | 使用指定处方 |
| `manual_override` | 手动填写本订单处方 |

如果 `save_as_customer_prescription = true`，则手动填写的订单处方同时保存为一条新的客户处方档案。

### 4.3 生成订单处方快照

订单创建或订单明细保存后，需要把选中的处方转换成订单快照：

1. 写入 `sale_order_detail.extra.optical`。
2. 写入或生成 `custom_order_item.params_snapshot`。
3. 调用 `OpticalPrescriptionExtractService` 抽取或更新 `optical_prescription` 的订单关联记录。

注意：

1. `sale_order_detail.extra` 保存下单原始参数。
2. `custom_order_item.params_snapshot` 保存履约生产参数。
3. 两者可以结构一致，但履约层必须有可直接拆解部件的标准格式。

### 4.4 根据处方快照拆解定制履约部件

当订单明细是定制眼镜产品时，系统生成：

1. `custom_order_item`
2. `custom_item_component`
3. `custom_item_process`

拆解逻辑：

1. 根据订单产品找到定制流程模板。
2. 根据模板生成部件清单。
3. 镜框部件默认取订单商品或模板默认产品。
4. 左镜片部件读取 `params_snapshot.prescription.left`。
5. 右镜片部件读取 `params_snapshot.prescription.right`。
6. 镜片部件根据 `lens.group`、`lens.option`、度数、散光、防蓝光等属性匹配 `hg_optical_component_product_rule`。
7. 匹配成功后写入部件 `product_id`、`supplier_id`。
8. 匹配失败时部件标记异常，订单层 `custom_abnormal_status = 1`。

## 五、服务设计

### 5.1 新增服务

建议新增：

```text
App\Service\Common\OpticalOrderPrescriptionService
```

职责：

| 方法 | 说明 |
| --- | --- |
| `getCustomerPrescriptionOptions(int $customerId)` | 查询客户可复用处方 |
| `buildOrderPrescriptionSnapshot(array $input, int $customerId)` | 根据客户处方或手动输入生成订单处方快照 |
| `saveOrderDetailPrescription(SaleOrderDetail $detail, array $snapshot)` | 写入订单明细 `extra` |
| `saveCustomOrderItemPrescription(CustomOrderItem $item, array $snapshot)` | 写入定制履约 `params_snapshot` |
| `saveAsCustomerPrescription(array $snapshot, int $customerId)` | 手动输入时保存为客户处方档案 |
| `assertCanChangeOrderPrescription(CustomOrderItem $item)` | 校验当前履约状态是否允许改处方 |

### 5.2 拆解服务调整

现有 Shopify 拉单中，镜片拆解逻辑在 `ThirdOrder` 内部，建议把通用逻辑抽出来，供 ERP 手动订单复用。

建议新增：

```text
App\Service\Common\OpticalCustomFulfillmentBuildService
```

职责：

| 方法 | 说明 |
| --- | --- |
| `buildFromSaleOrderDetail(SaleOrderDetail $detail)` | 根据订单明细生成定制履约 |
| `buildFromCustomOrderItem(CustomOrderItem $item)` | 根据履约快照生成部件和工序 |
| `buildComponentSnapshot(string $componentType, array $snapshot)` | 生成部件参数快照 |
| `matchComponentProduct(CustomItemComponent $component, array $snapshot)` | 调用规则匹配部件产品和供应商 |
| `rebuildBeforePrepare(CustomOrderItem $item)` | 未进入履约准备前重新拆解 |

长期建议将 `ThirdOrder` 中 Shopify 专用的：

1. `normalizeShopifyEyewearPrescription`
2. `buildShopifyOpticalItemSnapshot`
3. `buildShopifyOpticalComponentSnapshot`
4. `createShopifyOpticalComponents`

拆成通用的光学订单快照和部件构建服务。Shopify 只是输入来源之一，ERP 手动订单、红人寄样订单也可以复用。

## 六、接口设计

### 6.1 查询客户处方

```http
GET /admin/customer/{customer_id}/optical-prescriptions
```

也可以先复用：

```http
GET /admin/optical-prescription/index?customer_id=1001&sort=prescription_time&order=desc
```

返回重点：

```json
{
  "id": 12,
  "is_latest": 1,
  "prescription_time": "2026-07-31 10:00:00",
  "od_sph": -1.25,
  "od_cyl": -0.5,
  "od_axis": 90,
  "os_sph": -1.5,
  "os_cyl": -0.25,
  "os_axis": 80,
  "pd": 62,
  "pd_right": 31,
  "pd_left": 31,
  "prescription_type": "single_vision",
  "lens_group": "防蓝光",
  "lens_option": "1.60"
}
```

### 6.2 保存订单处方

建议新增：

```http
POST /admin/sale-order-detail/saveOpticalPrescription
```

请求：

```json
{
  "sale_order_detail_id": 2001,
  "prescription_mode": "use_customer_prescription",
  "optical_prescription_id": 12,
  "prescription": null,
  "lens": {
    "group": "防蓝光",
    "option": "1.60",
    "type": "prescription_lens"
  },
  "save_as_customer_prescription": false,
  "rebuild_custom_fulfillment": true
}
```

响应：

```json
{
  "sale_order_detail_id": 2001,
  "custom_order_item_id": 3001,
  "optical_prescription_id": 12,
  "component_count": 3,
  "component_abnormal_count": 0
}
```

### 6.3 保存定制履约处方

如果订单明细已经生成 `custom_order_item`，也可以提供：

```http
POST /admin/custom-order-item/saveOpticalPrescription
```

请求：

```json
{
  "custom_order_item_id": 3001,
  "prescription_mode": "manual_override",
  "prescription": {
    "right": {
      "sphere": -1.25,
      "cylinder": -0.5,
      "axis": 90,
      "add_power": null,
      "pd": 31
    },
    "left": {
      "sphere": -1.5,
      "cylinder": -0.25,
      "axis": 80,
      "add_power": null,
      "pd": 31
    },
    "pd": 62,
    "pd_right": 31,
    "pd_left": 31,
    "prescription_type": "single_vision"
  },
  "lens": {
    "group": "防蓝光",
    "option": "1.60",
    "type": "prescription_lens"
  },
  "save_as_customer_prescription": true,
  "rebuild_components": true
}
```

## 七、状态约束

订单处方不是任何时候都能改。

| 履约阶段 | 是否允许直接改处方 | 处理方式 |
| --- | --- | --- |
| 未生成 `custom_order_item` | 允许 | 写入订单明细快照，生成履约 |
| 已生成部件，未审核准备 | 允许 | 更新履约快照，重新拆部件和匹配规则 |
| 已审核，未生成委外或采购 | 谨慎允许 | 记录日志，重新匹配部件 |
| 已生成镜片委外，供应商未接单 | 不建议直接改 | 取消原委外，重新生成 |
| 供应商已接单或生产中 | 不允许直接改 | 走异常调整、供应商协商、重派或返工 |
| 镜片已入库 | 不允许直接改 | 走返工或重新委外 |
| 成品已入库或已发货 | 不允许直接改 | 走售后、返工、换货或补发 |

修改处方时必须写入 `custom_process_log`：

| event | 说明 |
| --- | --- |
| `prescription_created` | 创建订单处方 |
| `prescription_changed` | 修改订单处方 |
| `component_rebuilt` | 因处方修改重建部件 |
| `component_rule_rematched` | 因处方修改重新匹配部件规则 |

## 八、红人寄样订单处理

红人本质上仍然是 `Customer` 的一种属性，寄样订单沿用 `sale_order.type` 区分。

红人寄样下单流程：

1. 选择红人客户。
2. 选择寄样产品。
3. 选择客户历史处方或手动填写处方。
4. 系统创建销售订单，订单类型为红人寄样。
5. 明细价格按现有红人寄样规则处理为 0。
6. 保存订单处方快照。
7. 生成 `custom_order_item`。
8. 拆解镜框、左镜片、右镜片等部件。
9. 根据处方和镜片选项匹配镜片产品和供应商。
10. 订单审核通过后进入定制履约流程。

如果红人还没有处方：

1. 前端必须要求填写处方参数。
2. 可勾选保存为客户处方档案。
3. 保存后该处方可用于后续寄样或正式订单。

## 九、与部件规则匹配的关系

`hg_optical_component_product_rule` 不直接读取客户处方档案，而是读取订单处方快照。

原因：

1. 客户处方可能后续变更。
2. 同一客户不同订单可能使用不同处方。
3. 订单生产必须可追溯当时采用的参数。
4. 供应商委外参数必须和订单快照一致。

规则匹配输入建议统一为：

```json
{
  "industry_type": "optical",
  "component_type": "lens_left",
  "prescription": {
    "sphere": -1.5,
    "cylinder": -0.25,
    "axis": 80,
    "add_power": null,
    "pd": 31
  },
  "lens": {
    "group": "防蓝光",
    "option": "1.60",
    "type": "prescription_lens"
  },
  "frame": {
    "product_id": 3,
    "size": "54□18-135",
    "color": "black"
  }
}
```

## 十、实施建议

### 10.1 MVP

1. 复用 `optical_prescription` 提供客户处方维护。
2. 新增保存订单处方接口。
3. 新增 `OpticalOrderPrescriptionService`。
4. 把订单处方写入 `sale_order_detail.extra` 和 `custom_order_item.params_snapshot`。
5. 未进入履约前，根据处方快照生成或重建部件。
6. 镜片部件按订单处方快照匹配规则。
7. 修改处方时写入 `custom_process_log`。

### 10.2 第二阶段

1. 抽出 `OpticalCustomFulfillmentBuildService`，统一 Shopify、ERP、红人寄样三种来源。
2. 增加处方修改状态校验。
3. 增加已委外后的异常调整和返工流程。
4. 前端在订单详情页展示“订单处方快照”和“客户历史处方”对比。

### 10.3 第三阶段

1. 增加处方变更差异分析。
2. 增加客户处方复购提醒。
3. 增加红人寄样处方模板和批量导入。
4. 增加处方上传图片 OCR 或人工录入辅助。

## 十一、结论

客户处方参数应维护在 `optical_prescription`，用于客户档案和下单复用。订单创建时必须把客户处方复制成订单处方快照，保存到 `sale_order_detail.extra` 和 `custom_order_item.params_snapshot`。后续镜片拆解、部件规则匹配、委外参数展示都必须使用订单快照，而不是直接读取客户最新处方。

这样可以同时满足红人寄样 ERP 填写处方、订单复用历史处方、订单改写处方、定制履约部件拆解和生产追溯的需求。
