# 镜片入库后组装委外流程变更方案

## 一、背景

来源：

- `doc/jykj/2027-07-30会议纪要-整理版.md`
- `doc/jykj/生产管理委外入库工单组装质检入库实施方案.md`

原方案默认流程是：

```text
镜片委外加工完成
  -> 镜片接收入库
  -> 镜框、左镜片、右镜片齐套
  -> 自动启动内部组装工序
  -> 内部质检
  -> 内部成品入库
  -> 生成发货计划
```

会议纪要新增了一个关键变化：

```text
镜框、镜片到齐后，组装也可能走委外流程。
组装委外需要生成出库发货流程，也有接单、发货等后续操作。
```

因此，镜片到货入库后，后续不一定进入内部组装，而是可能进入：

```text
部件齐套
  -> 生成组装委外单
  -> 将镜框、镜片出库发给组装供应商
  -> 组装供应商接单、组装、发货
  -> ERP 接收组装成品入库
  -> 质检或直接成品入库完成
  -> 生成发货计划
```

本方案用于替代原方案中“镜片入库后自动启动内部 assemble 工序”的默认设计。

## 二、核心变化

### 2.1 原流程问题

原方案中，`stock_in` 成品入库时通过 `repository_assemble` 自动做：

```text
消耗镜框 + 左镜片 + 右镜片库存
  -> 入库 1 个成品眼镜
```

这适合内部组装，但不适合组装外发。

如果组装已经外发给供应商，库存动作应拆成两段：

1. 外发组装时：镜框、镜片从本仓出库，交给组装供应商。
2. 组装完成返回时：入库一个成品眼镜。

不能在成品入库时再用旧逻辑二次消耗镜框、镜片，否则会重复扣库存。

### 2.2 新流程原则

1. 镜片委外入库后只表示镜片部件已备齐。
2. 部件齐套后，根据规则决定进入内部组装还是组装委外。
3. 组装委外需要独立生成委外工单。
4. 组装委外发料时必须产生部件出库记录。
5. 组装供应商返回后，入库对象是成品，不是镜片或镜框。
6. 组装委外也要支持接单、拒单、超时、反馈、发货、接收入库。
7. 组装委外不能让供应商看到客户敏感信息、销售价格、Shopify 原始单号等。

## 三、推荐总体流程

```text
订单审核通过
  -> 生成定制履约单元 custom_order_item
  -> 生成部件 custom_item_component：镜框、左镜片、右镜片
  -> 镜片生成镜片加工委外单 entrust_lens
  -> 镜框锁库或采购

镜片加工供应商发货
  -> /admin/entrust/receiveStockIn
  -> 镜片入库
  -> 镜片部件 status = 50

检查镜框、左右镜片是否齐套
  -> 若配置为内部组装：走旧内部 assemble 流程
  -> 若配置为组装委外：生成组装委外单 entrust_assemble

组装委外单审核通过
  -> 供应商待接单
  -> 供应商接单
  -> 内部仓库将镜框、左右镜片出库发给组装供应商
  -> 组装供应商完成组装并寄回成品
  -> ERP 接收成品入库
  -> custom_order_item.status = 已生产入库
  -> 自动生成/解锁发货计划
```

## 四、模型复用策略

### 4.1 组装委外单

组装委外继续复用：

```text
entrust
```

建议约定：

| 字段 | 组装委外含义 |
| --- | --- |
| `type` | 使用 `ProductionSet::ENTRUST_TYPE_PROCESS`，表示工序委外 |
| `supplier_id` | 组装供应商 |
| `product_id` | 成品眼镜产品 ID，即 `custom_order_item.product_id` |
| `num` | 成品数量，通常为 1 |
| `price` | 组装供应商填写或内部预估的组装费用 |
| `plan_start_time` | 组装委外生成时间 |
| `plan_end_time` | 预计组装完成时间 |
| `params_snapshot` | 组装所需参数快照 |
| `custom_params` | 供应商可见组装参数、部件清单、注意事项 |

不建议为组装委外新建平行工单表。原因：

