# 定制履约订单推进实施建议

## 背景

当前 Shopify 眼镜定制订单已完成以下能力：

1. 自动建立或匹配客户档案。
2. 创建销售订单 `sale_order` 和销售明细 `sale_order_detail`。
3. 解析 Shopify line item 中的 `_Eyewear prescription` 等眼镜定制属性。
4. 生成定制履约数据：
   - `custom_order_item`
   - `custom_item_component`
   - `custom_item_process`
   - `custom_process_log`
5. 销售订单审核通过后，触发 `CustomFulfillmentPrepareService`：
   - `custom_order_item.status: 0 -> 10`
   - 首批 `custom_item_process.status: 0 -> 10`
   - 写入履约日志。

当前服务只完成“履约准备态推进”，还没有生成真实执行单据。

## 当前缺口

订单会停留在：

```text
custom_order_item.status = 10       准备中
custom_item_process.status = 10     首批工序进行中
```

还没有实现：

```text
供应商匹配
委外单生成
采购单生成
生产工单生成
库存冻结
质检单生成
入库/出库单生成
业务单据状态回写
下一工序自动启动
```

因此下一阶段要实现“执行单据生成与状态推进”。

## 总体原则

`custom_*` 表只作为履约编排层。

真实业务动作仍由现有 ERP 单据承载：

```text
purchase_order / purchase_order_detail     采购
entrust                                    委外
production_receipt / production_receipt_process 生产
qc_receipt / qc_receipt_detail             质检
repository_receipt / repository_receipt_detail 入库/出库
repository_freeze_log                      库存冻结
```

两层之间通过以下字段关联：

```text
custom_item_process.ref_module
custom_item_process.ref_id
custom_item_component.purchase_order_detail_id
custom_item_component.entrust_id
custom_item_component.production_receipt_id
custom_item_component.repository_freeze_log_id
custom_item_component.repository_receipt_detail_id
```

## 推荐实施顺序

### 1. 先解决供应商来源

委外生产必须有供应商。当前 Shopify 订单只提供镜片参数，不提供供应商 ID，因此不能直接生成委外单。

短期方案：

```text
在 custom_item_component.supplier_id 或 custom_item_process.supplier_id 上人工指定供应商。
```

适用场景：

- 初期只有少量固定镜片供应商。
- 运营人员可以手工维护。
- 先跑通履约闭环。

中长期方案：

新增眼镜供应商能力表，例如：

```text
optical_supplier_capability
```

用于按镜片参数匹配供应商：

```text
lens_group
lens_option
lens_type
lens_index
coating
prescription_type
delivery_lead_hours
supplier_id
```

供应商选择优先级建议：

```text
1. custom_item_process.supplier_id
2. custom_item_component.supplier_id
3. optical_supplier_capability 自动匹配
4. 无法匹配则标记异常
```

无法匹配供应商时：

```text
custom_order_item.status = 80
custom_item_process.status = 80
custom_process_log.event = abnormal
abnormal_reason = 未匹配到镜片委外供应商
```

### 2. 实现执行单据生成器

建议新增服务：

```text
App\Service\Common\CustomFulfillmentExecutionService
```

核心入口：

```php
public function executeProcess(CustomItemProcess $process, ?int $adminId = null): void
```

职责：

```text
根据 custom_item_process.process_type 生成对应业务单据。
将业务单据 ID 回写到 custom_item_process.ref_module/ref_id。
将部件关联字段同步回写。
写 custom_process_log。
```

推荐分发：

```text
frame_prepare  -> 库存冻结 / 采购镜框
lens_outsource -> 委外单 entrust
lens_stock_in  -> 委外入库 / 入库单
assemble       -> 组装工序 / 生产工序
qc             -> 质检单
stock_in       -> 成品入库单
ship           -> 出库 / 发货
```

### 3. 先实现最小闭环：镜片委外

眼镜业务中，最关键的第一步是镜片委外。

目标流程：

```text
custom_item_process.process_type = lens_outsource
  -> 获取供应商 supplier_id
  -> 生成 entrust
  -> 回写 custom_item_process.ref_module = entrust
  -> 回写 custom_item_process.ref_id = entrust.id
  -> 回写 lens_left / lens_right component.entrust_id = entrust.id
  -> 写 custom_process_log
```

供应商来源：

```text
process.supplier_id
component.supplier_id
optical_supplier_capability 匹配结果
```

委外单建议字段映射：

