# 供应商采购与镜片供应门户实现方案

## 需求范围

来源：`doc/jykj/软件开发需求功能点.md`

| 模块 | 功能 | 需求说明 |
| --- | --- | --- |
| 供应商管理 | 供应商管理 | 镜框成品供应商和镜片定制加工供应商管理，可以查看供应商支持的产品和报价 |
| 供应商分析 | 供应商分析 | 可以分析供应商的历史供应数据，分析供应商的价格走势。采购的时候可以对同款产品快速做供应商比价。 |
| 镜片供应商 | 供应商页面 | 供应商角色用户登录后只能看到供应页面。在这里，供应商可以查看分配给自己的委外加工单，查看工单中每个镜片的详细定制参数信息以及订单对应产品的部分基础信息，且不能导出包含原始销售单号、SKU 价格等敏感信息的内容。供应商镜片生产完成后，可以在供应商页面修改委外单里面每个镜片的状态为已生产，可以录入快递单号，可以通过扫码的方式快速修改工单中镜片的状态。 |
| 采购管理 | 采购单 | 支持采购人员提前手动对库存紧张的镜框发起采购订单做库存备货，输入采购产品可以列出关联的供应商和供应商报价，便于采购人员比价。支持采购审核流程管理，避免采购舞弊。支持按销售订单对应的镜框 SKU 自动锁定库存镜框，也支持在库存不足时自动添加采购计划。采购人员根据采购计划按需采购，并录入采购状态（如供应商现货预计入库时间、供应商正在生产预计入库时间、已停产无法采购、采购已入库等），采购状态要反馈到订单面板，供客服人员直观查看进度。 |
| 采购分析 | 采购分析 | 分析历史采购订单和走势，分析单款产品的历史采购价格走势。 |

## 现有基础

当前代码中已具备以下可复用能力：

| 能力 | 现有模型/模块 | 说明 |
| --- | --- | --- |
| 供应商主数据 | `supplier` | 已有供应商基本资料、负责人、评分、账期等字段 |
| 供应商产品关系/报价 | `supplier_product` | 已有供应商-产品关系、报价、最近价、最低价、MOQ、币种等 |
| 采购计划 | `purchase_plan_detail` | 已有采购计划明细，可按产品建计划 |
| 采购订单 | `purchase_order` / `purchase_order_detail` | 已有采购单、采购明细、审核、入库链路 |
| 委外单 | `entrust` | 已有供应商、产品、数量、预计时间、参数快照、物流单号、入库等 |
| 定制部件 | `custom_item_component` | 已有镜框/左镜片/右镜片部件、供应商、来源、状态、预计完成时间、委外/采购关联 |
| 定制履约流程 | `custom_order_item` / `custom_item_process` | 已有订单履约单元和工序链路 |
| 供应商端访问控制 | admin 登录、菜单权限、数据范围过滤 | 供应商沿用后台登录体系，通过菜单权限限制可访问页面，再按供应商身份过滤数据 |

结论：

1. 供应商管理、采购管理、委外管理不应另起一套平行系统。
2. 应在现有 `supplier / supplier_product / purchase_* / entrust / custom_item_component` 之上做眼镜业务扩展。
3. 镜框和镜片供应逻辑需要区分：
   - 镜框：标准产品采购，重点是库存锁定、采购计划、供应商报价、价格走势。
   - 镜片：定制加工委外，重点是参数可见、供应商能力匹配、供应门户、工单状态回写。

## 总体设计

建议按四层组织：

```text
供应商主数据与匹配配置层
  supplier
  supplier_product
  optical_component_product_rule

执行单据层
  purchase_plan_detail
  purchase_order / purchase_order_detail
  entrust
  custom_item_component

分析沉淀层
  supplier_product_quote_log
  supplier_optical_profile

展示与门户层
  admin supplier / purchase / analysis
  admin supplier role pages
```

## 一、供应商管理方案

### 1.1 供应商主数据

继续沿用 `supplier` 作为供应商主表。

不建议拆成“镜框供应商表”“镜片供应商表”。供应商本质上都是供应商，只是在眼镜业务中的能力不同。

建议通过供应商主数据 + 镜片匹配规则区分：

```text
supplier
  基础资料、联系人、负责人、评分、账期

optical_component_product_rule
  镜片参数匹配、默认产品、默认供应商
```

### 1.2 镜框成品供应商

镜框属于标准产品供应，继续复用：

```text
supplier_product
```

存储内容：