1. 供应商接单、拒单、发货、接收入库等能力与镜片委外一致。
2. SLA、供应商门户、状态流转可以复用。
3. 后续供应商分析也可以统一统计。

### 4.2 组装工序关联

组装工序继续使用：

```text
custom_item_process.process_type = assemble
```

但其含义从“内部组装工单”扩展为“组装节点”。

当走组装委外时：

| 字段 | 值 |
| --- | --- |
| `custom_item_process.process_type` | `assemble` |
| `custom_item_process.status` | 委外生成后进入 `10` 进行中 |
| `custom_item_process.supplier_id` | 组装供应商 |
| `custom_item_process.ref_module` | `entrust` |
| `custom_item_process.ref_id` | 组装委外单 ID |

当组装委外成品接收入库后：

```text
assemble.status = 20
actual_finish_time = 接收入库时间
```

### 4.3 部件关联

原有 `custom_item_component` 仍表示物料部件：

```text
frame
lens_left
lens_right
lens_pair
```

不建议把“组装”也建成一个 `custom_item_component`，因为组装是工序，不是物料部件。

需要补充的是：部件发给组装供应商后的出库记录。

建议在 `custom_item_component` 增加或复用字段记录组装外发：

| 字段 | 说明 |
| --- | --- |
| `assemble_entrust_id` | 组装委外单 ID，建议新增 |
| `assemble_out_receipt_detail_id` | 发给组装供应商的出库明细 ID，建议新增 |
| `assemble_supplier_id` | 组装供应商 ID，可选冗余 |
| `assemble_out_time` | 部件外发时间，可选 |

如果短期不想改表，也可以先通过 `custom_item_process.ref_id -> entrust.id` 和出库单 `entrust_id` 间接追踪，但排查和报表会不方便。

## 五、库存设计

### 5.1 推荐库存口径

组装委外库存推荐采用 MVP 直接出库模式：

```text
组装委外发料时：
  镜框库存 -1
  左镜片库存 -1
  右镜片库存 -1

组装供应商返回时：
  成品眼镜库存 +1
```

这样库存最清晰：

1. 仓库内已没有这三个部件。
2. 供应商手上正在加工的物料通过委外单跟踪。
3. 成品返回后只增加成品库存。
4. 不会出现部件库存和成品库存同时存在。

### 5.2 不推荐短期使用供应商虚拟仓

更严谨的方式是：

```text
内部仓 -> 供应商虚拟仓
供应商虚拟仓消耗部件 -> 成品入供应商虚拟仓
成品从供应商虚拟仓 -> 内部仓
```

但这会引入供应商仓、调拨、在制品库存等复杂度。当前阶段建议先不做，等需要供应商在制库存精细核算时再升级。

### 5.3 发料单据：复用原系统委外领料

原系统已经有委外发料/领料能力，不建议在组装委外方案中绕过这套流程直接创建普通出库单。

```text
entrust 组装委外单进入可发料状态
  -> 系统自动生成 material_receipt
  -> 系统自动生成 material_receipt_detail
  -> 系统自动审核 material_receipt
  -> 原系统自动生成 repository_receipt 出库单
  -> 原系统自动审核出库并扣减库存
```

现有能力：

| 现有能力 | 说明 |
| --- | --- |
| `EntrustController::generateMaterialReceipt()` | 已有委外单生成领料单入口 |
| `Entrust::generateMaterialReceipt()` | 已有按委外单生成领料单方法 |
| `material_receipt.type = 1` | 领料 |
| `material_receipt.target = 2` | 委外领料 |
| `MaterialReceipt::afterAuditData()` | 领料单审核后自动生成 `repository_receipt` 出库单 |
| `repository_receipt.pm = PM_OUT` | 出库 |
| `repository_receipt.type = RepositorySet::TYPE_EXPEND` | 现有生产/委外领料出库类型 |

因此，组装委外发料应优先使用：

```text
material_receipt
type = 1 领料
target = 2 委外领料
entrust_id = 组装委外单 ID
```

再由领料单审核自动生成：

```text
repository_receipt
pm = PM_OUT
type = TYPE_EXPEND
material_receipt_id = 领料单 ID
entrust_id = 组装委外单 ID
```

