# 定制眼镜 MVP 主流程自动化测试方法

## 一、测试目标

提供一个后端自动化入口，用一条已有测试销售订单验证定制眼镜核心 MVP 主流程是否能跑通。

覆盖流程：

```text
销售订单
  -> custom_order_item / custom_item_component / custom_item_process 结构检查
  -> 订单审核和履约准备
  -> 镜框库存锁定或采购计划
  -> 镜片委外单
  -> 委外发货
  -> 委外接收入库
  -> 组装
  -> 质检通过
  -> 成品入库
  -> 发货计划生成
  -> 可选：模拟快递任务和扫码发货
```

## 二、实现位置

服务方法：

```php
App\Service\Common\CustomMvpFlowTestService::runBySaleOrderId(
    int $saleOrderId,
    ?int $adminId = null,
    array $options = []
): array
```

命令入口：

```text
php bin/hyperf.php custom:mvp-flow-test
```

## 三、运行方式

### 3.1 只读前置检查

默认不改数据，只检查订单是否具备 MVP 测试基础结构。

```bash
php bin/hyperf.php custom:mvp-flow-test 3
```

输出 JSON：

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --json=1
```

### 3.2 真实推进主流程

会真实审核订单、生成委外单、生成入库/组装库存流水、推进工序状态。

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1
```

指定操作人：

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1 --admin_id=1
```

### 3.3 包含模拟扫码发货

会创建本地模拟快递任务，不调用真实快递 API，然后扫码发货并扣减成品库存。

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1 --include_shipping=1
```

指定模拟快递单号：

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1 --include_shipping=1 --tracking_num=MVPTEST0001
```

### 3.4 直接同步 Shopify 定制订单状态

默认流程中的状态变更会投递 `CustomOrderStageSyncJob` 队列，由队列异步写入 Shopify 订单元字段。测试时如果需要立即验证 Shopify 自定义订单状态，可以加：

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1 --sync_shopify_stage=1
```

如果包含模拟扫码发货，并希望最终同步为“已发货”：

```bash
php bin/hyperf.php custom:mvp-flow-test 3 --mutate=1 --include_shipping=1 --sync_shopify_stage=1
```

该参数会在测试流程结束后直接调用：

```php
App\Service\Common\CustomOrderStageSyncService::syncBySaleOrderId($saleOrderId)
```

同步目标为 Shopify 订单元字段：

```text
namespace = custom
key       = custom_order_stage
type      = single_line_text_field
```

## 四、测试订单前置条件

测试订单需要满足：

1. 已存在 `sale_order`。
2. 已生成 `custom_order_item`。
3. 每个履约单元有成品 `product_id`。
4. 每个履约单元有镜框部件：`component_type = frame`。
5. 每个履约单元有镜片部件：`lens_left` / `lens_right` / `lens_pair`。
6. 镜片部件有 `product_id` 和 `supplier_id`，否则无法生成委外单。
7. 镜框有库存可锁定，或者能生成采购计划；若要跑到组装/成品入库，镜框必须实际可用。
8. 镜片产品有默认入库仓位。
9. 成品产品有默认入库仓位。
10. 存在 `assemble`、`qc`、`stock_in` 工序。

## 五、采购补货后的兼容处理

如果镜框部件在订单审核时因库存不足进入采购计划，但后续通过采购入库或手工库存调整补足了该产品库存，MVP 测试在进入组装前会自动再次尝试锁定该镜框库存。

处理规则：

1. 只处理 `component_type = frame` 且 `status < 50` 的部件。
2. 如果已有 `repository_freeze_log_id`，视为已锁库存。
3. 如果产品当前已有可用库存，自动创建 `repository_freeze_log`，并将部件更新为库存来源、已齐套状态。
4. 如果仍无可用库存，继续报出 `部件尚未齐套`，并返回对应 `component_id`。
5. 不直接把部件状态硬改为完成，避免成品入库时缺少部件库存消耗依据。

## 六、返回结构

```json
{
  "success": true,
  "sale_order_id": 3,
  "company_id": "xxx",
  "admin_id": 1,
  "mutate": true,
  "include_shipping": false,
  "sync_shopify_stage": true,
  "summary": {
    "custom_order_item_count": 1,
    "component_count": 3,
    "process_count": 4,
    "entrust_count": 2,
    "delivery_plan_count": 1,
    "express_task_count": 0
  },
  "steps": [
    {
      "key": "load_order",
      "title": "加载销售订单",
      "status": "passed",
      "data": {}
    }
  ]
}
```

步骤失败时：

```json
{
  "key": "lens_entrust_flow",
  "title": "镜片委外发货并接收入库",
  "status": "failed",
  "message": "委外单ETxxx产品缺少默认入库仓位"
}
```

## 七、注意事项

1. `--mutate=1` 会真实修改数据库，只能在测试环境或明确的测试订单上执行。
2. `--include_shipping=1` 不调用真实快递 API，但会触发内部销售出库和发货状态变更。
3. 扫码发货后会投递 Shopify 发货同步队列；测试环境如不希望触发第三方同步，应暂停队列消费或使用非 Shopify 渠道订单。
4. `--sync_shopify_stage=1` 会真实调用 Shopify 元字段接口；仅在确认订单对应真实 Shopify 测试店铺时使用。
5. 该方法不负责自动造基础资料，它验证的是现有配置、规则、库存、供应商、仓位和单据联动是否可跑通。
6. 如果只想检查配置完整性，使用默认只读模式即可。
