# 采购模块技术设计文档

> **文档版本**: v1.0-draft
> **编写部门**: 项目部
> **编写日期**: 2026-04-17
> **文档状态**: 草稿（协作编写中）
> **技术框架**: yudao-cloud

---

## 目录

1. [概述与设计目标](#1-概述与设计目标)
2. [模块划分与服务边界](#2-模块划分与服务边界)
3. [数据模型设计](#3-数据模型设计)
4. [服务调用关系](#4-服务调用关系)
5. [API接口设计概要](#5-api接口设计概要)
6. [与yudao-cloud集成说明](#6-与yudao-cloud集成说明)

---

## 1. 概述与设计目标

### 1.1 文档背景

本文档是《烘焙企业信息化系统微服务架构方案提案》的**采购模块技术设计文档**，聚焦于**采购模块（ims-module-purchase）**的开发。

采购模块是整个信息化系统的**供应链核心**，负责供应商管理、采购订单全流程（申请→订单→到货→退货）、价格协议管理以及应付账款管理。本模块为生产排程提供原料采购需求支撑，为成本核算提供采购价格数据，为财务管理提供应付账款信息。

本项目基于 **yudao-cloud** 微服务框架进行开发。采购模块作为自建业务模块之一，与已有的 `ims-module-masterdata`（主数据服务）、`ims-module-recipe`（配方与工艺服务）以及待开发的 `ims-module-crm`（客户关系管理）、`ims-module-order`（订单服务）协同工作，通过 OpenFeign 进行服务间通信。

### 1.2 模块定位（在整体架构中的位置）

#### 1.2.1 模块在系统中的角色

| 能力域 | 所属模块 | 说明 |
|--------|---------|------|
| API网关、路由、认证、限流 | `ims-gateway` | 直接使用 |
| 用户管理、角色权限、组织架构 | `system-service` | 直接使用 |
| 数据字典、租户管理 | `system-service` | 直接使用 |
| 文件上传/管理、配置参数 | `infra-service` | 直接使用 |
| 报表（积木报表+GoView大屏） | `ims-module-report` | 按需启用 |
| 原料管理、产品管理、仓库管理 | `ims-module-masterdata` | 采购模块依赖原料/仓库数据 |
| 配方管理、工艺管理 | `ims-module-recipe` | 采购模块通知配方成本变更 |
| 客户合同管理 | `ims-module-crm` | 预留，暂不开发 |
| 订单服务 | `ims-module-order` | 待开发，预留接口 |
| **供应商管理、采购订单、价格管理、应付账款** | **`ims-module-purchase`** | **本文档范围** |

#### 1.2.2 与现有模块的关系

| 关系方向 | 对方模块 | 交互方式 | 交互内容 |
|---------|---------|---------|---------|
| 依赖（上游） | `ims-module-masterdata` | Feign调用 | 查询原料信息、原料单位、单位换算 |
| 依赖（上游） | `ims-module-recipe` | Feign调用 | 查询配方BOM、通知原料价格变更 |
| 被依赖（下游） | `ims-module-recipe` | Feign调用（被调用） | 配方成本重算通知 |
| 预留 | `ims-module-crm` | Feign调用 | 合同关联（暂不开发） |
| 预留 | `ims-module-order` | Feign调用 | 订单关联（待开发） |

### 1.3 设计目标

| 目标 | 说明 | 验收标准 |
|------|------|---------|
| **供应商全生命周期管理** | 建立完整的供应商档案、报价、评估体系 | 供应商信息完整可查，支持多维度评估 |
| **采购订单全流程闭环** | 从采购申请到到货入库再到退货的完整流程 | 申请→审批→下单→到货→入库→退货全链路可追溯 |
| **价格精细化管理** | 支持价格协议、历史价格追踪 | 可查询任意原料的历史价格变动，支持价格协议管理 |
| **应付账款基础管理** | 付款记录和发票管理的基础能力 | 可记录付款和发票信息，支持基本查询统计 |
| **yudao-cloud兼容** | 完全适配yudao-cloud框架规范 | 可直接集成到yudao-cloud工程 |
| **数据一致性** | 通过Feign同步保证跨模块数据一致 | 原料价格变更能正确通知配方模块重算成本 |
| **多租户支持** | 所有表支持租户隔离 | 不同租户的供应商、订单数据完全隔离 |
| **精度统一** | 金额/数量字段统一使用DECIMAL(14,6) | 数据库存储和API传输精度一致 |

### 1.4 术语定义

| 术语 | 定义 | 示例 |
|------|------|------|
| **供应商** | 为企业提供原料、包材、耗材、设备等物资的供应方 | 面粉供应商、包装材料供应商 |
| **供应商报价** | 供应商对特定原料的报价信息，含价格、最小订购量、交货周期等 | 高筋面粉 3.5元/kg，最小订购量500kg |
| **首选供应商** | 某原料的默认推荐供应商，在采购申请中自动推荐 | 高筋面粉的首选供应商为"张记粮油" |
| **采购申请** | 业务部门发起的采购需求单据，需经审批后才能生成采购订单 | 生产部因排程需要发起面粉采购申请 |
| **采购订单** | 向供应商下达的正式采购合同，包含采购物料明细、交货要求等 | 向"张记粮油"订购高筋面粉500kg |
| **到货单** | 供应商送货时的收货记录，记录实际到货数量和质量 | 收到高筋面粉498kg，其中2kg不合格 |
| **退货单** | 因质量问题或其他原因向供应商退回物料的记录 | 退回不合格的高筋面粉2kg |
| **价格协议** | 与供应商签订的价格约定，含有效期、付款条件等 | 年度价格协议，高筋面粉3.5元/kg，有效期2026全年 |
| **应付账款** | 企业因采购应付给供应商的款项 | 采购订单PO-2026-001应付金额1750元 |
| **聚合API** | 一次Feign调用返回关联的完整数据 | 批量查询原料信息 |

---

## 2. 模块划分与服务边界

### 2.1 ims-module-purchase 在架构中的位置

```mermaid
flowchart LR
    subgraph YUDAO["yudao-cloud 自带模块"]
        GW[ims-gateway<br/>网关/认证/限流]
        SYS[system-service<br/>用户/权限/字典/租户]
        INFRA[infra-service<br/>文件/配置/代码生成]
        RPT[ims-module-report<br/>积木报表/GoView]
    end

    subgraph SELF["自建业务模块"]
        subgraph MD["ims-module-masterdata<br/>（主数据服务）"]
            M1[原料管理]
            M2[仓库管理]
        end

        subgraph RECIPE["ims-module-recipe<br/>（配方与工艺服务）"]
            M3[配方管理]
        end

        subgraph PURCHASE["ims-module-purchase<br/>（采购服务 · 17张表）"]
            P1[供应商管理<br/>4张表]
            P2[采购订单流程<br/>8张表]
            P3[价格管理<br/>3张表]
            P4[应付账款<br/>2张表]
        end

        subgraph CRM["ims-module-crm<br/>（预留）"]
            C1[合同管理]
        end

        subgraph ORDER["ims-module-order<br/>（待开发）"]
            O1[订单服务]
        end
    end

    GW --> SYS
    GW --> MD
    GW --> RECIPE
    GW --> PURCHASE

    P1 -->|"Feign:查询原料"| M1
    P2 -->|"Feign:查询原料/仓库"| M1
    P3 -->|"Feign:查询原料"| M1
    P3 -->|"Feign:通知价格变更"| M3
    P2 -.->|"Feign:合同关联（预留）"| C1
    P4 -.->|"Feign:订单关联（预留）"| O1

    style YUDAO fill:#f3e5f5
    style SELF fill:#e8f5e9
    style MD fill:#e3f2fd
    style RECIPE fill:#fce4ec
    style PURCHASE fill:#fff3e0
    style CRM fill:#f5f5f5
    style ORDER fill:#f5f5f5
```

### 2.2 与其他模块的关系（Mermaid图）

```mermaid
flowchart TB
    subgraph PURCHASE["ims-module-purchase（采购服务）"]
        subgraph S1[供应商管理]
            S1_1[供应商主数据]
            S1_2[供应商报价]
            S1_3[供应商联系人]
            S1_4[供应商评估]
        end

        subgraph S2[采购订单流程]
            S2_1[采购申请]
            S2_2[采购订单]
            S2_3[到货管理]
            S2_4[退货管理]
        end

        subgraph S3[价格管理]
            S3_1[价格协议]
            S3_2[价格历史]
        end

        subgraph S4[应付账款]
            S4_1[付款记录]
            S4_2[发票管理]
        end
    end

    subgraph MASTERDATA["ims-module-masterdata"]
        MD1[md_material<br/>原料主数据]
        MD2[md_material_unit<br/>原料单位]
        MD3[md_warehouse<br/>仓库数据]
    end

    subgraph RECIPE["ims-module-recipe"]
        RC1[recipe_bom_snapshot<br/>BOM快照]
        RC2[配方成本管理]
    end

    subgraph CRM["ims-module-crm（预留）"]
        CR1[crm_contract<br/>合同管理]
    end

    S1_2 -->|"引用原料ID"| MD1
    S2_1 -->|"引用原料ID"| MD1
    S2_2 -->|"引用供应商/原料"| S1_1
    S2_3 -->|"引用仓库"| MD3
    S3_1 -->|"引用供应商/原料"| S1_1
    S3_2 -->|"引用原料"| MD1
    S3_1 -->|"价格变更通知"| RC2

    S2 -.->|"合同关联（预留）"| CR1

    style PURCHASE fill:#fff3e0
    style MASTERDATA fill:#e3f2fd
    style RECIPE fill:#fce4ec
    style CRM fill:#f5f5f5
```

### 2.3 功能模块划分

#### 2.3.1 供应商管理（Supplier Management）

| 功能 | 说明 | 对应表 |
|------|------|--------|
| 供应商主数据 | 供应商编码、名称、类型、联系方式、银行信息、信用额度、评级等 | `pur_supplier` |
| 供应商原料报价 | 供应商对各原料的报价、最小订购量、交货周期、是否首选供应商 | `pur_supplier_material` |
| 供应商联系人 | 供应商的多个联系人信息，支持设置主要联系人 | `pur_supplier_contact` |
| 供应商评估 | 定期/临时评估，包含质量、交货、价格、服务四维评分 | `pur_supplier_eval` |

**边界说明：**
- 供应商数据由采购模块独立管理，不放在 masterdata 中
- 供应商报价中的原料信息通过 Feign 调用 masterdata 获取
- 供应商类型：1原料、2包材、3耗材、4设备、5其他
- 信用额度管理：`credit_limit` 为总额度，`credit_used` 为已用额度
- 供应商评级：1-5星，综合评估得分自动更新

#### 2.3.2 采购订单流程（Purchase Order Flow）

| 功能 | 说明 | 对应表 |
|------|------|--------|
| 采购申请 | 业务部门发起采购需求，支持手动/MRP自动/安全库存三种来源 | `pur_requisition` + `pur_requisition_item` |
| 采购订单 | 向供应商下达的正式采购订单，支持标准/紧急/样品三种类型 | `pur_order` + `pur_order_item` |
| 到货管理 | 供应商送货的收货记录，区分合格/不合格数量 | `pur_receive` + `pur_receive_item` |
| 退货管理 | 因质量问题等向供应商退回物料 | `pur_return` + `pur_return_item` |

**边界说明：**
- 采购申请状态流转：0草稿 → 1待审批 → 2已下达 → 3部分到货 → 4已到货 → 5已关闭
- 采购订单状态流转：0草稿 → 1已确认 → 2部分到货 → 3已到货 → 4已关闭
- 到货单状态流转：0待检验 → 1部分入库 → 2已入库 → 3退货
- 退货单状态流转：0草稿 → 1已确认 → 2已退货
- 采购订单可关联采购申请（`req_id`），一个申请可拆分为多个订单
- 到货单必须关联采购订单，退货单必须关联到货单
- 到货单中的合格数量更新采购订单的 `received_qty`
- 仓库信息通过 Feign 调用 masterdata 获取

#### 2.3.3 价格管理（Price Management）

| 功能 | 说明 | 对应表 |
|------|------|--------|
| 价格协议 | 与供应商签订的价格约定，支持年度/单次/临时协议 | `pur_price_agreement` + `pur_price_agreement_item` |
| 价格变动历史 | 记录原料价格的每次变动，便于追溯和分析 | `pur_price_history` |

**边界说明：**
- 价格协议状态：0草稿 → 1已生效 → 2已失效
- 价格协议类型：1年度协议、2单次协议、3临时协议
- 价格变更时自动记录到 `pur_price_history`，并通知配方模块重算成本
- 供应商报价（`pur_supplier_material`）和价格协议（`pur_price_agreement`）是两个维度：前者是供应商的报价信息，后者是正式签订的协议价格

#### 2.3.4 应付账款（Accounts Payable）

| 功能 | 说明 | 对应表 |
|------|------|--------|
| 付款记录 | 记录向供应商的付款，支持预付款/尾款/质保金等类型 | `pur_payment` |
| 发票管理 | 管理供应商开具的发票，支持增值税专用/普通/收据 | `pur_invoice` |

**边界说明：**
- 应付账款为基础设计，不包含复杂的账龄分析、对账等功能
- 付款类型：1预付款、2尾款、3质保金、4其他
- 付款状态：0待确认 → 1已确认 → 2已核销
- 发票状态：0待收 → 1已收 → 2已认证 → 3已付款
- 发票类型：1增值税专用、2增值税普通、3收据
- 付款记录和发票均可关联采购订单

---

## 3. 数据模型设计

### 3.1 ER模型总览（Mermaid ER图）

```mermaid
erDiagram
    %% ========== 供应商管理 ==========
    PUR_SUPPLIER ||--o{ PUR_SUPPLIER_MATERIAL : "报价"
    PUR_SUPPLIER ||--o{ PUR_SUPPLIER_CONTACT : "联系人"
    PUR_SUPPLIER ||--o{ PUR_SUPPLIER_EVAL : "评估"

    %% ========== 采购订单流程 ==========
    PUR_REQUISITION ||--o{ PUR_REQUISITION_ITEM : "申请明细"
    PUR_REQUISITION ||--o| PUR_ORDER : "生成订单"
    PUR_ORDER ||--o{ PUR_ORDER_ITEM : "订单明细"
    PUR_ORDER ||--o{ PUR_RECEIVE : "到货"
    PUR_RECEIVE ||--o{ PUR_RECEIVE_ITEM : "到货明细"
    PUR_RECEIVE ||--o{ PUR_RETURN : "退货"
    PUR_RETURN ||--o{ PUR_RETURN_ITEM : "退货明细"

    %% ========== 价格管理 ==========
    PUR_SUPPLIER ||--o{ PUR_PRICE_AGREEMENT : "价格协议"
    PUR_PRICE_AGREEMENT ||--o{ PUR_PRICE_AGREEMENT_ITEM : "协议明细"
    PUR_SUPPLIER ||--o{ PUR_PRICE_HISTORY : "价格历史"

    %% ========== 应付账款 ==========
    PUR_ORDER ||--o{ PUR_PAYMENT : "付款记录"
    PUR_ORDER ||--o{ PUR_INVOICE : "发票管理"

    %% ========== 外部引用 ==========
    PUR_SUPPLIER_MATERIAL }o--|| MD_MATERIAL : "引用原料"
    PUR_REQUISITION_ITEM }o--|| MD_MATERIAL : "引用原料"
    PUR_ORDER_ITEM }o--|| MD_MATERIAL : "引用原料"
    PUR_RECEIVE_ITEM }o--|| MD_MATERIAL : "引用原料"
    PUR_RETURN_ITEM }o--|| MD_MATERIAL : "引用原料"
    PUR_PRICE_AGREEMENT_ITEM }o--|| MD_MATERIAL : "引用原料"
    PUR_PRICE_HISTORY }o--|| MD_MATERIAL : "引用原料"
    PUR_RECEIVE }o--|| MD_WAREHOUSE : "引用仓库"
    PUR_ORDER }o--|| PUR_SUPPLIER : "关联供应商"
```

### 3.2 表清单

| 序号 | 表名 | 说明 | 所属子模块 | 阶段 |
|------|------|------|-----------|------|
| 1 | `pur_supplier` | 供应商主表 | 供应商管理 | 阶段1 |
| 2 | `pur_supplier_material` | 供应商原料报价表 | 供应商管理 | 阶段1 |
| 3 | `pur_supplier_contact` | 供应商联系人表 | 供应商管理 | 阶段1 |
| 4 | `pur_supplier_eval` | 供应商评估表 | 供应商管理 | 阶段1 |
| 5 | `pur_requisition` | 采购申请表 | 采购订单流程 | 阶段1 |
| 6 | `pur_requisition_item` | 采购申请明细表 | 采购订单流程 | 阶段1 |
| 7 | `pur_order` | 采购订单表 | 采购订单流程 | 阶段1 |
| 8 | `pur_order_item` | 采购订单明细表 | 采购订单流程 | 阶段1 |
| 9 | `pur_receive` | 采购到货单表 | 采购订单流程 | 阶段1 |
| 10 | `pur_receive_item` | 到货明细表 | 采购订单流程 | 阶段1 |
| 11 | `pur_return` | 采购退货单表 | 采购订单流程 | 阶段1 |
| 12 | `pur_return_item` | 退货明细表 | 采购订单流程 | 阶段1 |
| 13 | `pur_price_agreement` | 价格协议表 | 价格管理 | 阶段1 |
| 14 | `pur_price_agreement_item` | 价格协议明细表 | 价格管理 | 阶段1 |
| 15 | `pur_price_history` | 价格变动历史表 | 价格管理 | 阶段1 |
| 16 | `pur_payment` | 付款记录表 | 应付账款 | 阶段1 |
| 17 | `pur_invoice` | 发票管理表 | 应付账款 | 阶段1 |

**说明：** 本次设计共 17 张表，其中供应商管理 4 张、采购订单流程 8 张、价格管理 3 张、应付账款 2 张。所有表共用一个数据库，通过表名前缀 `pur_` 区分模块归属。

### 3.3 供应商管理表结构

#### 3.3.1 供应商主表 `pur_supplier`

```sql
CREATE TABLE `pur_supplier` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '供应商ID',
    `supplier_code`     VARCHAR(32) NOT NULL COMMENT '供应商编码',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称',
    `short_name`        VARCHAR(64) COMMENT '简称',
    `supplier_type`     TINYINT NOT NULL DEFAULT 1 COMMENT '供应商类型:1原料 2包材 3耗材 4设备 5其他',
    `contact_person`    VARCHAR(64) COMMENT '主要联系人',
    `contact_phone`     VARCHAR(32) COMMENT '联系电话',
    `email`             VARCHAR(64) COMMENT '邮箱',
    `address`           VARCHAR(256) COMMENT '详细地址',
    `province`          VARCHAR(32) COMMENT '省份',
    `city`              VARCHAR(32) COMMENT '城市',
    `district`          VARCHAR(32) COMMENT '区县',
    `bank_name`         VARCHAR(128) COMMENT '开户银行',
    `bank_account`      VARCHAR(64) COMMENT '银行账号',
    `tax_no`            VARCHAR(32) COMMENT '税号',
    `payment_terms`     VARCHAR(128) COMMENT '付款条件',
    `credit_limit`      DECIMAL(14,6) DEFAULT 0.000000 COMMENT '信用额度',
    `credit_used`       DECIMAL(14,6) DEFAULT 0.000000 COMMENT '已用信用额度',
    `rating`            TINYINT DEFAULT 0 COMMENT '供应商评级:1-5星',
    `status`            TINYINT DEFAULT 1 COMMENT '状态:1启用 0停用',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`supplier_code`, `tenant_id`),
    INDEX `idx_name` (`supplier_name`),
    INDEX `idx_type` (`supplier_type`),
    INDEX `idx_status` (`status`),
    INDEX `idx_rating` (`rating`)
) ENGINE=InnoDB COMMENT='供应商主表';
```

#### 3.3.2 供应商原料报价表 `pur_supplier_material`

```sql
CREATE TABLE `pur_supplier_material` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '报价ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`     VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`           BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`         VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `min_order_qty`     DECIMAL(14,6) DEFAULT 0.000000 COMMENT '最小订购量',
    `lead_time_days`    INT DEFAULT 0 COMMENT '交货周期（天）',
    `price`             DECIMAL(14,6) NOT NULL COMMENT '报价单价',
    `valid_from`        DATE COMMENT '报价有效期起',
    `valid_to`          DATE COMMENT '报价有效期止',
    `is_preferred`      BIT DEFAULT 0 COMMENT '是否首选供应商:1是 0否',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_material` (`material_id`),
    UNIQUE KEY `uk_supplier_material` (`supplier_id`, `material_id`, `tenant_id`)
) ENGINE=InnoDB COMMENT='供应商原料报价表';
```

> **设计说明：** 同一原料同一供应商只能有一条有效报价记录。`is_preferred` 标记该供应商是否为该原料的首选供应商，在采购申请中自动推荐首选供应商。原料编码和名称为冗余字段，通过 Feign 调用 masterdata 获取后存储，避免频繁跨服务查询。

#### 3.3.3 供应商联系人表 `pur_supplier_contact`

```sql
CREATE TABLE `pur_supplier_contact` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '联系人ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `contact_name`      VARCHAR(64) NOT NULL COMMENT '联系人姓名',
    `phone`             VARCHAR(32) COMMENT '电话',
    `email`             VARCHAR(64) COMMENT '邮箱',
    `position`          VARCHAR(64) COMMENT '职位',
    `is_primary`        BIT DEFAULT 0 COMMENT '是否主要联系人:1是 0否',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_supplier` (`supplier_id`)
) ENGINE=InnoDB COMMENT='供应商联系人表';
```

#### 3.3.4 供应商评估表 `pur_supplier_eval`

```sql
CREATE TABLE `pur_supplier_eval` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '评估ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `eval_date`         DATE NOT NULL COMMENT '评估日期',
    `eval_type`         TINYINT NOT NULL DEFAULT 1 COMMENT '评估类型:1定期 2临时',
    `quality_score`     DECIMAL(5,2) DEFAULT 0.00 COMMENT '质量评分（0-100）',
    `delivery_score`    DECIMAL(5,2) DEFAULT 0.00 COMMENT '交货评分（0-100）',
    `price_score`       DECIMAL(5,2) DEFAULT 0.00 COMMENT '价格评分（0-100）',
    `service_score`     DECIMAL(5,2) DEFAULT 0.00 COMMENT '服务评分（0-100）',
    `total_score`       DECIMAL(5,2) DEFAULT 0.00 COMMENT '综合评分（0-100）',
    `eval_remark`       VARCHAR(512) COMMENT '评估备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_eval_date` (`eval_date`),
    INDEX `idx_eval_type` (`eval_type`)
) ENGINE=InnoDB COMMENT='供应商评估表';
```

> **设计说明：** 综合评分计算公式：`total_score = quality_score * 0.3 + delivery_score * 0.3 + price_score * 0.2 + service_score * 0.2`。评估完成后自动更新 `pur_supplier.rating`（按综合评分映射为1-5星：90+为5星，80-89为4星，70-79为3星，60-69为2星，60以下为1星）。

### 3.4 采购订单表结构

#### 3.4.1 采购申请表 `pur_requisition`

```sql
CREATE TABLE `pur_requisition` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '申请ID',
    `req_code`          VARCHAR(32) NOT NULL COMMENT '申请编号',
    `req_type`          TINYINT NOT NULL DEFAULT 1 COMMENT '申请类型:1生产采购 2库存补货 3临时采购',
    `req_source`        TINYINT NOT NULL DEFAULT 1 COMMENT '申请来源:1手动 2MRP自动 3安全库存',
    `req_status`        TINYINT NOT NULL DEFAULT 0 COMMENT '申请状态:0草稿 1待审批 2已下达 3部分到货 4已到货 5已关闭',
    `req_date`          DATE NOT NULL COMMENT '申请日期',
    `expected_date`     DATE COMMENT '期望到货日期',
    `department`        VARCHAR(64) COMMENT '申请部门',
    `req_reason`        VARCHAR(512) COMMENT '申请原因',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`req_code`, `tenant_id`),
    INDEX `idx_status` (`req_status`),
    INDEX `idx_type` (`req_type`),
    INDEX `idx_source` (`req_source`),
    INDEX `idx_req_date` (`req_date`),
    INDEX `idx_expected_date` (`expected_date`)
) ENGINE=InnoDB COMMENT='采购申请表';
```

#### 3.4.2 采购申请明细表 `pur_requisition_item`

```sql
CREATE TABLE `pur_requisition_item` (
    `id`                    BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID',
    `req_id`                BIGINT NOT NULL COMMENT '申请ID',
    `material_id`           BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`         VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`         VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`               BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`             VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `demand_qty`            DECIMAL(14,6) NOT NULL COMMENT '需求数量',
    `demand_date`           DATE COMMENT '需求日期',
    `suggested_supplier_id` BIGINT COMMENT '建议供应商ID',
    `suggested_price`       DECIMAL(14,6) COMMENT '建议单价',
    `remark`                VARCHAR(256) COMMENT '备注',
    `creator`               VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`           DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`               VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`           DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`               BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`             BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_req` (`req_id`),
    INDEX `idx_material` (`material_id`),
    INDEX `idx_supplier` (`suggested_supplier_id`)
) ENGINE=InnoDB COMMENT='采购申请明细表';
```

> **设计说明：** `suggested_supplier_id` 和 `suggested_price` 在创建采购申请时自动填充，来源于 `pur_supplier_material` 中该原料的首选供应商报价。用户可手动修改。

#### 3.4.3 采购订单表 `pur_order`

```sql
CREATE TABLE `pur_order` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
    `order_code`        VARCHAR(32) NOT NULL COMMENT '订单编号',
    `req_id`            BIGINT COMMENT '关联采购申请ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `supplier_code`     VARCHAR(32) NOT NULL COMMENT '供应商编码（冗余）',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称（冗余）',
    `order_type`        TINYINT NOT NULL DEFAULT 1 COMMENT '订单类型:1标准采购 2紧急采购 3样品采购',
    `order_status`      TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态:0草稿 1已确认 2部分到货 3已到货 4已关闭',
    `order_date`        DATE NOT NULL COMMENT '订单日期',
    `expected_date`     DATE COMMENT '期望交货日期',
    `delivery_address`  VARCHAR(256) COMMENT '交货地址',
    `contact_person`    VARCHAR(64) COMMENT '收货联系人',
    `contact_phone`     VARCHAR(32) COMMENT '收货联系电话',
    `total_amount`      DECIMAL(14,6) DEFAULT 0.000000 COMMENT '订单总金额',
    `payment_terms`     VARCHAR(128) COMMENT '付款条件',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`order_code`, `tenant_id`),
    INDEX `idx_req` (`req_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_status` (`order_status`),
    INDEX `idx_type` (`order_type`),
    INDEX `idx_order_date` (`order_date`),
    INDEX `idx_expected_date` (`expected_date`)
) ENGINE=InnoDB COMMENT='采购订单表';
```

#### 3.4.4 采购订单明细表 `pur_order_item`

```sql
CREATE TABLE `pur_order_item` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID',
    `order_id`          BIGINT NOT NULL COMMENT '订单ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`     VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`           BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`         VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `order_qty`         DECIMAL(14,6) NOT NULL COMMENT '订购数量',
    `received_qty`      DECIMAL(14,6) DEFAULT 0.000000 COMMENT '已收货数量',
    `unit_price`        DECIMAL(14,6) NOT NULL COMMENT '单价',
    `total_price`       DECIMAL(14,6) NOT NULL COMMENT '小计金额',
    `expected_date`     DATE COMMENT '期望交货日期',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_order` (`order_id`),
    INDEX `idx_material` (`material_id`)
) ENGINE=InnoDB COMMENT='采购订单明细表';
```

> **设计说明：** `total_price = order_qty * unit_price`，由系统自动计算。`received_qty` 在到货确认时累加更新。当所有明细的 `received_qty >= order_qty` 时，订单状态自动更新为"已到货"。

#### 3.4.5 采购到货单表 `pur_receive`

```sql
CREATE TABLE `pur_receive` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '到货单ID',
    `receive_code`      VARCHAR(32) NOT NULL COMMENT '到货单编号',
    `order_id`          BIGINT NOT NULL COMMENT '采购订单ID',
    `order_code`        VARCHAR(32) NOT NULL COMMENT '采购订单编号（冗余）',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `supplier_code`     VARCHAR(32) NOT NULL COMMENT '供应商编码（冗余）',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称（冗余）',
    `receive_date`      DATE NOT NULL COMMENT '到货日期',
    `warehouse_id`      BIGINT COMMENT '入库仓库ID（引用md_warehouse）',
    `warehouse_name`    VARCHAR(64) COMMENT '仓库名称（冗余）',
    `receive_status`    TINYINT NOT NULL DEFAULT 0 COMMENT '到货状态:0待检验 1部分入库 2已入库 3退货',
    `total_qty`         DECIMAL(14,6) DEFAULT 0.000000 COMMENT '到货总数量',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`receive_code`, `tenant_id`),
    INDEX `idx_order` (`order_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_warehouse` (`warehouse_id`),
    INDEX `idx_status` (`receive_status`),
    INDEX `idx_receive_date` (`receive_date`)
) ENGINE=InnoDB COMMENT='采购到货单表';
```

#### 3.4.6 到货明细表 `pur_receive_item`

```sql
CREATE TABLE `pur_receive_item` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID',
    `receive_id`        BIGINT NOT NULL COMMENT '到货单ID',
    `order_item_id`     BIGINT NOT NULL COMMENT '采购订单明细ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`     VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`           BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`         VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `receive_qty`       DECIMAL(14,6) NOT NULL COMMENT '到货数量',
    `qualified_qty`     DECIMAL(14,6) DEFAULT 0.000000 COMMENT '合格数量',
    `unqualified_qty`   DECIMAL(14,6) DEFAULT 0.000000 COMMENT '不合格数量',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_receive` (`receive_id`),
    INDEX `idx_order_item` (`order_item_id`),
    INDEX `idx_material` (`material_id`)
) ENGINE=InnoDB COMMENT='到货明细表';
```

> **设计说明：** `receive_qty = qualified_qty + unqualified_qty`。到货确认时，`qualified_qty` 累加更新 `pur_order_item.received_qty`。不合格数量可生成退货单。

#### 3.4.7 采购退货单表 `pur_return`

```sql
CREATE TABLE `pur_return` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '退货单ID',
    `return_code`       VARCHAR(32) NOT NULL COMMENT '退货单编号',
    `receive_id`        BIGINT NOT NULL COMMENT '到货单ID',
    `order_id`          BIGINT NOT NULL COMMENT '采购订单ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `return_date`       DATE NOT NULL COMMENT '退货日期',
    `return_reason`     VARCHAR(512) NOT NULL COMMENT '退货原因',
    `return_status`     TINYINT NOT NULL DEFAULT 0 COMMENT '退货状态:0草稿 1已确认 2已退货',
    `total_amount`      DECIMAL(14,6) DEFAULT 0.000000 COMMENT '退货总金额',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`return_code`, `tenant_id`),
    INDEX `idx_receive` (`receive_id`),
    INDEX `idx_order` (`order_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_status` (`return_status`),
    INDEX `idx_return_date` (`return_date`)
) ENGINE=InnoDB COMMENT='采购退货单表';
```

#### 3.4.8 退货明细表 `pur_return_item`

```sql
CREATE TABLE `pur_return_item` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID',
    `return_id`         BIGINT NOT NULL COMMENT '退货单ID',
    `receive_item_id`   BIGINT NOT NULL COMMENT '到货明细ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`     VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`           BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`         VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `return_qty`        DECIMAL(14,6) NOT NULL COMMENT '退货数量',
    `unit_price`        DECIMAL(14,6) NOT NULL COMMENT '单价',
    `total_price`       DECIMAL(14,6) NOT NULL COMMENT '小计金额',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_return` (`return_id`),
    INDEX `idx_receive_item` (`receive_item_id`),
    INDEX `idx_material` (`material_id`)
) ENGINE=InnoDB COMMENT='退货明细表';
```

> **设计说明：** `total_price = return_qty * unit_price`，由系统自动计算。退货数量不能超过到货明细中的不合格数量。退货确认后，扣减 `pur_order_item.received_qty`。

### 3.5 价格管理表结构

#### 3.5.1 价格协议表 `pur_price_agreement`

```sql
CREATE TABLE `pur_price_agreement` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '协议ID',
    `agreement_code`    VARCHAR(32) NOT NULL COMMENT '协议编号',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称（冗余）',
    `agreement_type`    TINYINT NOT NULL DEFAULT 1 COMMENT '协议类型:1年度协议 2单次协议 3临时协议',
    `valid_from`        DATE NOT NULL COMMENT '生效日期',
    `valid_to`          DATE NOT NULL COMMENT '失效日期',
    `payment_terms`     VARCHAR(128) COMMENT '付款条件',
    `status`            TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿 1已生效 2已失效',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_code` (`agreement_code`, `tenant_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_status` (`status`),
    INDEX `idx_type` (`agreement_type`),
    INDEX `idx_valid` (`valid_from`, `valid_to`)
) ENGINE=InnoDB COMMENT='价格协议表';
```

#### 3.5.2 价格协议明细表 `pur_price_agreement_item`

```sql
CREATE TABLE `pur_price_agreement_item` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '明细ID',
    `agreement_id`      BIGINT NOT NULL COMMENT '协议ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `material_name`     VARCHAR(128) NOT NULL COMMENT '原料名称（冗余）',
    `unit_id`           BIGINT NOT NULL COMMENT '单位ID（引用md_material_unit）',
    `unit_code`         VARCHAR(16) NOT NULL COMMENT '单位编码（冗余）',
    `agreed_price`      DECIMAL(14,6) NOT NULL COMMENT '协议价格',
    `min_order_qty`     DECIMAL(14,6) DEFAULT 0.000000 COMMENT '最小订购量',
    `currency`          VARCHAR(8) DEFAULT 'CNY' COMMENT '币种',
    `remark`            VARCHAR(256) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_agreement` (`agreement_id`),
    INDEX `idx_material` (`material_id`),
    UNIQUE KEY `uk_agreement_material` (`agreement_id`, `material_id`, `tenant_id`)
) ENGINE=InnoDB COMMENT='价格协议明细表';
```

#### 3.5.3 价格变动历史表 `pur_price_history`

```sql
CREATE TABLE `pur_price_history` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '历史ID',
    `material_id`       BIGINT NOT NULL COMMENT '原料ID（引用md_material）',
    `material_code`     VARCHAR(32) NOT NULL COMMENT '原料编码（冗余）',
    `old_price`         DECIMAL(14,6) DEFAULT NULL COMMENT '原价格',
    `new_price`         DECIMAL(14,6) NOT NULL COMMENT '新价格',
    `change_date`       DATETIME NOT NULL COMMENT '变动日期',
    `change_reason`     VARCHAR(256) COMMENT '变动原因',
    `supplier_id`       BIGINT COMMENT '供应商ID',
    `supplier_name`     VARCHAR(128) COMMENT '供应商名称（冗余）',
    `changed_by`        VARCHAR(64) COMMENT '变更人',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    INDEX `idx_material` (`material_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_change_date` (`change_date`)
) ENGINE=InnoDB COMMENT='价格变动历史表';
```

> **设计说明：** 价格变动历史在以下场景自动记录：
> 1. 供应商报价变更时
> 2. 价格协议生效时
> 3. 手动调整采购价格时
>
> 同时触发 Feign 调用通知配方模块（`RecipeApi.notifyPriceChange`），标记相关配方 `cost_stale=1`。

### 3.6 应付账款表结构

#### 3.6.1 付款记录表 `pur_payment`

```sql
CREATE TABLE `pur_payment` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '付款ID',
    `payment_no`        VARCHAR(32) NOT NULL COMMENT '付款编号',
    `order_id`          BIGINT COMMENT '关联采购订单ID',
    `order_code`        VARCHAR(32) COMMENT '采购订单编号（冗余）',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `supplier_code`     VARCHAR(32) NOT NULL COMMENT '供应商编码（冗余）',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称（冗余）',
    `payment_type`      TINYINT NOT NULL DEFAULT 1 COMMENT '付款类型:1预付款 2尾款 3质保金 4其他',
    `amount`            DECIMAL(14,6) NOT NULL COMMENT '付款金额',
    `payment_date`      DATE NOT NULL COMMENT '付款日期',
    `bank_account`      VARCHAR(64) COMMENT '付款银行账号',
    `bank_name`         VARCHAR(128) COMMENT '付款银行名称',
    `receipt_no`        VARCHAR(64) COMMENT '收据号',
    `payment_status`    TINYINT NOT NULL DEFAULT 0 COMMENT '付款状态:0待确认 1已确认 2已核销',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_no` (`payment_no`, `tenant_id`),
    INDEX `idx_order` (`order_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_status` (`payment_status`),
    INDEX `idx_payment_date` (`payment_date`)
) ENGINE=InnoDB COMMENT='付款记录表';
```

#### 3.6.2 发票管理表 `pur_invoice`

```sql
CREATE TABLE `pur_invoice` (
    `id`                BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '发票ID',
    `invoice_no`        VARCHAR(64) NOT NULL COMMENT '发票号码',
    `invoice_type`      TINYINT NOT NULL DEFAULT 1 COMMENT '发票类型:1增值税专用 2增值税普通 3收据',
    `order_id`          BIGINT COMMENT '关联采购订单ID',
    `supplier_id`       BIGINT NOT NULL COMMENT '供应商ID',
    `supplier_code`     VARCHAR(32) NOT NULL COMMENT '供应商编码（冗余）',
    `supplier_name`     VARCHAR(128) NOT NULL COMMENT '供应商名称（冗余）',
    `invoice_amount`    DECIMAL(14,6) NOT NULL COMMENT '发票金额',
    `tax_amount`        DECIMAL(14,6) DEFAULT 0.000000 COMMENT '税额',
    `invoice_date`      DATE COMMENT '开票日期',
    `received_date`     DATE COMMENT '收票日期',
    `invoice_status`    TINYINT NOT NULL DEFAULT 0 COMMENT '发票状态:0待收 1已收 2已认证 3已付款',
    `remark`            VARCHAR(512) COMMENT '备注',
    `creator`           VARCHAR(64) DEFAULT '' COMMENT '创建者',
    `create_time`       DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updater`           VARCHAR(64) DEFAULT '' COMMENT '更新者',
    `update_time`       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `deleted`           BIT DEFAULT 0 COMMENT '是否删除',
    `tenant_id`         BIGINT DEFAULT 0 COMMENT '租户ID',
    UNIQUE KEY `uk_no` (`invoice_no`, `tenant_id`),
    INDEX `idx_order` (`order_id`),
    INDEX `idx_supplier` (`supplier_id`),
    INDEX `idx_status` (`invoice_status`),
    INDEX `idx_invoice_date` (`invoice_date`),
    INDEX `idx_received_date` (`received_date`)
) ENGINE=InnoDB COMMENT='发票管理表';
```

### 3.7 yudao-cloud标准字段说明

以上所有表结构遵循yudao-cloud的表设计规范，包含以下标准字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| `creator` | VARCHAR(64) | 创建者用户名 |
| `create_time` | DATETIME | 创建时间 |
| `updater` | VARCHAR(64) | 更新者用户名 |
| `update_time` | DATETIME | 更新时间 |
| `deleted` | BIT | 逻辑删除标识（0未删除 1已删除） |
| `tenant_id` | BIGINT | 租户ID（多租户隔离） |

**对应的DO基类：** `cn.lucky.ims.framework.mybatis.core.dataobject.BaseDO`

### 3.8 数据字典枚举汇总

以下枚举值建议在 yudao-cloud 的数据字典（`system_dict_data`）中配置，前端通过字典API获取显示文本。

| 字典类型 | 字典编码 | 枚举值 | 说明 |
|---------|---------|--------|------|
| 供应商类型 | `pur_supplier_type` | 1=原料, 2=包材, 3=耗材, 4=设备, 5=其他 | 用于 `pur_supplier` |
| 供应商评级 | `pur_supplier_rating` | 1=1星, 2=2星, 3=3星, 4=4星, 5=5星 | 用于 `pur_supplier` |
| 评估类型 | `pur_eval_type` | 1=定期, 2=临时 | 用于 `pur_supplier_eval` |
| 采购申请类型 | `pur_req_type` | 1=生产采购, 2=库存补货, 3=临时采购 | 用于 `pur_requisition` |
| 采购申请来源 | `pur_req_source` | 1=手动, 2=MRP自动, 3=安全库存 | 用于 `pur_requisition` |
| 采购申请状态 | `pur_req_status` | 0=草稿, 1=待审批, 2=已下达, 3=部分到货, 4=已到货, 5=已关闭 | 用于 `pur_requisition` |
| 采购订单类型 | `pur_order_type` | 1=标准采购, 2=紧急采购, 3=样品采购 | 用于 `pur_order` |
| 采购订单状态 | `pur_order_status` | 0=草稿, 1=已确认, 2=部分到货, 3=已到货, 4=已关闭 | 用于 `pur_order` |
| 到货状态 | `pur_receive_status` | 0=待检验, 1=部分入库, 2=已入库, 3=退货 | 用于 `pur_receive` |
| 退货状态 | `pur_return_status` | 0=草稿, 1=已确认, 2=已退货 | 用于 `pur_return` |
| 价格协议类型 | `pur_agreement_type` | 1=年度协议, 2=单次协议, 3=临时协议 | 用于 `pur_price_agreement` |
| 价格协议状态 | `pur_agreement_status` | 0=草稿, 1=已生效, 2=已失效 | 用于 `pur_price_agreement` |
| 付款类型 | `pur_payment_type` | 1=预付款, 2=尾款, 3=质保金, 4=其他 | 用于 `pur_payment` |
| 付款状态 | `pur_payment_status` | 0=待确认, 1=已确认, 2=已核销 | 用于 `pur_payment` |
| 发票类型 | `pur_invoice_type` | 1=增值税专用, 2=增值税普通, 3=收据 | 用于 `pur_invoice` |
| 发票状态 | `pur_invoice_status` | 0=待收, 1=已收, 2=已认证, 3=已付款 | 用于 `pur_invoice` |

---

## 4. 服务调用关系

### 4.1 模块内部调用层级

```mermaid
flowchart TB
    subgraph PUR_Controller["purchase Controller层"]
        C1[SupplierController<br/>供应商管理]
        C2[RequisitionController<br/>采购申请]
        C3[OrderController<br/>采购订单]
        C4[ReceiveController<br/>到货管理]
        C5[ReturnController<br/>退货管理]
        C6[PriceAgreementController<br/>价格协议]
        C7[PaymentController<br/>付款管理]
        C8[InvoiceController<br/>发票管理]
    end

    subgraph PUR_Service["purchase Service层"]
        S1[SupplierService]
        S2[RequisitionService]
        S3[OrderService]
        S4[ReceiveService]
        S5[ReturnService]
        S6[PriceAgreementService]
        S7[PaymentService]
        S8[InvoiceService]
    end

    subgraph PUR_Mapper["purchase Mapper层"]
        M1[SupplierMapper]
        M2[RequisitionMapper]
        M3[OrderMapper]
        M4[ReceiveMapper]
        M5[ReturnMapper]
        M6[PriceAgreementMapper]
        M7[PaymentMapper]
        M8[InvoiceMapper]
    end

    subgraph DB["数据库"]
        DB1[（共用数据库<br/>ruoyi-vue-pro）]
    end

    C1 --> S1 --> M1 --> DB1
    C2 --> S2 --> M2 --> DB1
    C3 --> S3 --> M3 --> DB1
    C4 --> S4 --> M4 --> DB1
    C5 --> S5 --> M5 --> DB1
    C6 --> S6 --> M6 --> DB1
    C7 --> S7 --> M7 --> DB1
    C8 --> S8 --> M8 --> DB1

    S2 -.->|"内部调用"| S1
    S3 -.->|"内部调用"| S1
    S3 -.->|"内部调用"| S2
    S4 -.->|"内部调用"| S3
    S5 -.->|"内部调用"| S4
    S6 -.->|"内部调用"| S1
```

### 4.2 与其他模块的Feign调用

```mermaid
sequenceDiagram
    participant Client as 前端/外部系统
    participant PUR as purchase采购服务
    participant MD as masterdata主数据服务
    participant RECIPE as recipe配方与工艺服务

    Note over Client,RECIPE: 场景1：创建采购申请（自动推荐供应商）
    Client->>PUR: 1. 创建采购申请
    PUR->>MD: 2. Feign查询原料信息（校验原料ID）
    PUR->>PUR: 3. 查询首选供应商报价
    PUR-->>Client: 返回申请（含建议供应商和价格）

    Note over Client,RECIPE: 场景2：创建采购订单
    Client->>PUR: 1. 创建采购订单
    PUR->>MD: 2. Feign查询原料信息
    PUR->>PUR: 3. 校验供应商信息
    PUR->>PUR: 4. 计算订单总金额
    PUR-->>Client: 返回订单

    Note over Client,RECIPE: 场景3：采购到货确认
    Client->>PUR: 1. 创建到货单
    PUR->>MD: 2. Feign查询仓库信息
    PUR->>PUR: 3. 更新订单已收货数量
    PUR->>PUR: 4. 检查订单是否全部到货
    PUR-->>Client: 返回到货单

    Note over Client,RECIPE: 场景4：价格变更通知配方模块
    PUR->>RECIPE: 1. Feign通知原料价格变更
    RECIPE->>RECIPE: 2. 标记相关配方cost_stale=1
    RECIPE-->>PUR: 返回受影响配方数

    Note over Client,RECIPE: 场景5：采购需求计算（MRP）
    Client->>PUR: 1. 触发MRP采购需求计算
    PUR->>RECIPE: 2. Feign获取配方BOM快照
    PUR->>MD: 3. Feign获取原料库存信息
    PUR->>PUR: 4. 计算净需求，生成采购申请
    PUR-->>Client: 返回生成的采购申请
```

### 4.3 核心业务流程（Mermaid时序图）

#### 4.3.1 采购订单全流程

```mermaid
flowchart TD
    A[创建采购申请] --> B{审批}
    B -->|通过| C[申请已下达]
    B -->|驳回| A
    C --> D[生成采购订单]
    D --> E[订单已确认]
    E --> F[供应商发货]
    F --> G[创建到货单]
    G --> H[到货检验]
    H -->|全部合格| I[合格入库]
    H -->|部分不合格| J[合格入库 + 不合格退货]
    J --> K[创建退货单]
    K --> L[退货已确认]
    L --> M[退货完成]
    I --> N{订单是否全部到货}
    J --> N
    N -->|是| O[订单已到货]
    N -->|否| P[订单部分到货]
    P --> F

    style A fill:#e3f2fd
    style D fill:#e8f5e9
    style G fill:#fff3e0
    style K fill:#fce4ec
    style O fill:#f3e5f5
```

#### 4.3.2 价格变更通知流程

```mermaid
flowchart TD
    A[供应商报价变更] --> B[更新pur_supplier_material]
    B --> C[记录pur_price_history]
    C --> D[Feign通知recipe模块]
    D --> E[标记相关配方cost_stale=1]
    E --> F[前端显示"成本待更新"标识]

    G[价格协议生效] --> H[更新pur_price_agreement状态]
    H --> I[记录pur_price_history]
    I --> D

    style A fill:#e3f2fd
    style G fill:#fff3e0
    style E fill:#fce4ec
```

#### 4.3.3 供应商评估流程

```mermaid
flowchart LR
    A[创建评估记录] --> B[填写四维评分]
    B --> C[系统计算综合评分]
    C --> D[更新供应商评级]
    D --> E[评估完成]

    style A fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#e8f5e9
```

### 4.4 核心调用链路说明

| 场景 | 调用链路 | 说明 |
|------|---------|------|
| **创建供应商** | 前端 -> purchase:SupplierController -> SupplierService -> SupplierMapper | 创建供应商主数据 |
| **创建供应商报价** | 前端 -> purchase:SupplierController -> SupplierService -> Feign:masterdata:MaterialApi（校验原料） -> SupplierMaterialMapper | 创建报价时跨模块校验原料 |
| **创建采购申请** | 前端 -> purchase:RequisitionController -> RequisitionService -> 查询首选供应商报价 -> RequisitionMapper | 自动填充建议供应商和价格 |
| **审批采购申请** | 前端 -> purchase:RequisitionController -> RequisitionService.updateStatus | 更新申请状态 |
| **生成采购订单** | 前端 -> purchase:OrderController -> OrderService -> Feign:masterdata:MaterialApi（校验原料） -> OrderMapper | 从申请生成订单，关联req_id |
| **确认采购订单** | 前端 -> purchase:OrderController -> OrderService.confirmOrder | 订单状态草稿→已确认 |
| **创建到货单** | 前端 -> purchase:ReceiveController -> ReceiveService -> Feign:masterdata:MaterialApi（查询仓库） -> ReceiveMapper | 创建到货单，关联订单 |
| **到货确认** | 前端 -> purchase:ReceiveController -> ReceiveService.confirmReceive -> OrderService.updateReceivedQty | 确认到货，更新订单已收货数量 |
| **创建退货单** | 前端 -> purchase:ReturnController -> ReturnService -> ReturnMapper | 从到货单不合格数量创建退货单 |
| **退货确认** | 前端 -> purchase:ReturnController -> ReturnService.confirmReturn -> OrderService.deductReceivedQty | 确认退货，扣减订单已收货数量 |
| **价格协议生效** | 前端 -> purchase:PriceAgreementController -> PriceAgreementService.activate -> PriceHistoryMapper（记录历史） -> Feign:recipe:RecipeApi.notifyPriceChange | 协议生效时通知配方模块 |
| **供应商评估** | 前端 -> purchase:SupplierController -> SupplierService.evaluate -> SupplierMapper.updateRating | 评估后自动更新供应商评级 |
| **记录付款** | 前端 -> purchase:PaymentController -> PaymentService -> PaymentMapper | 记录付款信息 |
| **登记发票** | 前端 -> purchase:InvoiceController -> InvoiceService -> InvoiceMapper | 登记发票信息 |

---

## 5. API接口设计概要

### 5.1 接口命名规范

遵循yudao-cloud的RESTful API规范，按模块划分路径前缀：

| 操作 | HTTP方法 | URL格式 |
|------|---------|---------|
| 创建 | POST | `/admin-api/purchase/xxx/create` |
| 更新 | PUT | `/admin-api/purchase/xxx/update` |
| 删除 | DELETE | `/admin-api/purchase/xxx/delete?id=` |
| 获取详情 | GET | `/admin-api/purchase/xxx/get?id=` |
| 分页查询 | GET | `/admin-api/purchase/xxx/page` |
| 列表查询 | GET | `/admin-api/purchase/xxx/list` |

### 5.1 供应商管理API

#### 5.1.1 供应商主数据

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建供应商 | POST | `/admin-api/purchase/supplier/create` | 创建供应商 |
| 更新供应商 | PUT | `/admin-api/purchase/supplier/update` | 更新供应商 |
| 删除供应商 | DELETE | `/admin-api/purchase/supplier/delete?id=` | 删除供应商（校验是否有关联订单） |
| 获取供应商详情 | GET | `/admin-api/purchase/supplier/get?id=` | 获取供应商详情（含联系人和报价） |
| 分页查询供应商 | GET | `/admin-api/purchase/supplier/page` | 分页查询供应商列表 |
| 获取供应商列表 | GET | `/admin-api/purchase/supplier/list` | 获取全部启用的供应商列表（下拉选择用） |

#### 5.1.2 供应商报价

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建供应商报价 | POST | `/admin-api/purchase/supplier/material/create` | 创建/更新供应商报价 |
| 删除供应商报价 | DELETE | `/admin-api/purchase/supplier/material/delete?id=` | 删除供应商报价 |
| 获取供应商报价列表 | GET | `/admin-api/purchase/supplier/material/list?supplierId=` | 获取指定供应商的全部报价 |
| 获取原料报价列表 | GET | `/admin-api/purchase/supplier/material/by-material/list?materialId=` | 获取指定原料的全部供应商报价 |

#### 5.1.3 供应商联系人

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建联系人 | POST | `/admin-api/purchase/supplier/contact/create` | 创建供应商联系人 |
| 更新联系人 | PUT | `/admin-api/purchase/supplier/contact/update` | 更新联系人 |
| 删除联系人 | DELETE | `/admin-api/purchase/supplier/contact/delete?id=` | 删除联系人 |
| 获取联系人列表 | GET | `/admin-api/purchase/supplier/contact/list?supplierId=` | 获取指定供应商的联系人列表 |

#### 5.1.4 供应商评估

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建评估 | POST | `/admin-api/purchase/supplier/eval/create` | 创建供应商评估 |
| 获取评估列表 | GET | `/admin-api/purchase/supplier/eval/list?supplierId=` | 获取指定供应商的评估历史 |
| 获取评估详情 | GET | `/admin-api/purchase/supplier/eval/get?id=` | 获取评估详情 |

### 5.2 采购申请API

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建采购申请 | POST | `/admin-api/purchase/requisition/create` | 创建采购申请（含明细） |
| 更新采购申请 | PUT | `/admin-api/purchase/requisition/update` | 更新采购申请（仅草稿可修改） |
| 删除采购申请 | DELETE | `/admin-api/purchase/requisition/delete?id=` | 删除采购申请（仅草稿可删除） |
| 提交审批 | POST | `/admin-api/purchase/requisition/submit?id=` | 提交审批（草稿→待审批） |
| 审批通过 | POST | `/admin-api/purchase/requisition/approve?id=` | 审批通过（待审批→已下达） |
| 审批驳回 | POST | `/admin-api/purchase/requisition/reject?id=` | 审批驳回（待审批→草稿） |
| 获取申请详情 | GET | `/admin-api/purchase/requisition/get?id=` | 获取申请详情（含明细） |
| 分页查询申请 | GET | `/admin-api/purchase/requisition/page` | 分页查询采购申请列表 |
| 关闭申请 | POST | `/admin-api/purchase/requisition/close?id=` | 手动关闭申请 |

**创建采购申请请求示例：**

```json
{
    "reqCode": "REQ-2026-001",
    "reqType": 1,
    "reqSource": 1,
    "reqDate": "2026-04-17",
    "expectedDate": "2026-04-20",
    "department": "生产部",
    "reqReason": "生产排程需求",
    "items": [
        {
            "materialId": 1,
            "materialCode": "M001",
            "materialName": "高筋面粉",
            "unitId": 1,
            "unitCode": "kg",
            "demandQty": 500.000000,
            "demandDate": "2026-04-20",
            "suggestedSupplierId": 1,
            "suggestedPrice": 3.500000
        },
        {
            "materialId": 2,
            "materialCode": "M002",
            "materialName": "白砂糖",
            "unitId": 1,
            "unitCode": "kg",
            "demandQty": 100.000000,
            "demandDate": "2026-04-20",
            "suggestedSupplierId": 2,
            "suggestedPrice": 5.200000
        }
    ]
}
```

### 5.3 采购订单API

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建采购订单 | POST | `/admin-api/purchase/order/create` | 创建采购订单（含明细） |
| 更新采购订单 | PUT | `/admin-api/purchase/order/update` | 更新采购订单（仅草稿可修改） |
| 删除采购订单 | DELETE | `/admin-api/purchase/order/delete?id=` | 删除采购订单（仅草稿可删除） |
| 确认订单 | POST | `/admin-api/purchase/order/confirm?id=` | 确认订单（草稿→已确认） |
| 关闭订单 | POST | `/admin-api/purchase/order/close?id=` | 关闭订单 |
| 获取订单详情 | GET | `/admin-api/purchase/order/get?id=` | 获取订单详情（含明细） |
| 分页查询订单 | GET | `/admin-api/purchase/order/page` | 分页查询采购订单列表 |
| 从申请生成订单 | POST | `/admin-api/purchase/order/from-requisition` | 从采购申请生成采购订单 |

**创建采购订单请求示例：**

```json
{
    "orderCode": "PO-2026-001",
    "reqId": 1,
    "supplierId": 1,
    "supplierCode": "SUP001",
    "supplierName": "张记粮油",
    "orderType": 1,
    "orderDate": "2026-04-17",
    "expectedDate": "2026-04-20",
    "deliveryAddress": "北京市朝阳区XX路XX号",
    "contactPerson": "李明",
    "contactPhone": "13800138000",
    "paymentTerms": "月结30天",
    "items": [
        {
            "materialId": 1,
            "materialCode": "M001",
            "materialName": "高筋面粉",
            "unitId": 1,
            "unitCode": "kg",
            "orderQty": 500.000000,
            "unitPrice": 3.500000,
            "totalPrice": 1750.000000,
            "expectedDate": "2026-04-20"
        }
    ]
}
```

### 5.4 到货/退货API

#### 5.4.1 到货管理

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建到货单 | POST | `/admin-api/purchase/receive/create` | 创建到货单（含明细） |
| 确认到货 | POST | `/admin-api/purchase/receive/confirm?id=` | 确认到货（更新合格/不合格数量，更新订单已收货数量） |
| 获取到货单详情 | GET | `/admin-api/purchase/receive/get?id=` | 获取到货单详情（含明细） |
| 分页查询到货单 | GET | `/admin-api/purchase/receive/page` | 分页查询到货单列表 |
| 按订单查询到货记录 | GET | `/admin-api/purchase/receive/by-order/list?orderId=` | 获取指定订单的全部到货记录 |

#### 5.4.2 退货管理

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建退货单 | POST | `/admin-api/purchase/return/create` | 创建退货单（含明细，从到货单不合格数量生成） |
| 确认退货 | POST | `/admin-api/purchase/return/confirm?id=` | 确认退货（扣减订单已收货数量） |
| 获取退货单详情 | GET | `/admin-api/purchase/return/get?id=` | 获取退货单详情（含明细） |
| 分页查询退货单 | GET | `/admin-api/purchase/return/page` | 分页查询退货单列表 |
| 按到货单查询退货记录 | GET | `/admin-api/purchase/return/by-receive/list?receiveId=` | 获取指定到货单的退货记录 |

**创建到货单请求示例：**

```json
{
    "receiveCode": "RCV-2026-001",
    "orderId": 1,
    "orderCode": "PO-2026-001",
    "supplierId": 1,
    "supplierCode": "SUP001",
    "supplierName": "张记粮油",
    "receiveDate": "2026-04-20",
    "warehouseId": 1,
    "warehouseName": "原料仓",
    "items": [
        {
            "orderItemId": 1,
            "materialId": 1,
            "materialCode": "M001",
            "materialName": "高筋面粉",
            "unitId": 1,
            "unitCode": "kg",
            "receiveQty": 500.000000,
            "qualifiedQty": 498.000000,
            "unqualifiedQty": 2.000000
        }
    ]
}
```

### 5.5 价格管理API

#### 5.5.1 价格协议

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建价格协议 | POST | `/admin-api/purchase/price-agreement/create` | 创建价格协议（含明细） |
| 更新价格协议 | PUT | `/admin-api/purchase/price-agreement/update` | 更新价格协议（仅草稿可修改） |
| 删除价格协议 | DELETE | `/admin-api/purchase/price-agreement/delete?id=` | 删除价格协议（仅草稿可删除） |
| 生效价格协议 | POST | `/admin-api/purchase/price-agreement/activate?id=` | 生效协议（草稿→已生效），记录价格历史，通知配方模块 |
| 失效价格协议 | POST | `/admin-api/purchase/price-agreement/inactivate?id=` | 失效协议（已生效→已失效） |
| 获取协议详情 | GET | `/admin-api/purchase/price-agreement/get?id=` | 获取协议详情（含明细） |
| 分页查询协议 | GET | `/admin-api/purchase/price-agreement/page` | 分页查询价格协议列表 |
| 查询有效协议 | GET | `/admin-api/purchase/price-agreement/active/list?supplierId=` | 获取指定供应商的当前有效协议 |

#### 5.5.2 价格历史

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 获取价格变动历史 | GET | `/admin-api/purchase/price-history/list?materialId=` | 获取指定原料的价格变动历史 |
| 分页查询价格历史 | GET | `/admin-api/purchase/price-history/page` | 分页查询价格变动历史 |

**创建价格协议请求示例：**

```json
{
    "agreementCode": "PA-2026-001",
    "supplierId": 1,
    "supplierName": "张记粮油",
    "agreementType": 1,
    "validFrom": "2026-01-01",
    "validTo": "2026-12-31",
    "paymentTerms": "月结30天",
    "items": [
        {
            "materialId": 1,
            "materialCode": "M001",
            "materialName": "高筋面粉",
            "unitId": 1,
            "unitCode": "kg",
            "agreedPrice": 3.500000,
            "minOrderQty": 200.000000,
            "currency": "CNY"
        },
        {
            "materialId": 2,
            "materialCode": "M002",
            "materialName": "白砂糖",
            "unitId": 1,
            "unitCode": "kg",
            "agreedPrice": 5.200000,
            "minOrderQty": 100.000000,
            "currency": "CNY"
        }
    ]
}
```

### 5.6 应付账款API

#### 5.6.1 付款记录

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建付款记录 | POST | `/admin-api/purchase/payment/create` | 创建付款记录 |
| 更新付款记录 | PUT | `/admin-api/purchase/payment/update` | 更新付款记录（仅待确认可修改） |
| 确认付款 | POST | `/admin-api/purchase/payment/confirm?id=` | 确认付款（待确认→已确认） |
| 核销付款 | POST | `/admin-api/purchase/payment/write-off?id=` | 核销付款（已确认→已核销） |
| 获取付款详情 | GET | `/admin-api/purchase/payment/get?id=` | 获取付款详情 |
| 分页查询付款 | GET | `/admin-api/purchase/payment/page` | 分页查询付款记录 |
| 按供应商查询付款 | GET | `/admin-api/purchase/payment/by-supplier/list?supplierId=` | 获取指定供应商的付款记录 |

#### 5.6.2 发票管理

| 接口 | 方法 | URL | 说明 |
|------|------|-----|------|
| 创建发票 | POST | `/admin-api/purchase/invoice/create` | 登记发票信息 |
| 更新发票 | PUT | `/admin-api/purchase/invoice/update` | 更新发票信息 |
| 确认收票 | POST | `/admin-api/purchase/invoice/receive?id=` | 确认收票（待收→已收） |
| 认证发票 | POST | `/admin-api/purchase/invoice/verify?id=` | 发票认证（已收→已认证） |
| 获取发票详情 | GET | `/admin-api/purchase/invoice/get?id=` | 获取发票详情 |
| 分页查询发票 | GET | `/admin-api/purchase/invoice/page` | 分页查询发票列表 |
| 按供应商查询发票 | GET | `/admin-api/purchase/invoice/by-supplier/list?supplierId=` | 获取指定供应商的发票列表 |

**创建付款记录请求示例：**

```json
{
    "paymentNo": "PAY-2026-001",
    "orderId": 1,
    "orderCode": "PO-2026-001",
    "supplierId": 1,
    "supplierCode": "SUP001",
    "supplierName": "张记粮油",
    "paymentType": 2,
    "amount": 1750.000000,
    "paymentDate": "2026-05-20",
    "bankAccount": "6222021234567890",
    "bankName": "中国工商银行",
    "receiptNo": "RCP-2026-001"
}
```

### 5.7 供外部调用的RPC API

以下API供其他微服务调用（通过Feign）：

| 服务 | 接口 | URL | 说明 |
|------|------|-----|------|
| **recipe配方服务** | 通知原料价格变更 | POST `/rpc-api/purchase/price-history/notify` | 采购模块主动调用recipe，通知价格变更 |
| **masterdata主数据服务** | 批量获取供应商信息 | POST `/rpc-api/purchase/supplier/batch-get` | 按供应商ID批量获取供应商信息 |
| **masterdata主数据服务** | 获取原料首选供应商 | GET `/rpc-api/purchase/supplier/preferred/get?materialId=` | 获取指定原料的首选供应商及报价 |
| **order订单服务（预留）** | 查询采购订单状态 | GET `/rpc-api/purchase/order/status/get?orderCode=` | 查询采购订单当前状态 |
| **order订单服务（预留）** | 查询供应商应付汇总 | GET `/rpc-api/purchase/payment/summary/get?supplierId=` | 查询供应商的应付金额汇总 |

**原料首选供应商查询响应示例：**

```
GET /rpc-api/purchase/supplier/preferred/get?materialId=1

响应体：
{
    "code": 0,
    "data": {
        "supplierId": 1,
        "supplierCode": "SUP001",
        "supplierName": "张记粮油",
        "price": 3.500000,
        "unitCode": "kg",
        "minOrderQty": 200.000000,
        "leadTimeDays": 3
    }
}
```

---

## 6. 与yudao-cloud集成说明

### 6.1 模块集成步骤

#### 步骤1：创建模块目录

在 yudao-cloud 项目根目录下创建采购模块：

```bash
mkdir -p ims-module-purchase/ims-module-purchase-api
mkdir -p ims-module-purchase/ims-module-purchase-biz
```

#### 步骤2：配置 pom.xml

**父模块 pom.xml 添加：**

```xml
<modules>
    <!-- 其他模块 -->
    <module>ims-module-purchase</module>
</modules>
```

**ims-module-purchase/pom.xml：**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>cn.iocoder.boot</groupId>
        <artifactId>yudao</artifactId>
        <version>${revision}</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <artifactId>ims-module-purchase</artifactId>
    <name>${project.artifactId}</name>

    <modules>
        <module>ims-module-purchase-api</module>
        <module>ims-module-purchase-biz</module>
    </modules>
</project>
```

#### 步骤3：配置数据库

采购模块与其他模块**共用一个数据库**（`ruoyi-vue-pro`），通过表名前缀 `pur_` 区分模块归属：

```yaml
spring:
  datasource:
    dynamic:
      datasource:
        master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
          username: root
          password: password
```

#### 步骤4：执行SQL脚本

将本文档第3章的DDL脚本导入数据库。

#### 步骤5：配置菜单权限

在 `system_menu` 表中添加采购模块的菜单：

```sql
-- 采购管理一级菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('采购管理', '', 1, 55, 0, '/purchase', 'shopping', NULL);

-- 供应商管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('供应商管理', 'purchase:supplier:query', 2, 1, {上级ID}, 'supplier', '', 'purchase/supplier/index');

-- 采购申请菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('采购申请', 'purchase:requisition:query', 2, 2, {上级ID}, 'requisition', '', 'purchase/requisition/index');

-- 采购订单菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('采购订单', 'purchase:order:query', 2, 3, {上级ID}, 'order', '', 'purchase/order/index');

-- 到货管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('到货管理', 'purchase:receive:query', 2, 4, {上级ID}, 'receive', '', 'purchase/receive/index');

-- 价格管理菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('价格管理', 'purchase:price-agreement:query', 2, 5, {上级ID}, 'price-agreement', '', 'purchase/price-agreement/index');

-- 应付账款菜单
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component)
VALUES ('应付账款', 'purchase:payment:query', 2, 6, {上级ID}, 'payment', '', 'purchase/payment/index');
```

### 6.2 Feign客户端配置

#### 6.2.1 purchase模块调用的Feign接口（依赖上游）

在 `ims-module-purchase-api` 模块中引用上游模块的Feign客户端：

```java
// 引用 masterdata 模块的 Feign 接口
// 已在 ims-module-masterdata-api 中定义，直接依赖即可
import cn.lucky.ims.module.masterdata.api.MaterialApi;
import cn.lucky.ims.module.masterdata.api.ProductApi;

// 引用 recipe 模块的 Feign 接口
// 已在 ims-module-recipe-api 中定义，直接依赖即可
import cn.lucky.ims.module.recipe.api.RecipeApi;
```

**ims-module-purchase-biz 的 pom.xml 需添加依赖：**

```xml
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>ims-module-masterdata-api</artifactId>
    <version>${revision}</version>
</dependency>
<dependency>
    <groupId>cn.iocoder.boot</groupId>
    <artifactId>ims-module-recipe-api</artifactId>
    <version>${revision}</version>
</dependency>
```

#### 6.2.2 purchase模块提供的Feign接口（供下游调用）

在 `ims-module-purchase-api` 模块中定义：

```java
package cn.lucky.ims.module.purchase.api;

import cn.lucky.ims.framework.common.pojo.CommonResult;
import cn.lucky.ims.module.purchase.api.dto.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.List;

@FeignClient(value = "purchase-server", contextId = "supplierApi")
public interface SupplierApi {

    /**
     * 批量获取供应商信息
     */
    @PostMapping("/rpc-api/purchase/supplier/batch-get")
    CommonResult<List<SupplierRespDTO>> batchGetSuppliers(@RequestBody List<Long> ids);

    /**
     * 获取原料的首选供应商及报价
     */
    @GetMapping("/rpc-api/purchase/supplier/preferred/get")
    CommonResult<PreferredSupplierRespDTO> getPreferredSupplier(
            @RequestParam("materialId") Long materialId);
}

@FeignClient(value = "purchase-server", contextId = "purchasePriceApi")
public interface PurchasePriceApi {

    /**
     * 获取原料当前有效价格（从价格协议和供应商报价中取最优）
     */
    @GetMapping("/rpc-api/purchase/price/current/get")
    CommonResult<MaterialCurrentPriceRespDTO> getCurrentPrice(
            @RequestParam("materialId") Long materialId);
}

@FeignClient(value = "purchase-server", contextId = "purchaseOrderApi")
public interface PurchaseOrderApi {

    /**
     * 查询采购订单状态
     */
    @GetMapping("/rpc-api/purchase/order/status/get")
    CommonResult<PurchaseOrderStatusRespDTO> getOrderStatus(
            @RequestParam("orderCode") String orderCode);

    /**
     * 查询供应商应付金额汇总
     */
    @GetMapping("/rpc-api/purchase/payment/summary/get")
    CommonResult<SupplierPaymentSummaryRespDTO> getPaymentSummary(
            @RequestParam("supplierId") Long supplierId);
}
```

**Feign DTO定义：**

```java
package cn.lucky.ims.module.purchase.api.dto;

import lombok.Data;
import java.math.BigDecimal;

/**
 * 首选供应商响应
 */
@Data
public class PreferredSupplierRespDTO {
    private Long supplierId;
    private String supplierCode;
    private String supplierName;
    private BigDecimal price;       // DECIMAL(14,6)
    private String unitCode;
    private BigDecimal minOrderQty; // DECIMAL(14,6)
    private Integer leadTimeDays;
}

/**
 * 原料当前有效价格响应
 */
@Data
public class MaterialCurrentPriceRespDTO {
    private Long materialId;
    private String materialCode;
    private String materialName;
    private BigDecimal currentPrice;  // DECIMAL(14,6)
    private String unitCode;
    private Long supplierId;
    private String supplierName;
    private String priceSource;       // 价格来源：协议价格/供应商报价
}

/**
 * 采购订单状态响应
 */
@Data
public class PurchaseOrderStatusRespDTO {
    private Long orderId;
    private String orderCode;
    private Integer orderStatus;
    private BigDecimal totalAmount;   // DECIMAL(14,6)
    private BigDecimal receivedAmount; // DECIMAL(14,6) 已到货金额
}

/**
 * 供应商应付汇总响应
 */
@Data
public class SupplierPaymentSummaryRespDTO {
    private Long supplierId;
    private String supplierCode;
    private String supplierName;
    private BigDecimal totalOrderAmount;  // DECIMAL(14,6) 订单总金额
    private BigDecimal totalPaidAmount;   // DECIMAL(14,6) 已付金额
    private BigDecimal totalUnpaidAmount; // DECIMAL(14,6) 未付金额
}

/**
 * 供应商响应DTO
 */
@Data
public class SupplierRespDTO {
    private Long id;
    private String supplierCode;
    private String supplierName;
    private String shortName;
    private Integer supplierType;
    private String contactPerson;
    private String contactPhone;
    private String email;
    private Integer rating;
    private Integer status;
}
```

### 6.3 报表方案（SQL数据集示例）

使用 yudao-cloud 自带的 `ims-module-report` 模块，由于所有模块共用一个数据库，报表可直接通过SQL查询关联数据。

#### 6.3.1 供应商采购汇总报表

```sql
SELECT
    s.supplier_code AS '供应商编码',
    s.supplier_name AS '供应商名称',
    s.supplier_type AS '供应商类型',
    CASE s.supplier_type
        WHEN 1 THEN '原料'
        WHEN 2 THEN '包材'
        WHEN 3 THEN '耗材'
        WHEN 4 THEN '设备'
        WHEN 5 THEN '其他'
    END AS '供应商类型说明',
    s.rating AS '评级（星）',
    s.credit_limit AS '信用额度',
    s.credit_used AS '已用额度',
    COUNT(DISTINCT po.id) AS '订单总数',
    COALESCE(SUM(po.total_amount), 0) AS '采购总金额',
    COALESCE(SUM(pm.amount), 0) AS '已付金额',
    COALESCE(SUM(po.total_amount), 0) - COALESCE(SUM(pm.amount), 0) AS '未付金额',
    AVG(se.total_score) AS '平均评估得分'
FROM pur_supplier s
LEFT JOIN pur_order po ON s.id = po.supplier_id AND po.deleted = 0
LEFT JOIN pur_payment pm ON po.id = pm.order_id AND pm.deleted = 0 AND pm.payment_status = 1
LEFT JOIN pur_supplier_eval se ON s.id = se.supplier_id AND se.deleted = 0
WHERE s.deleted = 0
GROUP BY s.id, s.supplier_code, s.supplier_name, s.supplier_type, s.rating, s.credit_limit, s.credit_used
ORDER BY SUM(po.total_amount) DESC
```

#### 6.3.2 采购订单执行明细报表

```sql
SELECT
    po.order_code AS '订单编号',
    po.order_date AS '订单日期',
    po.supplier_name AS '供应商名称',
    CASE po.order_type WHEN 1 THEN '标准采购' WHEN 2 THEN '紧急采购' WHEN 3 THEN '样品采购' END AS '订单类型',
    CASE po.order_status WHEN 0 THEN '草稿' WHEN 1 THEN '已确认' WHEN 2 THEN '部分到货' WHEN 3 THEN '已到货' WHEN 4 THEN '已关闭' END AS '订单状态',
    poi.material_code AS '原料编码',
    poi.material_name AS '原料名称',
    poi.unit_code AS '单位',
    poi.order_qty AS '订购数量',
    poi.received_qty AS '已收货数量',
    poi.order_qty - poi.received_qty AS '未到货数量',
    poi.unit_price AS '单价（元）',
    poi.total_price AS '小计金额（元）',
    po.total_amount AS '订单总金额（元）',
    po.expected_date AS '期望交货日期'
FROM pur_order po
JOIN pur_order_item poi ON po.id = poi.order_id AND poi.deleted = 0
WHERE po.deleted = 0
ORDER BY po.order_date DESC, po.order_code
```

#### 6.3.3 供应商价格对比分析报表

```sql
SELECT
    m.material_code AS '原料编码',
    m.material_name AS '原料名称',
    m.spec AS '规格',
    s1.supplier_name AS '供应商A',
    sm1.price AS '供应商A报价',
    s2.supplier_name AS '供应商B',
    sm2.price AS '供应商B报价',
    s3.supplier_name AS '供应商C',
    sm3.price AS '供应商C报价',
    pa.agreed_price AS '协议价格',
    pa.payment_terms AS '协议付款条件',
    ph.new_price AS '最新价格',
    ph.change_date AS '价格变动日期',
    ph.change_reason AS '变动原因'
FROM md_material m
LEFT JOIN pur_supplier_material sm1 ON m.id = sm1.material_id AND sm1.is_preferred = 1 AND sm1.deleted = 0
LEFT JOIN pur_supplier s1 ON sm1.supplier_id = s1.id
LEFT JOIN pur_supplier_material sm2 ON m.id = sm2.material_id AND sm2.deleted = 0 AND sm2.supplier_id != COALESCE(sm1.supplier_id, 0)
LEFT JOIN pur_supplier s2 ON sm2.supplier_id = s2.id
LEFT JOIN pur_supplier_material sm3 ON m.id = sm3.material_id AND sm3.deleted = 0 AND sm3.supplier_id != COALESCE(sm1.supplier_id, 0) AND sm3.supplier_id != COALESCE(sm2.supplier_id, 0)
LEFT JOIN pur_supplier s3 ON sm3.supplier_id = s3.id
LEFT JOIN pur_price_agreement_item pai ON m.id = pai.material_id AND pai.deleted = 0
LEFT JOIN pur_price_agreement pa ON pai.agreement_id = pa.id AND pa.status = 1 AND pa.deleted = 0
LEFT JOIN pur_price_history ph ON m.id = ph.material_id AND ph.deleted = 0
WHERE m.deleted = 0
ORDER BY m.material_code
```

#### 6.3.4 应付账款汇总报表

```sql
SELECT
    po.supplier_code AS '供应商编码',
    po.supplier_name AS '供应商名称',
    po.order_code AS '订单编号',
    po.order_date AS '订单日期',
    po.total_amount AS '订单金额（元）',
    COALESCE(pay_sum.paid_amount, 0) AS '已付金额（元）',
    po.total_amount - COALESCE(pay_sum.paid_amount, 0) AS '应付余额（元）',
    inv_sum.invoice_amount AS '发票金额（元）',
    inv_sum.tax_amount AS '税额（元）',
    CASE po.order_status
        WHEN 0 THEN '草稿'
        WHEN 1 THEN '已确认'
        WHEN 2 THEN '部分到货'
        WHEN 3 THEN '已到货'
        WHEN 4 THEN '已关闭'
    END AS '订单状态'
FROM pur_order po
LEFT JOIN (
    SELECT order_id, SUM(amount) AS paid_amount
    FROM pur_payment
    WHERE deleted = 0 AND payment_status IN (1, 2)
    GROUP BY order_id
) pay_sum ON po.id = pay_sum.order_id
LEFT JOIN (
    SELECT order_id, SUM(invoice_amount) AS invoice_amount, SUM(tax_amount) AS tax_amount
    FROM pur_invoice
    WHERE deleted = 0 AND invoice_status IN (1, 2, 3)
    GROUP BY order_id
) inv_sum ON po.id = inv_sum.order_id
WHERE po.deleted = 0
ORDER BY po.supplier_name, po.order_date
```

#### 6.3.5 供应商评估趋势报表

```sql
SELECT
    s.supplier_code AS '供应商编码',
    s.supplier_name AS '供应商名称',
    se.eval_date AS '评估日期',
    CASE se.eval_type WHEN 1 THEN '定期' WHEN 2 THEN '临时' END AS '评估类型',
    se.quality_score AS '质量评分',
    se.delivery_score AS '交货评分',
    se.price_score AS '价格评分',
    se.service_score AS '服务评分',
    se.total_score AS '综合评分',
    se.eval_remark AS '评估备注'
FROM pur_supplier s
JOIN pur_supplier_eval se ON s.id = se.supplier_id AND se.deleted = 0
WHERE s.deleted = 0
ORDER BY s.supplier_name, se.eval_date DESC
```

### 6.4 模块目录结构

```
ims-module-purchase/                        # 采购服务（新建）
├── ims-module-purchase-api/                # API接口层（DTO、VO、常量、Feign接口）
│   └── src/main/java/.../purchase/api/
│       ├── dto/
│       │   ├── SupplierRespDTO.java            # 供应商响应DTO
│       │   ├── PreferredSupplierRespDTO.java    # 首选供应商响应DTO
│       │   ├── MaterialCurrentPriceRespDTO.java # 原料当前价格响应DTO
│       │   ├── PurchaseOrderStatusRespDTO.java  # 订单状态响应DTO
│       │   └── SupplierPaymentSummaryRespDTO.java # 应付汇总响应DTO
│       ├── enums/
│       │   ├── SupplierTypeEnum.java           # 供应商类型枚举
│       │   ├── ReqStatusEnum.java              # 采购申请状态枚举
│       │   ├── OrderStatusEnum.java            # 采购订单状态枚举
│       │   ├── ReceiveStatusEnum.java          # 到货状态枚举
│       │   ├── ReturnStatusEnum.java           # 退货状态枚举
│       │   ├── AgreementTypeEnum.java          # 价格协议类型枚举
│       │   ├── AgreementStatusEnum.java        # 价格协议状态枚举
│       │   ├── PaymentTypeEnum.java            # 付款类型枚举
│       │   ├── PaymentStatusEnum.java          # 付款状态枚举
│       │   ├── InvoiceTypeEnum.java            # 发票类型枚举
│       │   └── InvoiceStatusEnum.java          # 发票状态枚举
│       └── SupplierApi.java                    # 供应商Feign接口
│       └── PurchasePriceApi.java               # 价格Feign接口
│       └── PurchaseOrderApi.java               # 订单Feign接口
│
└── ims-module-purchase-biz/                # 业务实现层
    └── src/main/java/.../purchase/
        ├── controller/
        │   └── admin/
        │       ├── SupplierController.java          # 供应商管理
        │       ├── RequisitionController.java        # 采购申请
        │       ├── OrderController.java              # 采购订单
        │       ├── ReceiveController.java            # 到货管理
        │       ├── ReturnController.java             # 退货管理
        │       ├── PriceAgreementController.java     # 价格协议
        │       ├── PaymentController.java            # 付款管理
        │       └── InvoiceController.java            # 发票管理
        ├── service/
        │   ├── supplier/
        │   │   ├── SupplierService.java
        │   │   └── SupplierServiceImpl.java
        │   ├── requisition/
        │   │   ├── RequisitionService.java
        │   │   └── RequisitionServiceImpl.java
        │   ├── order/
        │   │   ├── OrderService.java
        │   │   └── OrderServiceImpl.java
        │   ├── receive/
        │   │   ├── ReceiveService.java
        │   │   └── ReceiveServiceImpl.java
        │   ├── return/
        │   │   ├── ReturnService.java
        │   │   └── ReturnServiceImpl.java
        │   ├── price/
        │   │   ├── PriceAgreementService.java
        │   │   └── PriceAgreementServiceImpl.java
        │   ├── payment/
        │   │   ├── PaymentService.java
        │   │   └── PaymentServiceImpl.java
        │   └── invoice/
        │       ├── InvoiceService.java
        │       └── InvoiceServiceImpl.java
        ├── dal/
        │   ├── dataobject/
        │   │   ├── SupplierDO.java
        │   │   ├── SupplierMaterialDO.java
        │   │   ├── SupplierContactDO.java
        │   │   ├── SupplierEvalDO.java
        │   │   ├── RequisitionDO.java
        │   │   ├── RequisitionItemDO.java
        │   │   ├── OrderDO.java
        │   │   ├── OrderItemDO.java
        │   │   ├── ReceiveDO.java
        │   │   ├── ReceiveItemDO.java
        │   │   ├── ReturnDO.java
        │   │   ├── ReturnItemDO.java
        │   │   ├── PriceAgreementDO.java
        │   │   ├── PriceAgreementItemDO.java
        │   │   ├── PriceHistoryDO.java
        │   │   ├── PaymentDO.java
        │   │   └── InvoiceDO.java
        │   └── mysql/
        │       ├── SupplierMapper.java
        │       ├── SupplierMaterialMapper.java
        │       ├── SupplierContactMapper.java
        │       ├── SupplierEvalMapper.java
        │       ├── RequisitionMapper.java
        │       ├── RequisitionItemMapper.java
        │       ├── OrderMapper.java
        │       ├── OrderItemMapper.java
        │       ├── ReceiveMapper.java
        │       ├── ReceiveItemMapper.java
        │       ├── ReturnMapper.java
        │       ├── ReturnItemMapper.java
        │       ├── PriceAgreementMapper.java
        │       ├── PriceAgreementItemMapper.java
        │       ├── PriceHistoryMapper.java
        │       ├── PaymentMapper.java
        │       └── InvoiceMapper.java
        └── convert/
            ├── SupplierConvert.java
            ├── RequisitionConvert.java
            ├── OrderConvert.java
            ├── ReceiveConvert.java
            ├── ReturnConvert.java
            ├── PriceAgreementConvert.java
            ├── PaymentConvert.java
            └── InvoiceConvert.java
```

### 6.5 关键配置项

| 配置项 | 说明 | 示例值 |
|--------|------|--------|
| `yudao.info.base-package` | 扫描包路径 | `cn.lucky.ims.module.purchase` |
| `yudao.swagger.title` | API文档标题 | `采购管理服务` |
| `yudao.swagger.description` | API文档描述 | `供应商、采购订单、价格管理、应付账款接口` |

### 6.6 代码生成配置

使用 yudao-cloud 的代码生成功能，配置如下：

| 配置项 | 值 |
|--------|-----|
| 生成模板 | 单表（standard）/ 主子表（master-detail） |
| 前端类型 | Vue3 + Element Plus |
| 上级菜单 | 采购管理 |
| 前端路径 | `purchase/xxx` |
| 后端包路径 | `cn.lucky.ims.module.purchase` |

**主子表代码生成说明：**

以下表组合应使用**主子表模板**：

| 主表 | 子表 | 关联字段 | 说明 |
|------|------|---------|------|
| `pur_supplier` | `pur_supplier_contact` | `supplier_id` | 供应商+联系人 |
| `pur_supplier` | `pur_supplier_material` | `supplier_id` | 供应商+报价 |
| `pur_requisition` | `pur_requisition_item` | `req_id` | 采购申请+明细 |
| `pur_order` | `pur_order_item` | `order_id` | 采购订单+明细 |
| `pur_receive` | `pur_receive_item` | `receive_id` | 到货单+明细 |
| `pur_return` | `pur_return_item` | `return_id` | 退货单+明细 |
| `pur_price_agreement` | `pur_price_agreement_item` | `agreement_id` | 价格协议+明细 |

### 6.7 开发规范

#### 6.7.1 命名规范

| 类型 | 规范 |
|------|------|
| 表名 | `pur_` 前缀 |
| DO类 | `XxxDO` 后缀 |
| VO类 | `XxxVO` 后缀 |
| DTO类 | `XxxDTO` 后缀 |
| Service | `XxxService` |
| Controller | `XxxController` |
| Mapper | `XxxMapper` |

#### 6.7.2 注解使用

```java
// DO类注解
@TableName("pur_supplier")
@KeySequence("pur_supplier_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SupplierDO extends BaseDO {
    // ...
}

// Controller注解
@RestController
@RequestMapping("/admin-api/purchase/supplier")
@Tag(name = "管理后台 - 供应商管理")
public class SupplierController {
    // ...
}

// Service注解
@Service
@Validated
public class SupplierServiceImpl implements SupplierService {
    // ...
}
```

#### 6.7.3 权限配置

```java
// 供应商管理权限
@PostMapping("/create")
@Operation(summary = "创建供应商")
@PreAuthorize("@ss.hasPermission('purchase:supplier:create')")
public CommonResult<Long> createSupplier(@Valid @RequestBody SupplierSaveReqVO createReqVO) {
    return success(supplierService.createSupplier(createReqVO));
}

// 采购申请权限
@PostMapping("/submit")
@Operation(summary = "提交审批")
@PreAuthorize("@ss.hasPermission('purchase:requisition:submit')")
public CommonResult<Boolean> submitRequisition(@RequestParam("id") Long id) {
    requisitionService.submitRequisition(id);
    return success(true);
}

// 采购订单确认权限
@PostMapping("/confirm")
@Operation(summary = "确认订单")
@PreAuthorize("@ss.hasPermission('purchase:order:confirm')")
public CommonResult<Boolean> confirmOrder(@RequestParam("id") Long id) {
    orderService.confirmOrder(id);
    return success(true);
}
```

### 6.8 测试要点

| 测试项 | 测试内容 | 预期结果 |
|--------|---------|---------|
| 基础CRUD | 供应商/订单/报价等创建/更新/删除/查询 | 操作成功，数据正确 |
| 多租户隔离 | 不同租户的供应商和订单数据隔离 | 租户A看不到租户B的数据 |
| 权限控制 | 无权限用户访问 | 返回403错误 |
| 采购申请流程 | 创建→提交→审批→下达 | 状态流转正确 |
| 采购订单流程 | 创建→确认→到货→关闭 | 状态流转正确 |
| 到货数量更新 | 到货确认后更新订单已收货数量 | received_qty正确累加 |
| 退货扣减 | 退货确认后扣减订单已收货数量 | received_qty正确扣减 |
| 价格变更通知 | 价格协议生效时通知配方模块 | Feign调用成功，配方cost_stale=1 |
| 供应商评估 | 创建评估后自动更新供应商评级 | rating根据综合评分正确映射 |
| 首选供应商推荐 | 采购申请自动填充首选供应商 | suggested_supplier_id和suggested_price正确 |
| 外键关联校验 | 删除有关联订单的供应商 | 提示存在关联，禁止删除 |
| Feign调用 | 跨模块调用masterdata/recipe | 调用成功，数据一致 |
| 精度校验 | 金额/数量字段DECIMAL(14,6)精度 | 数据库存储和API传输精度一致 |
| 信用额度校验 | 订单金额超过供应商信用额度 | 提示信用额度不足 |

### 6.9 后续迭代计划

| 阶段 | 内容 | 涉及模块 | 预计工期 |
|------|------|---------|---------|
| **第一阶段** | 供应商管理：供应商主数据、报价、联系人、评估 | ims-module-purchase | 2周 |
| **第二阶段** | 采购订单流程：采购申请、采购订单、到货、退货 | ims-module-purchase | 3周 |
| **第三阶段** | 价格管理：价格协议、价格历史、价格变更通知 | ims-module-purchase | 1周 |
| **第四阶段** | 应付账款：付款记录、发票管理 | ims-module-purchase | 1周 |
| **第五阶段** | 跨模块集成：Feign调用、价格变更通知配方模块 | purchase + recipe + masterdata | 1周 |
| **第六阶段** | 报表配置：积木报表SQL数据集 | ims-module-report | 1周 |
| **后续扩展** | MRP自动采购需求计算（对接配方BOM和库存数据） | purchase + recipe + wms | 待定 |
| **后续扩展** | CRM合同关联（对接ims-module-crm） | purchase + crm | 待定 |
| **后续扩展** | 订单服务关联（对接ims-module-order） | purchase + order | 待定 |

---

> **附录：参考资源**
>
> - yudao-cloud 官方文档：https://doc.iocoder.cn/
> - yudao-cloud GitHub：https://github.com/YunaiV/ruoyi-vue-pro