不建议第一阶段新增：

```text
TYPE_ASSEMBLY_ENTRUST_OUT
```

如果后续财务、仓储统计需要区分“生产领料出库”和“组装委外发料出库”，再新增独立库存类型；MVP 阶段用 `material_receipt.target = 2` 已经可以区分委外领料。

组装委外领料单字段建议：

| 字段 | 值 |
| --- | --- |
| `company_id` | 当前公司 |
| `entrust_id` | 组装委外单 ID |
| `type` | `1` 领料 |
| `target` | `2` 委外领料 |
| `create_admin_id` | 操作人 |
| `main_admin_id` | 负责人，可选 |

领料明细：

| 字段 | 说明 |
| --- | --- |
| `product_id` | 镜框、左镜片、右镜片产品 |
| `repository_id` | 部件所在仓位 |
| `num` | 通常为 1 |

领料到出库应设计为自动过程。正常业务中，仓库/生产人员不需要再手工创建领料单、手工审核领料单、手工创建出库单。

自动完成后：

1. 扣减部件库存。
2. `material_receipt.repository_receipt_id` 关联真实出库单。
3. 可按 `repository_receipt_detail` 回写 `custom_item_component.assemble_out_receipt_detail_id`。
4. 写 `custom_process_log`。

### 5.3.1 需要扩展的点

现有 `Entrust::generateMaterialReceipt()` 主要按 `material_requirement_id` 生成领料明细：

```text
entrust.material_requirement_id
  -> material_requirement.detailList
  -> material_receipt_detail
```

组装委外场景不是传统 BOM 领料，而是按定制履约部件发料：

```text
custom_order_item
  -> custom_item_component: frame / lens_left / lens_right
  -> material_receipt_detail
```

因此需要扩展一个新的生成方式：

```text
CustomAssemblyEntrustService::generateMaterialReceiptByComponents(Entrust $entrust)
```

职责：

1. 根据 `custom_item_process.ref_id = entrust.id` 找到 `custom_order_item`。
2. 读取该履约单元下已齐套的镜框、左镜片、右镜片部件。
3. 按部件的 `repository_receipt_detail_id`、`repository_freeze_log_id` 或可用库存确定 `repository_id`。
4. 创建 `material_receipt`：
   - `type = 1`
   - `target = 2`
   - `entrust_id = 组装委外单 ID`
5. 创建 `material_receipt_detail`。
6. 审核领料单，触发原系统出库扣库存。
7. 回写部件的组装外发字段。
8. 写 `custom_process_log`。

### 5.3.2 自动触发时机

推荐自动触发点：

```text
组装委外单审核通过
  -> 进入待接单
  -> 自动生成并审核委外领料单
  -> 自动出库
```

是否必须等供应商接单后再出库，需要按业务风险配置：

| 模式 | 触发时机 | 适用场景 |
| --- | --- | --- |
| `after_audit` | 组装委外单审核通过后立即自动发料出库 | 默认组装供应商稳定，拒单概率低，追求效率 |
| `after_accept` | 供应商接单后自动发料出库 | 供应商可能拒单，需避免物料提前出库 |

建议 MVP 使用：

```text
assemble_material_issue_mode = after_accept
```

原因：

1. 会议纪要明确供应商接单后可能拒单。
2. 等接单后再自动领料出库，可以避免供应商拒单后物料已经出库。
3. 如果后续供应商稳定，可切换为审核通过后自动发料，提高时效。

### 5.3.3 幂等规则

自动领料出库必须幂等：

1. 同一个组装委外单只能有一张有效的委外领料单。
2. 如果已存在 `material_receipt.status >= 10` 或已有关联 `repository_receipt_id`，不得重复生成。
3. 如果前一次生成失败但未审核，可允许继续编辑或作废后重建。
4. 如果出库失败，不能推进为“已发料”，需要标记异常并提示库存不足或仓位缺失。

建议判断：

```text
material_receipt.entrust_id = 当前组装委外单 ID
material_receipt.type = 1
material_receipt.target = 2
material_receipt.status <> -1
```

