# 眼镜核心数据模型设计方案

## 设计目标

在不破坏现有通用 ERP 订单、采购、仓库、生产、委外、质检能力的前提下，新增一层定制眼镜业务模型，用来承载：

- 一张销售订单下 1 副或多副眼镜的生产履约。
- 每副眼镜的镜框、左镜片、右镜片独立状态。
- 验光/处方参数快照。
- 镜框采购、库存锁定、镜片委外、组装、质检、生产入库、发货状态汇总。
- 异常、重做、回退和状态变更日志。

## 核心原则

`sale_order` 和 `sale_order_detail` 继续表示销售业务，不直接承担每副眼镜的生产履约状态。

推荐关系：

```text
sale_order
  1 - N sale_order_detail              # 现有销售订单明细，对应 Shopify/销售商品行
        1 - N optical_order_item       # 每副眼镜，一个履约单元
              1 - 1 optical_frame_item # 镜框履约
              1 - N optical_lens_item  # 左/右镜片履约
              1 - N optical_process_log

customer
  1 - N optical_prescription           # 客户验光档案

product
  1 - 1 optical_product_profile        # 产品眼镜属性扩展

supplier
  1 - N optical_supplier_capability    # 供应商加工/供货能力
```

## sale_order_detail 与“一副/多副眼镜”的关系

`sale_order_detail` 应按销售行项目落库，不建议按每副眼镜拆分。

### 场景 1：一个 Shopify 行项目，数量 1，对应 1 副眼镜

```text
sale_order_detail: 1 条，num = 1
optical_order_item: 1 条
```

### 场景 2：一个 Shopify 行项目，数量 3，对应 3 副眼镜

```text
sale_order_detail: 1 条，num = 3
optical_order_item: 3 条，pair_no = 1 / 2 / 3
```

即使三副眼镜参数不同，也仍建议保留一条 `sale_order_detail` 作为原始销售行，再拆出 3 条 `optical_order_item` 分别保存参数快照和履约状态。

### 场景 3：Shopify 本身有多个商品行

```text
sale_order_detail: 多条，对应 Shopify 多个 line_item
optical_order_item: 按实际眼镜副数归组
```

例如销售行包含镜框、镜片、配件时，可通过 `optical_order_item` 把相关销售明细归并为同一副眼镜的履约对象。

### 不建议直接拆 sale_order_detail 的原因

- 现有 `sale_order_detail` 承担销售金额、SKU、数量、采购/发货生成、历史价格统计等通用职责。
- Shopify line item 与本地明细应尽量保持清晰映射，方便退款、改价、同步。
- 同一订单中同一 SKU 可能出现多副眼镜，但处方参数不同；现有 `SaleOrderDetail` 存在 `order_id + product_id` 的合并逻辑，直接用它表达每副眼镜容易合并错。
- 重做、返工、镜片重新委外属于履约事件，不应污染销售明细。

## 推荐新增表

### 1. optical_order_item：眼镜履约单元

一条记录表示一副眼镜，是订单面板和生产流转的主对象。

```sql
CREATE TABLE `hg_optical_order_item` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `sale_order_id` int(11) unsigned NOT NULL COMMENT '销售订单ID',
  `sale_order_detail_id` int(11) unsigned DEFAULT NULL COMMENT '销售订单明细ID',
  `customer_id` int(11) unsigned DEFAULT NULL COMMENT '客户ID',
  `third_line_item_id` varchar(128) DEFAULT NULL COMMENT '第三方行项目ID',
  `pair_no` smallint(6) unsigned NOT NULL DEFAULT '1' COMMENT '同一销售明细下第几副眼镜',
  `frame_product_id` int(11) unsigned DEFAULT NULL COMMENT '镜框产品ID',
  `prescription_id` int(11) unsigned DEFAULT NULL COMMENT '客户验光档案ID',
  `prescription_snapshot` json DEFAULT NULL COMMENT '下单时处方快照',
  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '整副眼镜状态',
  `abnormal_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '异常状态',
  `abnormal_reason` varchar(500) DEFAULT NULL COMMENT '异常原因',
  `is_remake` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否重做',
  `remake_from_id` int(11) unsigned DEFAULT NULL COMMENT '来源眼镜履约单元',
  `owner_admin_id` int(11) unsigned DEFAULT NULL COMMENT '负责人',
  `create_admin_id` int(11) unsigned DEFAULT NULL,
  `edit_admin_id` int(11) unsigned DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_company_order` (`company_id`, `sale_order_id`),
  KEY `idx_detail` (`sale_order_detail_id`),
  KEY `idx_customer` (`customer_id`),
  KEY `idx_status` (`status`, `abnormal_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜订单履约单元';