```text
entrust.company_id      = custom_order_item.company_id
entrust.supplier_id     = 匹配到的供应商
entrust.product_id      = 镜片产品 ID，如暂未维护可为空或使用默认镜片产品
entrust.num             = 2 或按左右镜片部件数量计算
entrust.sale_order_id   = custom_order_item.sale_order_id
entrust.create_admin_id = 当前操作人
```

回写：

```text
custom_item_process.ref_module = entrust
custom_item_process.ref_id = entrust.id
custom_item_process.status = 10

custom_item_component.entrust_id = entrust.id
custom_item_component.status = 30
```

### 4. 实现业务单据状态回写

业务单据完成后，需要回写履约状态。

建议新增服务：

```text
App\Service\Common\CustomFulfillmentStatusSyncService
```

核心入口：

```php
public function syncByRef(string $refModule, int $refId): void
```

委外完成时：

```text
entrust 完成 / 委外入库完成
  -> custom_item_process.status = 20
  -> lens_left.status = 50
  -> lens_right.status = 50
  -> 写 custom_process_log
  -> 尝试启动下一工序
```

状态回写规则建议：

```text
purchase_order 完成入库
  -> 采购工序 status = 20
  -> 对应 component.status = 50

entrust 完成入库
  -> 委外工序 status = 20
  -> 对应 component.status = 50

production_receipt_process 完成
  -> 生产/组装工序 status = 20

qc_receipt 审核通过
  -> 质检工序 status = 20

repository_receipt 审核通过
  -> 入库/出库工序 status = 20
```

### 5. 实现下一工序启动器

建议新增服务：

```text
App\Service\Common\CustomFulfillmentProcessFlowService
```

核心入口：

```php
public function tryStartNextProcesses(CustomOrderItem $item, ?int $adminId = null): void
```

规则：

```text
1. 找到当前进行中的最小 rank。
2. 如果该 rank 下所有必需工序均 status = 20，则启动下一个 rank。
3. 下一个 rank 的 process.status: 0 -> 10。
4. 写 custom_process_log。
5. 如果没有下一个 rank，则汇总 custom_order_item.status。
```

汇总规则：

```text
任一 component/process 异常
  -> custom_order_item.status = 80

存在 qc 进行中
  -> custom_order_item.status = 30

存在 purchase/outsource/production/assemble 进行中
  -> custom_order_item.status = 20

全部 process 完成，未交付
  -> custom_order_item.status = 40

已发货/交付
  -> custom_order_item.status = 50
```

## 最小可交付版本

建议先做以下三项：

### A. 供应商配置

先不建复杂能力表，使用人工指定：

```text
custom_item_process.supplier_id
```

或：

```text
custom_item_component.supplier_id
```

### B. lens_outsource 生成委外单

实现：

```text
CustomFulfillmentExecutionService::executeProcess()
```

只支持：

```text
process_type = lens_outsource
```

生成：

```text
entrust
```

并回写：

```text
custom_item_process.ref_module/ref_id
custom_item_component.entrust_id
```

### C. 委外完成后启动 assemble

实现状态同步：

```text
entrust 完成
  -> lens_outsource.status = 20
  -> lens components.status = 50
  -> assemble.status = 10
```

完成 A/B/C 后，眼镜订单就可以从 Shopify 定制数据进入真实委外执行流程。

## 后续增强

### 1. 镜框备货

```text
frame_prepare
  -> 查询库存
  -> 有库存则冻结库存
  -> 无库存则生成采购单
```

### 2. 组装生产

```text
assemble
  -> 复用 production_receipt_process
  -> 或先使用轻量 custom_item_process 状态
```

### 3. 质检

```text
qc
  -> 生成 qc_receipt
  -> 质检通过进入 stock_in
  -> 不通过写 abnormal，并支持重做
```

### 4. 成品入库和发货

```text
stock_in
  -> repository_receipt

ship
  -> delivery / repository_receipt 出库
```

### 5. 客户验光档案

当前订单处方保存在：

```text
custom_order_item.params_snapshot
```

如果要支持“复用上一次处方下单 / 处方有效期提醒 / 客户长期验光档案”，再新增：

```text
optical_prescription
```

订单快照和客户长期档案应分离：

```text
custom_order_item.params_snapshot  订单历史快照，不随客户档案修改
optical_prescription               客户长期处方档案，可复用和更新
```

## 注意事项

1. 不要在 Shopify 拉单时直接生成委外单，拉单只负责同步销售和定制数据。
2. 不要在没有供应商的情况下生成委外单。
3. 不要把采购、委外、生产、质检状态只写在 JSON 里，应回写结构化字段。
4. `custom_*` 表不要替代原有业务单据，只负责编排和展示。
5. 所有状态变化都要写 `custom_process_log`，方便追踪和排查。