存在即复用，不重复创建。

### 5.3.4 失败处理

自动领料出库失败时：

1. `assemble` 工序保持 `10` 进行中。
2. 组装委外单保持待发料或加工前状态。
3. 关联 `custom_item_component.is_abnormal = 1`。
4. 写入异常原因，例如：
   - 部件未找到库存来源
   - 仓位库存不足
   - 领料单审核失败
5. 订单面板显示“组装委外发料异常”。
6. 提供人工补触发入口重试。

### 5.4 成品接收入库

组装供应商寄回的是成品眼镜，接收入库时不再生成 `repository_assemble`。

推荐直接生成：

```text
repository_receipt
pm = PM_IN
type = TYPE_ENTRUST_IN 或新增 TYPE_ASSEMBLY_ENTRUST_IN
entrust_id = 组装委外单 ID
```

入库明细：

| 字段 | 说明 |
| --- | --- |
| `product_id` | 成品眼镜产品 ID |
| `repository_id` | 成品默认仓位 |
| `num` | 成品数量 |
| `supplier_id` | 组装供应商 |

建议新增入库类型：

```php
RepositorySet::TYPE_ASSEMBLY_ENTRUST_IN = 新值
```

如果短期不新增枚举，也可以先复用 `TYPE_ENTRUST_IN`，但备注和 `entrust.type` 必须能区分这是组装委外返回的成品。

## 六、状态流转

### 6.1 `custom_order_item.status`

| 节点 | 状态 |
| --- | --- |
| 镜框/镜片备齐前 | `10` 准备中 |
| 组装委外单生成/接单/加工中 | `20` 组装中 |
| 组装供应商返回成品，若需要内部质检 | `30` 质检中 |
| 质检通过并成品入库，或组装返回即自动质检通过 | `40` 已生产入库 |
| 任一委外拒单、超时、丢件、质检不通过 | `80` 异常 |

### 6.2 `custom_item_process.status`

| 工序 | 组装委外场景状态含义 |
| --- | --- |
| `lens_stock_in` | 镜片加工委外入库后完成 |
| `assemble` | 组装委外单生成后进行中，成品返回后完成 |
| `qc` | 可选：内部质检或供应商质检回传后完成 |
| `stock_in` | 成品入库后完成 |

### 6.3 `entrust.status`

组装委外复用委外状态：

| 状态 | 组装委外含义 |
| --- | --- |
| `10` | 待组装供应商接单 |
| `20` | 组装供应商已接单/组装中 |
| `30` | 组装供应商已发货 |
| `50` | 待接收入库 |
| `70/90` | 部分入库/近全入库 |
| `100` | 组装成品已全部入库 |
| `80` | 建议新增：拒单/异常 |

### 6.4 Shopify 状态

组装委外期间，Shopify 自定义订单状态仍应展示为：

```text
CUSTOM_ORDER_STAGE = 20 组装中
```

不要暴露“供应商组装委外”等内部执行细节给消费者。可展示的文案建议：

```text
Your custom glasses are being assembled.
```

## 七、组装供应商选择

### 7.1 规则来源

建议新增组装委外规则配置，或复用供应商能力规则：

```text
assembly_supplier_rule
```

匹配维度：

| 维度 | 说明 |
| --- | --- |
| 成品类型 | 眼镜成品、太阳镜、渐进镜等 |
| 镜框类型 | 全框、半框、无框 |
| 镜片类型 | 单光、渐进、防蓝光、染色等 |
| 是否需要特殊装配 | 打孔、开槽、特殊鼻托等 |
| 国家/渠道 | 不同销售渠道可能指定不同组装商 |
| 供应商时效/价格 | 后续用于自动择优 |

### 7.2 MVP 规则

第一阶段可以简化为：

1. 公司配置一个默认组装供应商。
2. 定制履约部件齐套后自动生成组装委外单给默认供应商。
3. 如果订单或产品指定了组装供应商，则优先使用指定供应商。

后续再扩展属性规则匹配。

## 八、接口设计

### 8.1 部件齐套后生成组装委外单

建议新增：

```text
POST /admin/custom-order-item/generate-assemble-entrust
```