| 字段 | 用途 |
| --- | --- |
| `supplier_id` | 供应商 |
| `product_id` | 镜框产品 |
| `price` / `exw_price` / `fob_price` | 报价 |
| `currency` | 币种 |
| `moq` | 起订量 |
| `recently_price` / `lowest_price` / `highest_price` | 聚合价 |
| `recently_purchase_time` | 最近采购时间 |

后台页面直接沿用：

```text
/admin/supplier/index
/admin/supplier/detail
/admin/supplier-product/index?supplier_id={id}
```

### 1.3 镜片定制加工供应商

镜片不是标准 SKU 报价模型，单靠 `supplier_product` 不够，因为镜片价格和交期依赖参数：

- 镜片类型
- 折射率
- 材料
- 膜层
- 球镜范围
- 柱镜范围
- ADD
- 是否支持染色、棱镜等

这部分不再单独新增 `hg_optical_supplier_capability`，当前阶段直接复用现有：

```text
hg_optical_component_product_rule
```

该表已经具备以下职责基础：

1. 按镜框/镜片参数匹配 `product_id`
2. 挂接默认 `supplier_id`
3. 作为定制履约拆件后的默认供应来源

为满足供应商管理和镜片委外需求，本期建议在 `hg_optical_component_product_rule` 上按需补充少量字段，而不是再建第二张参数规则表：

| 字段 | 用途 |
| --- | --- |
| `lead_time_days` | 默认交期天数 |
| `base_price` | 默认加工基准价，用于委外预估和比价 |
| `is_discontinued` | 是否停接/停产 |
| `lens_group` / `lens_option` | 如当前业务匹配维度需要，可直接补在规则表 |

这样规则口径保持唯一：

```text
镜片参数 -> optical_component_product_rule -> product_id + 默认 supplier_id
```

如果未来出现“同一套镜片参数要配置多个供应商报价并做横向比价”的场景，再从该规则表之下拆出独立的供应商能力/报价表。

### 1.4 供应商详情页扩展

供应商详情页建议增加两个区块：

1. 供应能力
   - 镜框供应产品列表
   - 镜片匹配规则列表（按供应商筛选）

2. 历史表现
   - 历史采购金额
   - 最近交付时间
   - 平均交期
   - 交期达成率
   - 历史异常次数

## 二、供应商分析方案

供应商分析建议拆成两类：

### 2.1 镜框采购分析

数据来源：

- `purchase_order`
- `purchase_order_detail`
- `supplier_product`
- `repository_receipt`

分析指标：

| 指标 | 说明 |
| --- | --- |
| 采购次数 | 某供应商历史被采购次数 |
| 采购金额 | 某供应商累计采购金额 |
| 单款历史采购价 | 单个镜框产品历史采购价格序列 |
| 最近采购价 | 最近一次采购成交价 |
| 平均采购价 | 历史平均成交价 |
| 最低采购价 | 历史最低成交价 |
| 交付周期 | 下单到入库平均时长 |
| 延期率 | 预计到货时间超期比例 |
| 停产次数 | 已停产/无法采购次数 |

### 2.2 镜片委外分析

数据来源：

- `entrust`
- `custom_item_component`
- `optical_component_product_rule`
- `repository_receipt`

分析指标：

| 指标 | 说明 |
| --- | --- |
| 委外单数 | 某镜片供应商历史承接次数 |
| 镜片数量 | 加工片数 |
| 平均加工周期 | 委外下单到接收入库时长 |
| 超期率 | 超出 SLA 的比例 |
| 异常率 | 异常委外单比例 |
| 常加工镜片类型 | 供应商主力能力分布 |
| 实际加工单价走势 | 同类镜片历史加工价趋势 |

### 2.3 价格走势

仅靠 `supplier_product.lowest_price / recently_price / highest_price` 不足以形成趋势图，建议新增报价日志表：

