Files
geMoldInsight/docs/MOLD_ERP_ANALYSIS_REPORT.md
T
2026-03-25 23:59:29 +08:00

296 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模具制造进销存核心模块分析报告(代码基线:geMoldInsight)
## 0. 范围与术语映射
- 客户订单(Customer Order):本仓库实现为 SalesOrder(销售订单),其业务语义更贴近“模具订单/按单生产订单”。对应表:`sales_orders`、`sales_order_items`。
- 物料采购订单(Purchase Order):本仓库实现为 PurchaseOrder。对应表:`purchase_orders`、`purchase_order_items`。
- 物料清单(BOM):本仓库实现为 `product_materials`(ProductMaterial),以“成品(模具)→ 物料”的多行明细表达。
- 成品库存:系统数据模型存在 `products.item_type="finished"`,但库存管理接口仅面向 `item_type="material"`,且“交付”不产生任何成品入库/出库流水;因此可视为“零成品库存”设计。
## 1. 业务流程梳理
### 1.1 端到端流程图(客户下单→交付)
```mermaid
flowchart LR
A[客户下单\nSalesOrder: POST /api/sales-orders] --> B{BOM 是否存在?}
B -- 否 --> B1[订单创建成功\nproduction_status=bom_missing\n不扣料]
B -- 是 --> C[计算物料需求\n按 BOM×订单数量×(1+loss_rate)]
C --> D{默认仓库库存是否足够?}
D -- 否 --> D1[创建失败/回滚\n提示缺料明细]
D -- 是 --> E[创建时自动扣减物料\nInventory.quantity-=需求\nStockMovement=issue_to_production]
B1 --> F[物料需求计划\nGET /api/sales-orders/{id}/production-plan]
E --> F
F --> G[采购下单(手工)\nPurchaseOrder: POST /api/purchase-orders]
G --> H[到货入库\nPOST /api/purchase-orders/{id}/receive\nStockMovement=purchase_in]
H --> I[生产领料(可选)\nPOST /api/sales-orders/{id}/issue-materials]
I --> J[交付客户\nPATCH /api/sales-orders/{id}/status=delivered]
```
### 1.2 节点级状态、角色、输入输出、异常与回退
| 节点 | 负责角色 | 单据/状态 | 输入 | 输出 | 异常分支 | 回退机制 |
|---|---|---|---|---|---|---|
| 客户下单 | 销售/项目 | SalesOrder.status=manufacturing(创建时强制) | customer_id、成品(模具)明细、交期 | sales_orders + sales_order_items | 默认仓库未配置;BOM 存在但库存不足(创建失败) | 事务回滚;不会产生库存/流水 |
| 物料需求计算 | 计划/工艺 | production-plan | 订单明细 + BOM | 需求汇总(含 loss_rate)+ 缺口 | BOM 缺失(返回空计划) | 无(纯查询) |
| 采购申请 | 采购 | 未实现专用单据 | 需求缺口 | 采购单(手工创建) | 无 | 无 |
| 供应商采购 | 采购 | PurchaseOrder.status=draft | supplier_id + 物料行 | purchase_orders + items | 物料不是 material;cost_price<=0 | 事务回滚 |
| 到货质检 | 质检 | 未实现专用质检单 | 到货信息 | 无 | 无 | 无 |
| 到货入库 | 仓库 | PurchaseOrder.status=partial_received/received | 入库仓库、入库数量 | Inventory.quantity 增加;StockMovement=purchase_in;received_quantity 累加 | 入库超收;仓库不存在/停用 | 事务回滚 |
| 模具生产/领料 | 生产/仓库 | production_status=material_issued | 发料仓库 | Inventory.quantity 扣减;StockMovement=issue_to_production | BOM 缺失;库存不足;已自动扣料禁止重复领料 | 若后续“修改/删除订单”,会回补已扣料(return_from_production) |
| 交付客户 | 销售/项目 | status=delivered | order_id | status 更新 | 状态值非法 | 无 |
关键落地差异(与“采购申请/到货质检/生产工序/交付签收”相比):
- 采购申请、到货质检、生产工序、交付签收均未建模为独立单据;当前系统以“库存流水 + 订单状态”覆盖部分过程。
## 2. 数据模型与字段审计
### 2.1 核心表字段、主外键关系、索引概览(以 ORM 为准)
以下字段来源于 [database.py](file:///d:/project/geMoldInsight/src/models/database.py)。
#### 客户订单(SalesOrder / 模具订单)
- 表:`sales_orders`
- PK:`id`
- 关键字段:`order_no`(unique,index)、`customer_id`(FK,index)、`status`、`production_status`(index)、`production_no`(index)、`planned_material_cost`、`actual_material_cost`、`total_amount`、`received_amount`、`delivery_date`、`operator_id`(FK)
- 表:`sales_order_items`
- PK:`id`
- FK:`order_id`(→sales_orders.id)、`product_id`(→products.id)
- 关键字段:`quantity`、`delivered_quantity`、`unit_price`、`amount`
#### 物料清单(BOM)
- 表:`product_materials`
- PK:`id`
- FK:`finished_product_id`(→products.id,index)、`material_product_id`(→products.id,index)
- 关键字段:`quantity`、`loss_rate`
- 约束:唯一约束 `uq_product_material_unique(finished_product_id, material_product_id)`
#### 采购订单(PurchaseOrder)
- 表:`purchase_orders`
- PK:`id`
- 关键字段:`order_no`(unique,index)、`supplier_id`(FK,index)、`status`、`total_amount`、`paid_amount`、`expected_date`、`operator_id`(FK)
- 表:`purchase_order_items`
- PK:`id`
- FK:`order_id`(→purchase_orders.id)、`product_id`(→products.id)
- 关键字段:`quantity`、`received_quantity`、`unit_price`、`amount`
#### 供应商/客户主数据
- 表:`suppliers`:`code`(unique,index)、`name`、`is_active`
- 表:`customers`:`code`(unique,index)、`name`、`credit_limit`
#### 到货明细表(现状映射)
系统未实现独立“到货单/质检单”表:
- 到货数量沉淀在 `purchase_order_items.received_quantity`
- 到货批次/明细流水沉淀在 `stock_movements`(其中 `movement_type="purchase_in"`、`reference_type="purchase_order"`)
### 2.2 问题验证与整改清单(以代码检出为依据)
#### A. 缺失约束/索引(高优先)
- `inventory` 缺少数据库层唯一约束 `(product_id, warehouse_id)`:接口层有重复校验,但并发下可能插入重复行。
- `purchase_order_items.order_id / product_id`、`sales_order_items.order_id / product_id` 缺少索引:高数据量下会拖慢订单详情/联动查询。
- `stock_movements.warehouse_id`、`stock_movements(reference_type, reference_id)` 缺少索引:对“按单追溯”与仓库流水分页不友好。
- 修复脚本:见 `scripts/db/002_indexes_and_constraints.sql`。
#### B. 枚举值不一致(中高优先)
- `sales_orders.status`:
- DB 默认 `draft`
- 创建/更新强制 `manufacturing`
- 发料接口可能写入 `pending`(但状态更新接口不允许 `pending`)
- `sales_orders.production_status`:
- DB 默认 `not_started`
- 业务中出现 `bom_missing/material_issued/completed`,但缺少统一枚举约束
- 数据修复:`pending→manufacturing` 示例脚本见 `scripts/db/003_data_fixups.sql`。
#### C. 金额精度风险(高优先)
- 金额/单价字段大量使用 `Float`(IEEE 754),在财务口径下会出现精度丢失与累计误差。
- 建议将以下字段迁移为 `NUMERIC(18,4)` 或 `NUMERIC(18,2)`:
- `products.cost_price/sale_price`
- `purchase_orders.total_amount/paid_amount`
- `purchase_order_items.unit_price/amount`
- `sales_orders.planned_material_cost/actual_material_cost/total_amount/received_amount`
- `sales_order_items.unit_price/amount`
- `stock_movements.unit_price/total_amount`
#### D. 冗余字段(中优先)
- `sales_orders.received_amount` 与 `purchase_orders.paid_amount` 可能与财务分摊表(`finance_allocations`)重复表达;若以财务分摊为准,应明确哪一方为“源数据”。
## 3. 业务规则验证(以当前实现为准)
### 3.1 新增模具订单是否自动触发物料需求计算
- 结论:会触发“物料需求计算”,并在满足条件时会进一步触发“自动扣料”。
- 证据:
- 创建销售单后调用 `_issue_materials_for_order_creation()`,内部调用 `_build_material_plan()` 进行需求汇总,并按默认仓库扣减库存(`issue_to_production`)。
- 需求计算考虑 `loss_rate`;不考虑安全库存、在途量。
覆盖情况:
- 损耗率:已支持(`qty * (1 + loss_rate)`)。
- 安全库存:未支持(`products.min_stock` 仅用于低库存查询,不参与需求计算)。
- 在途量:未支持(系统无“采购在途数量”的统一口径,需通过采购单未收数量推导,但当前逻辑未做)。
### 3.2 修改模具订单对已生成采购订单的联动策略
- 结论:当前系统不存在“客户订单↔采购订单”的显式关联,因此无法实现联动策略与版本控制。
- 现状:
- 修改销售单会回补该销售单曾扣减的物料(按 `stock_movements` 追溯),然后删除旧明细、重建明细、重新计算并扣料。
- 采购单创建完全独立,未记录来源销售单/需求行。
- 建议(整改方向):
- 增加 `sales_order_material_demands`(需求行)并记录来源、版本号;采购单行引用需求行;修改订单时按版本触发“采购重确认”。
- 为 `sales_orders` 增加 `row_version`(int/bigint)乐观锁;更新时带版本号校验。
### 3.3 禁止对已交付模具订单进行任何字段修改(双重约束)
- 应用层:已实现(PUT/DELETE/PATCH status/issue-materials)对 `status="delivered"` 的拒绝。
- 代码位置:[sales_order_routes.py](file:///d:/project/geMoldInsight/src/api/inventory/sales_order_routes.py)
- 数据库层(PostgreSQL):提供触发器脚本,禁止更新/删除订单头与增删改订单明细。
- 脚本:`scripts/db/001_freeze_delivered_sales_orders.sql`
## 4. 库存与成本逻辑审查
### 4.1 库存扣减策略
- 到货入库:`purchase-orders/{id}/receive` 会实时写入 `Inventory.quantity += receive_quantity`,并记 `StockMovement=purchase_in`。
- 生产领料:
- 创建模具订单时,若 BOM 存在且库存充足,会实时扣减库存并记 `StockMovement=issue_to_production`。
- 也支持按单领料接口(若未自动扣料)。
- 结论:系统是“实时更新库存现存量”,不是“到货时更新快照”。
### 4.2 成本核算方式与附加成本
- 现状:无移动加权/FIFO/标准成本核算引擎。
- 采购单明细单价强制取 `products.cost_price`;入库流水记录 `unit_price`,但不会更新 `products.cost_price` 的移动平均。
- 发料成本按 `products.cost_price` 估值,不含运费、关税、质检费等分摊。
- 建议:
- 增加“入库批次成本层”或“库存成本层”表以支持 FIFO/移动加权;
- 增加 `purchase_landed_costs`(关税/运费/质检费)并按行/金额比例分摊到入库批次。
### 4.3 “零成品库存”证明与对月结影响
证明要点(以代码行为为准):
- 库存查询接口仅返回物料(`item_type=="material"`),成品库存不在日常库存口径。
- 交付(`status=delivered`)仅更新订单状态,不产生成品入库/出库、也没有销售出库流水。
- 系统未实现销售退货/成品出库/成品调拨等模块。
对财务月结影响(需补齐口径):
- 目前可获得的成本数据是“发料成本(按物料成本价)”,更接近制造费用的材料消耗口径;
- 若要形成完整月结(在制/完工/成本结转),需补齐工序报工、完工入库(或完工确认)、制造费用分摊与结转规则。
## 5. 接口与集成盘点
- 对外系统集成(ERP/财务/供应商/客户门户):当前代码未发现任何外部同步实现(无外部 HTTP client、无 webhook、无 MQ、无定时同步、无文件交换)。
- 系统对外提供接口:FastAPI REST(HTTP/JSON),前缀 `/api`。
建议交付模板(用于后续对接):
- 《接口清单》:接口名、协议、调用方、频率、数据量、重试/幂等键(例如 `order_no`)。
- 《字段映射表》:源字段、目标字段、单位/币种换算、枚举映射、空值策略。
## 6. 权限与审计
### 6.1 RBAC 现状与推荐矩阵
现状:
- 仅实现“管理员 is_superuser”级别拦截;大部分写接口只要求登录。
- 数据模型已存在 Role/Permission 以及 `has_permission()`,但未在业务路由中落地。
推荐矩阵(目标态,需用 permission_code 落地):
| 角色 | 客户订单 | 采购订单 | 库存/流水 | 主数据(产品/客户/供应商) | 财务 | 用户/角色 |
|---|---|---|---|---|---|---|
| 销售 | C/R/U | R | R | R | R | - |
| 采购 | R | C/R/U | R | R | R | - |
| 仓库 | R | R | C/R/U | R | - | - |
| 财务 | R | R | R | R | C/R/U | - |
| 管理员 | C/R/U/D | C/R/U/D | C/R/U/D | C/R/U/D | C/R/U/D | C/R/U/D |
按“表级 CRUD”口径(用于落地 RBAC 与审计范围界定):
| 表 | 销售 | 采购 | 仓库 | 财务 | 管理员 |
|---|---|---|---|---|---|
| customers | R | R | R | R | C/R/U/D |
| suppliers | R | R | R | R | C/R/U/D |
| products | R | R | R | R | C/R/U/D |
| product_materials | R | R | R | R | C/R/U/D |
| sales_orders / sales_order_items | C/R/U | R | R | R | C/R/U/D |
| purchase_orders / purchase_order_items | R | C/R/U | R | R | C/R/U/D |
| inventory | R | R | C/R/U | R | C/R/U/D |
| stock_movements | R | R | C/R | R | C/R/U/D |
### 6.2 RLS(行级安全)检查与建议
- 现状:订单/进销存未体现“事业部/组织”字段,数据库未启用 RLS;部分接口甚至未鉴权(与业务无关的历史记录接口)。
- 建议:
- 增加 `org_id` 字段至订单、主数据、库存等关键表;
- Postgres 层启用 RLS,并通过应用设置 `SET app.current_org_id = ...` 绑定会话变量实现隔离。
- 参考脚本:`scripts/db/011_rls_template.sql`
### 6.3 操作日志 180 天可追溯
- 现状:存在 `user_activities` 与 `system_logs` 表,但缺少统一落库策略与留存/归档脚本。
- 建议:
- 关键操作(登录、下单、收货、领料、交付、权限变更)落库记录 old/new、操作人、时间、IP、UA;
- 增加分区表或归档表,并提供 180 天归档/清理策略。
- 参考脚本:`scripts/db/020_audit_trail.sql`(依赖应用层设置会话变量 current_user_id/ip/ua)
## 7. 性能与扩展性评估(方向性建议)
### 7.1 千万级订单量分库分表(建议)
- 优先建议:先做 PostgreSQL 原生分区(比跨库路由成本低)
- 拆分键:`sales_orders.order_date` / `purchase_orders.order_date` 按月 Range 分区
- 历史迁移:`INSERT INTO ... SELECT ... WHERE order_date < ...`,并校验行数/校验和
- 灰度切换:读写双写→读切换→写切换→下线旧表
- 若必须分库:按 `org_id + 时间` 组合路由(hash(org_id)→库,时间→表)
- 分区模板脚本:`scripts/db/010_partitioning_template.sql`
### 7.2 慢 SQL 风险点与优化脚本
- 高风险点:
- 订单详情按 order_id 读取明细、追溯库存流水、汇总库存,若缺少索引会退化为全表扫。
- BOM 需求计算需要避免 N+1(已在 `_build_material_plan` 做批量化)。
- 索引脚本:见 `scripts/db/002_indexes_and_constraints.sql`。
### 7.3 并发下新增/修改模具订单锁冲突
风险源:
- 并发创建订单并同时扣减同一物料库存,会竞争同一 `inventory` 行。
- 并发修改订单会先回补再扣减,冲突窗口更大。
建议:
- 在扣减库存时采用“原子更新”语句:`UPDATE inventory SET quantity = quantity - :qty WHERE id=:id AND quantity >= :qty`,并检查 affected_rows,实现乐观并发控制;
- 或引入 `row_version`(乐观锁)到 inventory/sales_orders。
## 8. 测试与验收标准(仓库内已落地的最小集合)
- 已新增接口自动化用例(示例,覆盖“交付后冻结”核心规则):
- `tests/test_sales_order_delivered_freeze.py`
- `tests/test_api_inventory_orders.py`(参数化用例,覆盖正常/异常/边界/幂等与约束)
- 建议在 CI 中运行:
- `pytest -q`
- 将 `scripts/db/*.sql` 纳入数据库初始化后的约束/索引校验步骤
## 9. 交付物结构建议
- 报告(建议以 Markdown→Word/PDF 转换)
- `docs/MOLD_ERP_ANALYSIS_REPORT.md`
- `docs/UAT_CHECKLIST.md`
- `docs/INTERFACE_INTEGRATION_CATALOG_TEMPLATE.md`
- `docs/CONFLUENCE_ARCHIVE_STRUCTURE.md`
- 可执行脚本
- `scripts/db/001_freeze_delivered_sales_orders.sql`
- `scripts/db/002_indexes_and_constraints.sql`
- `scripts/db/003_data_fixups.sql`
- `scripts/db/010_partitioning_template.sql`
- `scripts/db/011_rls_template.sql`
- `scripts/db/020_audit_trail.sql`
如需输出 Word+PDF,可使用 pandoc 将本报告导出为 `.docx` 与 `.pdf`,并将生成物纳入 CI 产物归档。