参数：

| 参数 | 说明 |
| --- | --- |
| `custom_order_item_id` | 定制履约单元 ID |
| `supplier_id` | 可选，组装供应商 |
| `plan_end_time` | 可选，预计完成时间 |
| `remark` | 备注 |

处理：

1. 校验镜框、镜片部件已齐套。
2. 校验部件有可出库库存来源。
3. 创建组装委外单 `entrust`。
4. 绑定 `custom_item_process.assemble.ref_module/ref_id`。
5. 将 `assemble.status` 置为 `10`。
6. 写日志。

### 8.2 组装委外自动发料与手动补触发

组装委外发料主链路应由服务自动触发。接口主要用于人工补触发、异常重试或调试，不作为日常必点操作。

建议新增：

```text
POST /admin/entrust/shipAssembleMaterials
```

参数：

| 参数 | 说明 |
| --- | --- |
| `entrust_id` | 组装委外单 ID |
| `repository_id_map` | 可选，指定各部件出库仓位 |
| `tracking_num` | 可选，内部发给组装供应商的物流单号 |
| `remark` | 备注 |

自动触发时调用同一套服务逻辑：

```text
CustomAssemblyEntrustService::autoIssueMaterials(Entrust $entrust)
```

接口处理：

1. 校验委外单类型为组装委外。
2. 找到关联 `custom_order_item` 和部件。
3. 检查是否已存在有效委外领料单，存在则幂等返回。
4. 生成 `material_receipt` 委外领料单。
5. 生成 `material_receipt_detail`，明细为镜框、左镜片、右镜片。
6. 自动审核领料单，由原系统自动生成 `repository_receipt` 出库单并扣减部件库存。
7. 回写部件发料明细。
8. 记录外发物流单号。

### 8.3 组装供应商接单

复用或扩展供应商端：

```text
POST /admin/supplier-entrust/accept
POST /admin/supplier-entrust/reject
POST /admin/supplier-entrust/updateTracking
POST /admin/supplier-entrust/feedback
```

供应商端看到的是一个组装委外单，内容包括：

1. 短委外单号。
2. 成品基础信息。
3. 镜框信息。
4. 左右镜片信息。
5. 组装注意事项。
6. 预计完成时间。

不展示：

1. Shopify 原始订单号。
2. 销售价格。
3. 客户敏感信息。
4. 内部成本和利润。

### 8.4 组装成品接收入库

建议新增：

```text
POST /admin/entrust/receiveAssembleProduct
```

参数：

| 参数 | 说明 |
| --- | --- |
| `entrust_id` | 组装委外单 ID |
| `repository_id` | 成品入库仓位 |
| `num` | 入库数量 |
| `qc_result` | 可选：`pass` / `pending` / `reject` |
| `remark` | 备注 |

处理：

1. 校验组装委外单已发货或允许手动接收。
2. 生成成品入库单。
3. 审核入库单，增加成品库存。
4. 将组装委外单状态推进到完成。
5. 将 `assemble` 工序置为完成。
6. 如果 `qc_result = pass`，自动完成 `qc` 和 `stock_in`。
7. 如果 `qc_result = pending`，启动内部质检工序。
8. 成品入库完成后自动生成/解锁发货计划。

## 九、质检设计

### 9.1 两种质检模式

组装委外后有两种质检模式：

| 模式 | 说明 | 适用 |
| --- | --- | --- |
| 供应商质检 | 供应商在发货前质检，ERP 接收后直接入库 | 供应商稳定、低风险订单 |
| 内部质检 | 供应商寄回后，仓库/质检员验收 | 高价值、复杂镜片、抽检订单 |

建议系统支持配置：

```text
assembly_qc_mode = supplier / internal / random
```

### 9.2 内部质检

如果需要内部质检：

```text
receiveAssembleProduct
  -> 成品暂存入待检仓或待检状态
  -> qc.status = 10
  -> 质检通过
  -> stock_in.status = 20
  -> 成品转正式库存或确认入库
```

MVP 可简化为：

1. 接收入库时先入成品正式仓。
2. 如果质检不通过，再走售后/返工/异常处理。