```sql
CREATE TABLE `hg_supplier_product_quote_log` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `company_id` char(18) NOT NULL,
  `supplier_id` int(11) unsigned NOT NULL,
  `product_id` int(11) unsigned DEFAULT NULL,
  `rule_id` int(11) unsigned DEFAULT NULL COMMENT '镜片匹配规则ID',
  `quote_type` tinyint(2) unsigned NOT NULL COMMENT '1镜框报价 2镜片加工报价',
  `price` decimal(10,2) NOT NULL,
  `currency` char(4) NOT NULL DEFAULT 'CNY',
  `source` varchar(32) DEFAULT NULL COMMENT 'manual/purchase/entrust/import',
  `ref_type` varchar(32) DEFAULT NULL,
  `ref_id` int(11) unsigned DEFAULT NULL,
  `quote_time` datetime DEFAULT NULL,
  `create_admin_id` int(11) unsigned DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_quote_trend` (`company_id`, `supplier_id`, `product_id`, `quote_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商报价历史';
```

写入时机：

1. 维护 `supplier_product` 报价时。
2. 新增镜片能力报价时。
3. 采购单审核通过时，记录实际成交价。
4. 委外单审核通过时，记录镜片加工成交价。

### 2.4 快速比价

采购时输入产品，直接返回可选供应商及历史情况。

建议接口：

```text
GET /admin/purchase-order/supplierCompare?product_id={product_id}
GET /admin/optical-supplier-capability/compare?component_id={custom_item_component_id}
```

返回内容：

```text
供应商名称
当前报价
最近成交价
历史最低价
平均交期
延期率
MOQ
是否停产
最近采购时间
推荐原因
```

推荐排序建议：

```text
1. 未停产
2. 价格低
3. 交期短
4. 延期率低
5. 最近成交稳定
```

## 三、镜片供应商门户方案

### 3.1 现状

供应商不再单独建设一套 `/supplier/*` 登录和门户会话。

当前采用后台 admin 登录体系。供应商用户作为一种后台账号/角色存在，通过菜单权限控制只能访问供应商相关页面，再通过数据范围过滤确保只能看到属于自己的委外单和镜片部件。

### 3.2 目标

供应商角色用户登录 admin 后，只允许看到供应商菜单范围内的页面。

供应商页面主要服务镜片加工供应商，只展示该供应商自己的委外单和镜片工单。

推荐菜单：

```text
镜片委外单
镜片工单
物流发货
扫码生产
```

### 3.3 数据范围

供应商角色的数据范围必须在接口层强制过滤，不能只依赖前端菜单隐藏。

基础过滤条件：

```text
entrust.supplier_id = 当前供应商
custom_item_component.entrust_id = 可见委外单
custom_item_component.component_type in ('lens_left', 'lens_right')
```

实现建议：

1. 供应商用户登录仍使用 admin 认证和权限中间件。
2. 通过管理员账号绑定的 `supplier_id` 识别当前供应商身份。
3. 供应商可访问菜单由后台权限系统配置。
4. 供应商可见数据参考 `App\Controller\Admin\Traits\PartialShareCompany` 的做法，在 controller 的查询入口统一追加数据范围条件。
5. 对委外单、镜片部件、物流更新、扫码更新等接口，必须在查询和更新前校验记录所属 `supplier_id`。

数据过滤不建议只放在列表查询中，详情、更新、扫码、导出都要走同一套供应商范围判断。

### 3.4 展示模型

供应商页面建议以“委外单头 + 镜片行项目”展示：

#### 委外单头

来源：`entrust`

展示字段：

- 委外单号
- 状态
- 计划开始时间
- 计划完成时间
- 发货物流单号
- 已生产数量
- 待入库数量

#### 镜片行项目

来源：`custom_item_component`

展示字段：

- 镜片方向：左/右
- 镜片组
- 镜片选项
- 球镜
- 散光/柱镜
- 轴位
- ADD
- PD / 左右 PD
- 镀膜/材质
- 关联产品基础信息（产品名、内部品类、图片）
- 当前生产状态

这里不建议让供应商直接查看原始销售行，因为：

1. 原始销售单号属于内部订单链路。
2. 销售 SKU、销售价、客户售价属于敏感商业信息。
3. 供应商只需要知道生产所需参数和少量基础产品信息。

### 3.5 敏感字段脱敏

供应商页面中禁止展示和导出以下字段：

```text
sale_order.code
sale_order_detail.third_id
sale_order_detail.price
sale_order_detail.cost_price
客户售价
客户联系方式
原始 Shopify line item id
运营备注
内部采购价分析
```

实现建议：

1. 不复用后台 `Entrust` 的导出接口。
2. 供应商菜单对应接口单独定义 DTO / 返回字段白名单。
3. 供应商如需导出，仅导出委外单号、镜片参数、数量、物流单号、供应商状态。

### 3.6 供应商操作

供应商可执行：

1. 查看分配给自己的委外单。
2. 查看委外单中的镜片参数。
3. 更新镜片行状态为“已生产”。
4. 录入委外单物流单号。
5. 使用扫码更新镜片状态。

#### 镜片状态建议

建议在 `custom_item_component` 上新增供应商侧状态字段：

```sql
ALTER TABLE `hg_custom_item_component`
  ADD `supplier_status` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '供应商侧状态',
  ADD `supplier_finish_time` datetime DEFAULT NULL COMMENT '供应商完成时间',
  ADD `supplier_scan_code` varchar(64) DEFAULT NULL COMMENT '镜片扫码码';
```

建议状态：

```text
0 待接单
10 已接单
20 生产中
30 已生产
40 已打包
50 已寄回
80 异常
```

#### 物流单号

委外单为批次寄回，建议继续使用：

```text
entrust.tracking_num
entrust.shipped_time
```

### 3.7 扫码改状态

扫码对象建议不是 `entrust`，而是镜片行：

```text
custom_item_component.id
```

做法：

1. 为每个镜片部件生成唯一 `supplier_scan_code`。
2. 供应商扫码后调用 admin 权限下的供应商页面接口：

```text
POST /admin/supplier-entrust/component/scanUpdate
```

参数：

```text
scan_code
target_status
tracking_num 可选
```

### 3.8 供应商菜单接口建议

```text
GET  /admin/supplier-entrust/index
GET  /admin/supplier-entrust/detail?id={entrust_id}
GET  /admin/supplier-entrust/componentList?entrust_id={entrust_id}
POST /admin/supplier-entrust/updateTracking
POST /admin/supplier-entrust/component/updateStatus
POST /admin/supplier-entrust/component/scanUpdate
GET  /admin/supplier-entrust/export
```

建议新建：

```text
App\Controller\Admin\SupplierEntrustController
```

不要让供应商菜单直接复用后台内部 `Admin\EntrustController` 的完整字段和完整数据范围。

该 controller 应沿用 admin 登录、菜单权限和操作日志，但在查询和更新时追加供应商范围过滤。可参考 `App\Controller\Admin\Traits\PartialShareCompany` 的思路抽出供应商数据范围 trait，例如：

```text
App\Controller\Admin\Traits\PartialShareSupplier
```

trait 职责：

1. 识别当前 admin 账号绑定的 `supplier_id`。
2. 列表查询自动追加 `supplier_id = 当前供应商`。
3. 详情和更新前校验记录归属。
4. 非供应商角色访问时，可按普通后台权限走完整数据范围。

## 四、采购管理方案

### 4.1 采购对象

采购管理主要面向镜框成品。

镜片加工走委外，不建议混入标准采购单逻辑。

### 4.2 手动备货采购

当前已有：

- `purchase_plan_detail`
- `purchase_order`
- `purchase_order_detail`

可在此基础上实现“库存紧张镜框提前采购备货”。

采购流程：

```text
采购人员选择产品
  -> 查询 supplier_product
  -> 展示供应商报价、MOQ、最近成交价、历史走势
  -> 生成 purchase_order / purchase_order_detail
  -> 走审核
  -> 到货入库
```

### 4.3 自动锁定库存与自动加采购计划

订单审核通过后，对镜框部件执行：

```text
custom_item_component.component_type = frame
```

处理逻辑：

1. 若库存足够：
   - 直接冻结库存
   - `source_type = 1`
   - `status = 已备齐`

2. 若库存不足：
   - 自动新增 `purchase_plan_detail`
   - `source_type = 2`
   - 回写 `custom_item_component.purchase_plan_detail_id`
   - 回写 `custom_item_component.purchase_order_id / purchase_order_detail_id`
   - 订单面板显示采购中

### 4.4 采购状态反馈到订单面板

当前 `custom_item_component` 已有：

- `source_type`
- `status`
- `expected_finish_time`
- `actual_finish_time`
- `purchase_order_id`
- `purchase_order_detail_id`
- `purchase_plan_detail_id`
- `abnormal_reason`

建议新增一个更细的采购子状态：

```sql
ALTER TABLE `hg_custom_item_component`
  ADD `supplier_progress_status` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '供应商进度状态',
  ADD `supplier_eta_time` datetime DEFAULT NULL COMMENT '供应商预计入库时间';
```

建议状态：

```text
0 未开始
10 库存已锁定
20 已生成采购计划
30 供应商现货
40 供应商生产中
80 已停产无法采购
90 已采购入库
```

订单面板显示：

| 业务含义 | 展示字段 |
| --- | --- |
| 已完成备货 | `status=10/已备齐` |
| 采购中-供应商现货 | `supplier_progress_status=30` |
| 采购中-供应商生产中 | `supplier_progress_status=40` |
| 已停产无法采购 | `supplier_progress_status=80` |
| 采购已入库 | `supplier_progress_status=90` |
| 预计入库时间 | `supplier_eta_time` |
| 异常原因 | `abnormal_reason` |

### 4.5 审核防舞弊

采购审核继续复用现有 `audit_status` 流程，但建议增强：

1. 采购单强制记录创建人、审核人。
2. 审核通过后写日志，保存当时供应商报价快照。
3. 如果采购价高于最近报价/历史均价阈值，增加红色预警。
4. 修改供应商、数量、价格、预计入库时间必须记录操作日志。

### 4.6 采购相关接口建议

```text
GET  /admin/purchase-order/supplierCompare?product_id={product_id}
POST /admin/purchase-plan-detail/generateByCustomComponent
POST /admin/purchase-order/updateSupplierProgress
GET  /admin/purchase-order/trend?product_id={product_id}
```

## 五、采购分析方案

采购分析应围绕“产品-供应商-价格-交期”展开。

### 5.1 单款产品采购价格走势

数据源：

- `purchase_order_detail.price`
- `purchase_order.order_time`
- `purchase_order.supplier_id`

图表：

1. 单 SKU 历史采购价格折线图
2. 按供应商分组的价格趋势
3. 同期供应商最低价/平均价对比

### 5.2 供应商维度采购分析

指标：

- 累计采购金额
- 累计采购件数
- 最近采购时间
- 平均交期
- 超期次数
- 停产次数

### 5.3 镜片委外价格趋势

镜片委外单价应从：

- `entrust.price`
- 或 `supplier_product_quote_log.quote_type = 2`

统计同类镜片能力的加工单价变化。

## 六、推荐数据结构调整

### 6.1 新增表

1. `hg_supplier_product_quote_log`

### 6.2 扩展表

1. `custom_item_component`
   - `supplier_progress_status`
   - `supplier_eta_time`
   - `purchase_plan_detail_id`
   - `supplier_status`
   - `supplier_finish_time`
   - `supplier_scan_code`

2. `entrust`
   - 已有 `tracking_num`、`params_snapshot`、`custom_params`，继续沿用

3. `purchase_order_detail`
   - 如需采购面板更精细，可补 `supplier_progress_status`、`eta_time`，但最终订单面板仍建议以 `custom_item_component` 为准

4. `optical_component_product_rule`
   - 复用为镜片参数匹配 + 默认供应商规则
   - 如供应商管理阶段需要交期、基准价、停接状态，可直接补 `lead_time_days`、`base_price`、`is_discontinued`
   - 如匹配口径需要镜片组/选项，可继续补 `lens_group`、`lens_option`

## 七、权限设计

### 7.1 后台角色

角色建议：

- 采购
- 采购审核
- 供应商管理员
- 客服/运营查看采购进度

### 7.2 供应商角色

供应商沿用 admin 登录后：

1. 通过菜单权限只开放供应商相关页面。
2. 只能查看 `supplier_id = 当前供应商` 的委外单。
3. 不能访问采购、销售、客户、财务等非授权后台菜单。
4. 不能导出销售敏感字段。
5. 不能查看其他供应商数据。
6. 详情、更新、扫码、导出接口都必须执行供应商数据范围校验。

供应商数据范围过滤建议参考 `App\Controller\Admin\Traits\PartialShareCompany` 的设计方式，在供应商菜单 controller 中统一追加供应商条件，而不是每个接口手写零散判断。

## 八、实施顺序

### 第一阶段：镜片规则收敛和比价

1. 扩展 `optical_component_product_rule`
2. 后台供应商详情增加镜片规则配置/查看
3. 增加产品快速比价接口
4. 增加报价历史日志

### 第二阶段：采购状态与订单面板

1. 扩展 `custom_item_component` 采购/供应商状态
2. 库存不足自动生成 `purchase_plan_detail`
3. 采购状态回写订单面板
4. 采购分析和价格走势

### 第三阶段：镜片供应商门户

1. 配置供应商角色菜单权限
2. 新增 admin 下的供应商委外单列表和详情
3. 镜片行状态更新
4. 批次物流单号录入
5. 扫码改状态
6. 导出脱敏
7. 增加供应商数据范围 trait

### 第四阶段：交期和异常预警

1. 供应商现货/生产中预计入库时间维护
2. 超期预警
3. 停产异常
4. 订单面板红色预警与消息通知

## 九、结论

这几项需求不需要新建“供应商系统”和“采购系统”，而应在现有 ERP 基础上完成眼镜业务扩展：

1. `supplier` 继续做供应商主数据。
2. `supplier_product` 继续承载镜框标准报价。
3. `optical_component_product_rule` 继续承载镜片参数匹配、默认产品、默认供应商，并按需补充交期/基准价字段。
4. `purchase_plan_detail / purchase_order / purchase_order_detail` 继续承载镜框采购。
5. `entrust + custom_item_component` 继续承载镜片委外和供应商门户。
6. 用 `supplier_product_quote_log` 和采购/委外历史单据完成价格走势与比价分析。

这样改动范围可控，也能和现有订单、定制履约、采购、委外、仓库、供应商模块形成真正的业务闭环。
