17 KiB
17 KiB
模具制造进销存核心模块分析报告(代码基线:geMoldInsight)
文档定位:业务分析/审计型报告文档。 本文保留一次特定分析基线下的观察结论与流程梳理,不作为当前项目状态或当前架构的权威说明。当前状态见 ../STATUS.md,当前架构见 ../ARCHITECTURE.md,演进路线见 ../ROADMAP.md。
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 端到端流程图(客户下单→交付)
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。
客户订单(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)
- PK:
- 表:
sales_order_items- PK:
id - FK:
order_id(→sales_orders.id)、product_id(→products.id) - 关键字段:
quantity、delivered_quantity、unit_price、amount
- PK:
物料清单(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)
- PK:
采购订单(PurchaseOrder)
- 表:
purchase_orders- PK:
id - 关键字段:
order_no(unique,index)、supplier_id(FK,index)、status、total_amount、paid_amount、expected_date、operator_id(FK)
- PK:
- 表:
purchase_order_items- PK:
id - FK:
order_id(→purchase_orders.id)、product_id(→products.id) - 关键字段:
quantity、received_quantity、unit_price、amount
- PK:
供应商/客户主数据
- 表:
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)
- DB 默认
sales_orders.production_status:- DB 默认
not_started - 业务中出现
bom_missing/material_issued/completed,但缺少统一枚举约束
- DB 默认
- 数据修复:
pending→manufacturing示例脚本见scripts/db/003_data_fixups.sql。
C. 金额精度风险(高优先)
- 金额/单价字段大量使用
Float(IEEE 754),在财务口径下会出现精度丢失与累计误差。 - 建议将以下字段迁移为
NUMERIC(18,4)或NUMERIC(18,2):products.cost_price/sale_pricepurchase_orders.total_amount/paid_amountpurchase_order_items.unit_price/amountsales_orders.planned_material_cost/actual_material_cost/total_amount/received_amountsales_order_items.unit_price/amountstock_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"的拒绝。 - 数据库层(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。 - 也支持按单领料接口(若未自动扣料)。
- 创建模具订单时,若 BOM 存在且库存充足,会实时扣减库存并记
- 结论:系统是“实时更新库存现存量”,不是“到货时更新快照”。
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.pytests/test_api_inventory_orders.py(参数化用例,覆盖正常/异常/边界/幂等与约束)
- 建议在 CI 中运行:
pytest -q- 将
scripts/db/*.sql纳入数据库初始化后的约束/索引校验步骤
9. 交付物结构建议
- 报告(建议以 Markdown→Word/PDF 转换)
docs/MOLD_ERP_ANALYSIS_REPORT.mddocs/UAT_CHECKLIST.mddocs/INTERFACE_INTEGRATION_CATALOG_TEMPLATE.mddocs/CONFLUENCE_ARCHIVE_STRUCTURE.md
- 可执行脚本
scripts/db/001_freeze_delivered_sales_orders.sqlscripts/db/002_indexes_and_constraints.sqlscripts/db/003_data_fixups.sqlscripts/db/010_partitioning_template.sqlscripts/db/011_rls_template.sqlscripts/db/020_audit_trail.sql
如需输出 Word+PDF,可使用 pandoc 将本报告导出为 .docx 与 .pdf,并将生成物纳入 CI 产物归档。