更严谨版本再引入待检仓。

### 9.3 质检不通过

质检不通过时：

1. `custom_order_item.status = 80`
2. `qc.status = 80`
3. 组装委外单标记异常。
4. 可选择：
   - 退回组装供应商返工
   - 内部返工
   - 重新发起镜片委外
   - 报废并重做

必须写 `custom_process_log`，保留责任和处理记录。

## 十、SLA 和预警

组装委外应复用：

```text
doc/jykj/委外工单时效性管理设计方案.md
```

规则范围：

```text
entrust_sla_rule.scope_type = assemble
```

预警节点：

| 节点 | 说明 |
| --- | --- |
| 接单超时 | 组装供应商超过 24 小时未接单 |
| 发料超时 | 内部仓库超过计划时间未发料 |
| 组装超时 | 供应商超过预计组装完成时间未发货 |
| 返回超时 | 供应商已发货但超过预计到货时间未入库 |
| 质检超时 | 成品到货后内部质检未完成 |

订单面板应展示：

```text
组装委外中
组装供应商：xxx
预计完成：yyyy-mm-dd hh:mm:ss
当前状态：待接单 / 组装中 / 已发货 / 待入库 / 异常
```

## 十一、数据结构调整建议

### 11.1 `custom_item_component`

建议新增：

```sql
ALTER TABLE `hg_custom_item_component`
  ADD `assemble_entrust_id` int(11) unsigned DEFAULT NULL COMMENT '组装委外单ID' AFTER `entrust_id`,
  ADD `assemble_out_receipt_detail_id` int(11) unsigned DEFAULT NULL COMMENT '组装委外发料出库明细ID' AFTER `assemble_entrust_id`,
  ADD `assemble_supplier_id` int(11) unsigned DEFAULT NULL COMMENT '组装供应商ID' AFTER `assemble_out_receipt_detail_id`,
  ADD `assemble_out_time` datetime DEFAULT NULL COMMENT '组装委外发料时间' AFTER `assemble_supplier_id`;
```

### 11.2 `entrust`

建议增加区分业务来源的字段：

```sql
ALTER TABLE `hg_entrust`
  ADD `business_type` varchar(32) DEFAULT NULL COMMENT '业务类型：lens/assemble' AFTER `type`,
  ADD `source_module` varchar(64) DEFAULT NULL COMMENT '来源模块' AFTER `business_type`,
  ADD `source_id` int(11) unsigned DEFAULT NULL COMMENT '来源ID' AFTER `source_module`;
```

组装委外：

```text
business_type = assemble
source_module = custom_item_process
source_id = assemble process id
```

镜片委外：

```text
business_type = lens
source_module = custom_item_component
source_id = lens component id
```

如果短期不改 `entrust`，可先通过 `custom_item_process.ref_module/ref_id` 区分，但列表筛选和统计不如字段直观。

### 11.3 发料和入库单据

组装委外发料侧复用原系统委外领料：

```text
material_receipt.type = 1 领料
material_receipt.target = 2 委外领料
material_receipt.entrust_id = 组装委外单 ID
```

领料单审核后由原系统自动生成：

```text
repository_receipt.pm = PM_OUT
repository_receipt.type = TYPE_EXPEND
repository_receipt.material_receipt_id = 领料单 ID
```

组装成品入库侧建议：

```text
repository_receipt.pm = PM_IN
repository_receipt.type = TYPE_ENTRUST_IN
repository_receipt.entrust_id = 组装委外单 ID
```

如果后续统计需要更细区分，可以只新增成品入库类型：

```text
TYPE_ASSEMBLY_ENTRUST_IN  组装委外成品入库
```

发料出库不建议新增 `TYPE_ASSEMBLY_ENTRUST_OUT` 作为第一阶段方案，因为 `material_receipt.target = 2` 已经能表达委外领料，并且复用了原系统领料审核和库存扣减链路。

## 十二、服务设计

建议新增服务：

```text
App\Service\Common\CustomAssemblyEntrustService
```

职责：