```

建议整副眼镜状态：

```text
0  待解析/待处理
10 备货中
20 组装中
30 质检中
40 已生产入库
50 已发货
90 已取消
```

### 2. optical_frame_item：镜框履约

一条记录表示某副眼镜的镜框备货、采购或停产状态。

```sql
CREATE TABLE `hg_optical_frame_item` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `optical_order_item_id` int(11) unsigned NOT NULL COMMENT '眼镜履约单元ID',
  `product_id` int(11) unsigned DEFAULT NULL COMMENT '镜框产品ID',
  `supplier_id` int(11) unsigned DEFAULT NULL COMMENT '供应商ID',
  `source_type` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '1库存 2采购',
  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '镜框状态',
  `expected_in_time` datetime DEFAULT NULL COMMENT '预计入库时间',
  `actual_in_time` datetime DEFAULT NULL COMMENT '实际入库时间',
  `purchase_order_id` int(11) unsigned DEFAULT NULL COMMENT '采购单ID',
  `purchase_order_detail_id` int(11) unsigned DEFAULT NULL COMMENT '采购明细ID',
  `repository_freeze_log_id` int(11) unsigned DEFAULT NULL COMMENT '库存冻结记录ID',
  `repository_receipt_detail_id` int(11) unsigned DEFAULT NULL COMMENT '入库明细ID',
  `is_abnormal` tinyint(2) unsigned NOT NULL DEFAULT '0',
  `abnormal_reason` varchar(500) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_optical_frame` (`optical_order_item_id`),
  KEY `idx_product` (`product_id`),
  KEY `idx_supplier` (`supplier_id`),
  KEY `idx_status` (`status`, `is_abnormal`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜镜框履约';
```

建议镜框状态：

```text
0  待处理
10 已完成备货
20 采购中-供应商现货
30 采购中-供应商生产中
40 采购已入库
80 无法采购-供应商已停产
90 已取消
```

### 3. optical_lens_item：镜片履约

左右眼各一条，必要时也支持多焦点、备用片、返工片。

