296 lines
16 KiB
Markdown
296 lines
16 KiB
Markdown
|
|
# 模具制造进销存核心模块分析报告(代码基线: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 产物归档。
|