| 方法 | 说明 |
| --- | --- |
| `ensureAfterComponentsReady(int $customOrderItemId)` | 部件齐套后按规则创建组装委外或内部组装 |
| `generateAssembleEntrust(CustomOrderItem $item, int $supplierId)` | 生成组装委外单 |
| `autoIssueMaterials(Entrust $entrust, array $payload = [])` | 自动生成并审核组装委外领料单，复用原系统委外发料出库 |
| `retryIssueMaterials(Entrust $entrust, array $payload = [])` | 发料失败后的人工补触发 |
| `receiveProduct(Entrust $entrust, array $payload)` | 组装成品接收入库 |
| `syncProcessAfterEntrust(Entrust $entrust)` | 委外状态回写组装工序 |
| `buildSupplierParams(CustomOrderItem $item)` | 构造供应商可见组装参数 |

现有 `CustomProductionFlowService::tryStartAssemble()` 需要调整：

```text
旧逻辑：
部件齐套 -> assemble.status = 10

新逻辑：
部件齐套 -> 判断 assemble_mode
  -> internal：assemble.status = 10
  -> outsource：生成组装委外单，assemble.status = 10，ref_module=entrust
```

## 十三、实施顺序

### 阶段一：流程分叉

1. 增加组装模式配置：
   - `internal`
   - `outsource`
2. 部件齐套后不再固定启动内部组装。
3. 组装模式为 `outsource` 时生成组装委外单。
4. 组装委外单绑定 `custom_item_process.assemble`。

### 阶段二：发料出库

1. 实现组装委外自动发料服务。
2. 按配置在组装委外审核通过或供应商接单后自动触发。
3. 自动生成并审核 `material_receipt` 委外领料单。
4. 由原系统自动生成 `repository_receipt` 出库单并扣减部件库存。
5. 回写部件出库明细。
6. 供应商端展示已发料状态。
7. 保留人工补触发接口，用于异常重试。

### 阶段三：成品接收入库

1. 实现组装成品接收入库接口。
2. 入库一个成品眼镜。
3. 完成 `assemble` 工序。
4. 根据质检模式启动或完成 `qc`。
5. 完成 `stock_in` 并触发发货计划。

### 阶段四：供应商门户和预警

1. 供应商端支持组装委外单。
2. 支持接单、拒单、反馈、发货。
3. 接入组装委外 SLA。
4. 订单面板展示组装供应商和组装预计时间。

## 十四、与旧方案的兼容

旧方案仍可保留为内部组装模式：

```text
assemble_mode = internal
```

新方案增加组装委外模式：

```text
assemble_mode = outsource
```

两种模式共享：

1. `custom_order_item`
2. `custom_item_component`
3. `custom_item_process`
4. `custom_process_log`
5. Shopify 状态同步

差异：

| 环节 | 内部组装 | 组装委外 |
| --- | --- | --- |
| 组装执行人 | 内部员工 | 组装供应商 |
| 组装单据 | `custom_item_process` / `repository_assemble` | `entrust` |
| 部件库存消耗 | 成品入库时消耗 | 发料给供应商时消耗 |
| 成品入库 | `repository_assemble` 自动入成品 | 供应商返回后 `repository_receipt` 入成品 |
| 质检 | 内部质检 | 供应商质检或内部验收 |

## 十五、关键注意事项

1. 组装委外发料后，部件库存已经出库，成品入库时不能再走旧的 `repository_assemble` 消耗部件逻辑。
2. 组装委外返回入库的是成品产品，即 `custom_order_item.product_id`。
3. 镜片加工委外和组装委外是两类委外，必须能在 `entrust` 上区分。
4. 组装委外供应商看到的是组装参数和部件信息，不应看到客户敏感信息和销售价格。
5. 组装委外拒单后，需要回到“部件齐套待分配组装供应商”状态，而不是回到镜片加工。
6. 如果组装质检不通过，通常回退到组装委外返工；只有镜片或镜框本身有问题时，才回退到对应部件重做。
7. 发货计划只能在成品入库完成后创建或解锁，不能在镜片入库完成后提前创建。
8. 自动化测试主流程也要区分内部组装和组装委外两种模式。