```sql
CREATE TABLE `hg_optical_lens_item` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `optical_order_item_id` int(11) unsigned NOT NULL COMMENT '眼镜履约单元ID',
  `eye_side` char(2) NOT NULL COMMENT 'L左眼 R右眼',
  `product_id` int(11) unsigned DEFAULT NULL COMMENT '镜片产品ID',
  `supplier_id` int(11) unsigned DEFAULT NULL COMMENT '镜片加工供应商ID',
  `entrust_id` int(11) unsigned DEFAULT NULL COMMENT '委外单ID',
  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '镜片状态',
  `expected_finish_time` datetime DEFAULT NULL COMMENT '预计生产完成时间',
  `factory_finish_time` datetime DEFAULT NULL COMMENT '工厂生产完成时间',
  `actual_in_time` datetime DEFAULT NULL COMMENT '实际入库时间',
  `tracking_num` varchar(128) DEFAULT NULL COMMENT '供应商寄回快递单号',
  `sphere` decimal(5,2) DEFAULT NULL COMMENT '球镜 SPH',
  `cylinder` decimal(5,2) DEFAULT NULL COMMENT '柱镜 CYL',
  `axis` smallint(3) unsigned DEFAULT NULL COMMENT '轴位',
  `add_power` decimal(5,2) DEFAULT NULL COMMENT 'ADD',
  `pd` decimal(5,2) DEFAULT NULL COMMENT '瞳距',
  `prism` varchar(64) DEFAULT NULL COMMENT '棱镜',
  `base` varchar(64) DEFAULT NULL COMMENT '基底',
  `lens_type` varchar(64) DEFAULT NULL COMMENT '镜片类型',
  `refractive_index` varchar(32) DEFAULT NULL COMMENT '折射率',
  `coating` varchar(128) DEFAULT NULL COMMENT '膜层',
  `color` varchar(64) DEFAULT NULL COMMENT '颜色',
  `custom_params` json DEFAULT NULL COMMENT '其他定制参数',
  `is_abnormal` tinyint(2) unsigned NOT NULL DEFAULT '0',
  `abnormal_reason` varchar(500) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_optical_eye` (`optical_order_item_id`, `eye_side`),
  KEY `idx_supplier` (`supplier_id`),
  KEY `idx_entrust` (`entrust_id`),
  KEY `idx_status` (`status`, `is_abnormal`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜镜片履约';
```

建议镜片状态：

```text
0  待处理
10 已委外下单
20 工厂已生产完成
30 已入库
80 异常/无法加工
90 已取消
```

### 4. optical_prescription：客户验光档案

保存客户历史处方。订单需要另存 `prescription_snapshot`，避免客户档案修改影响历史订单。

```sql
CREATE TABLE `hg_optical_prescription` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `customer_id` int(11) unsigned NOT NULL,
  `source` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '1 Shopify 2 人工录入 3 历史导入',
  `exam_time` datetime DEFAULT NULL COMMENT '验光时间',
  `od_data` json DEFAULT NULL COMMENT '右眼参数',
  `os_data` json DEFAULT NULL COMMENT '左眼参数',
  `pd` decimal(5,2) DEFAULT NULL COMMENT '瞳距',
  `remark` varchar(500) DEFAULT NULL,
  `create_admin_id` int(11) unsigned DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_customer` (`company_id`, `customer_id`),
  KEY `idx_exam_time` (`exam_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户验光档案';
```

`od_data` / `os_data` 示例：

```json
{
  "sphere": -2.50,
  "cylinder": -0.75,
  "axis": 180,
  "add_power": 1.00,
  "pd": 31.5,
  "prism": "",
  "base": ""
}
```

### 5. optical_product_profile：产品眼镜属性

不建议直接大改 `product` 表。眼镜属性通过扩展表关联产品。

```sql
CREATE TABLE `hg_optical_product_profile` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `product_id` int(11) unsigned NOT NULL,
  `optical_type` tinyint(2) unsigned NOT NULL COMMENT '1镜框 2镜片 3配件 4成品眼镜',
  `frame_material` varchar(64) DEFAULT NULL,
  `frame_color` varchar(64) DEFAULT NULL,
  `frame_size` varchar(64) DEFAULT NULL,
  `lens_type` varchar(64) DEFAULT NULL,
  `lens_index` varchar(32) DEFAULT NULL,
  `lens_coating` varchar(128) DEFAULT NULL,
  `default_lead_days` smallint(6) unsigned DEFAULT NULL COMMENT '默认交期天数',
  `is_discontinued` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否停产',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_product` (`product_id`),
  KEY `idx_company_type` (`company_id`, `optical_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜产品属性';
```

### 6. optical_supplier_capability：供应商眼镜能力

用于镜片自动匹配供应商、交期计算和采购比价。

```sql
CREATE TABLE `hg_optical_supplier_capability` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `supplier_id` int(11) unsigned NOT NULL,
  `optical_type` tinyint(2) unsigned NOT NULL COMMENT '1镜框供应 2镜片加工',
  `product_id` int(11) unsigned DEFAULT NULL COMMENT '指定产品时使用',
  `lens_type` varchar(64) DEFAULT NULL,
  `lens_index` varchar(32) DEFAULT NULL,
  `lead_days` smallint(6) unsigned NOT NULL DEFAULT '0' COMMENT '交期天数',
  `currency` char(4) NOT NULL DEFAULT 'CNY',
  `price` decimal(10,2) unsigned DEFAULT NULL,
  `status` tinyint(2) unsigned NOT NULL DEFAULT '1' COMMENT '1正常 0停用',
  `admin_remark` varchar(500) DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_supplier` (`supplier_id`),
  KEY `idx_product` (`product_id`),
  KEY `idx_rule` (`company_id`, `optical_type`, `lens_type`, `lens_index`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜供应商能力';
```

### 7. optical_process_log：状态、重做、异常日志

所有状态变化、重做回退、异常标记都写日志。

```sql
CREATE TABLE `hg_optical_process_log` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `optical_order_item_id` int(11) unsigned NOT NULL,
  `target_type` varchar(32) NOT NULL COMMENT 'order/frame/lens',
  `target_id` int(11) unsigned DEFAULT NULL,
  `from_status` tinyint(2) DEFAULT NULL,
  `to_status` tinyint(2) DEFAULT NULL,
  `event` varchar(64) NOT NULL COMMENT 'status_change/remake/abnormal/rollback',
  `remark` varchar(1000) DEFAULT NULL,
  `operator_admin_id` int(11) unsigned DEFAULT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_item` (`optical_order_item_id`),
  KEY `idx_event` (`company_id`, `event`),
  KEY `idx_target` (`target_type`, `target_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='眼镜流程日志';
```

## 与现有模块的衔接

### Shopify 同步

1. Shopify 订单先正常创建 `sale_order`。
2. Shopify line item 先创建 `sale_order_detail`，`third_id` 保存 line item id。
3. 将 Shopify properties/custom attributes 保存到 `sale_order_detail.extra`，作为原始快照。
4. 解析每副眼镜，生成 `optical_order_item`。
5. 为每副眼镜生成 `optical_frame_item` 和左右 `optical_lens_item`。

### 镜框库存和采购

- 有库存时，复用 `repository_freeze_log` 锁定镜框库存，并回写到 `optical_frame_item.repository_freeze_log_id`。
- 库存不足时，复用 `purchase_order` / `purchase_order_detail` 发起采购，并回写采购明细 ID。
- 采购状态和预计入库时间同步到 `optical_frame_item`，供订单面板展示。

### 镜片委外

- 根据 `optical_lens_item` 的镜片类型、折射率、膜层等参数，匹配 `optical_supplier_capability`。
- 复用现有 `entrust` 创建委外单。
- `optical_lens_item.entrust_id` 关联委外单。
- 供应商生产完成、寄回、入库时更新 `optical_lens_item.status`。

### 组装、质检、生产入库

- 当 `optical_frame_item` 已备齐，且左右 `optical_lens_item` 均已入库后，`optical_order_item.status` 可进入组装中。
- 可复用 `production_receipt` / `production_receipt_process` 管理组装和质检，也可以先用 `optical_order_item.status` 做轻量流程。
- 质检不通过时，写 `optical_process_log`，并按场景回退到组装或重新生成镜片委外。

### 发货和 Shopify 回传

- 生产入库后，可基于 `optical_order_item` 生成发货计划或快递任务。
- 发货后更新 `optical_order_item.status = 已发货`。
- Shopify 回传时不直接读 `sale_order.status`，而应汇总 `optical_order_item` 的状态，生成用户可理解的生产/组装/物流状态。

## 订单面板查询建议

订单列表展示时，可以按销售订单汇总：

```text
sale_order
  optical_order_items
    frame_item
    lens_items
```

建议后端返回结构：

```json
{
  "sale_order_id": 1001,
  "code": "SHOP_1001",
  "optical_summary": {
    "total": 2,
    "abnormal_total": 1,
    "status": "备货中"
  },
  "optical_items": [
    {
      "pair_no": 1,
      "status": "组装中",
      "frame": {
        "status": "已完成备货",
        "expected_in_time": null
      },
      "lenses": [
        {
          "eye_side": "L",
          "status": "已入库"
        },
        {
          "eye_side": "R",
          "status": "已入库"
        }
      ]
    }
  ]
}
```

## 实施顺序建议

1. 先建 `optical_order_item`、`optical_frame_item`、`optical_lens_item`、`optical_process_log`。
2. 在 Shopify 同步后增加解析器，从 `sale_order_detail.extra` 生成眼镜履约数据。
3. 打通镜框库存锁定和库存不足采购。
4. 打通镜片供应商匹配和委外单生成。
5. 做订单面板状态汇总和异常预警。
6. 再补 `optical_prescription`、`optical_product_profile`、`optical_supplier_capability` 的管理页面和分析报表。

## 结论

推荐保留现有订单主从表作为销售事实表，新增眼镜履约模型作为生产事实表。

`sale_order_detail` 表示“卖了什么、多少钱、数量多少”；`optical_order_item` 表示“实际要生产/履约的第几副眼镜”。这样既能复用现有 ERP 能力，又能满足定制眼镜对镜框、镜片、处方、委外、异常和重做流程的精细追踪。
