外观
VOL Pro 开发 ERP:成本重估、开发流程清单、优化建议
第二版。相比第一版(
VOL_Pro_ERP_能力评估.md,那份是"框架能力是否具备"的证据清单),本文回答三个问题:要花多少、按什么顺序做、怎么做才不返工。 估算前提:1 名熟悉 Vue3 + .NET 的中高级全栈;1 人日 = 8 有效工时;已掌握框架基本用法(上手期 1–2 周另计)。
一、成本重估
1.1 一句话结论
框架能替你干掉约 60% 的工作量,但被干掉的 60% 恰恰是"不值钱"的那部分 CRUD。ERP 真正的成本——库存事务、成本核算、单据流转、往来核销——框架帮不上忙,而且这部分的比重比大多数人预估的高。
1.2 三种场景的估算
| 场景 | 单据数 | 工作量 | 折合 | 说明 |
|---|---|---|---|---|
| A. 进销存 + 往来(采购/销售/库存/应收应付,无生产、无总账) | 12–15 | 70–100 人日 | 1 人 × 4–5 月 | 最现实的起步范围,建议先做这个 |
| B. 完整 ERP(A + 生产制造 + 总账凭证 + 成本核算) | 25–30 | 150–220 人日 | 1 人 × 8–10 月,或 2 人 × 4–5 月 | 生产与成本部分占增量的一半 |
| C. 行业定制版(B + 行业特性,如批次效期、序列号、委外、多单位换算) | 35+ | 250–350 人日 | 3 人 × 4–5 月 | 不建议一开始就做 |
对比:纯自研(自建框架 + 业务)约 300–450 人日,即 VOL Pro 节省 55%–65%。
1.3 分模块人日明细(场景 A,共约 92–96 人日;2026-10-31 修订:补交付与运维基设)
| 模块 | 人日 | 框架省了多少 | 主要成本在哪 |
|---|---|---|---|
| 环境/规范/脚手架 | 3 | — | 命名规范、Git 策略、字段标准 |
| 基础资料(物料/分类/仓库/库位/供应商/客户/单位/税率) | 8 | 省 70% | 几乎是纯 CRUD,生成器出完微调即可 |
| 公共基类抽取(单据基类 + 库存服务 + 单据关系表) | 8 | 省 0% | 必须先做,否则后面全线返工 |
| 采购链路(请购/订单/入库/退货/发票) | 18 | 省 50% | 上下游选单、数量反写、订单关闭判定 |
| 销售链路(报价/订单/出库/退货/发票) | 18 | 省 50% | 同上 + 价格策略、信用额度 |
| 库存(调拨/盘点/报损报溢/批次/预警) | 15 | 省 40% | 库存事务一致性 + 移动加权成本,最硬的一块 |
| 往来(应收应付/收付款/核销/对账) | 12 | 省 40% | 核销规则、账期、账龄 |
| 审批流挂接 | 4 | 省 80% | FormWorkFlow 配流程 + 挂节点 |
| 打印模板 | 6 | 省 50% | 每套单据一个模板,套打调位置很耗时 |
| OSS 文件存储(自建) | 3 | 省 0% | 框架没有,必须写 |
| 消息/邮件/定时任务集成 | 3 | 省 80% | 现成,接一下 |
| 权限/菜单/数据权限 | 3 | 省 70% | 配置为主 |
| 交付(期初/报表/培训) | 5–8 | 省 30% | 期初库存成本初始化、对账单报表、用户手册(阶段 10) |
| 运维基设(备份/环境分离/部署) | 2–3 | 省 0% | 四.P0 新增四项 |
| 测试 + 对账 + 上线 | 10 | — | 库存对账、数据校验、UAT |
1.4 一个必须知道的"成本陷阱"
框架生成的单表页面看起来 30 分钟就能出一个功能,这会严重误导排期。真实耗时结构是:
生成器出骨架 0.5 小时 ← 你看到的"快"
前端页面调整 3-4 小时 ← 列、查询条件、合计、导入导出、校验
后端业务逻辑 8-16 小时 ← 单据编号、状态机、上下游、库存事务、校验规则 ★真正的大头
打印模板 3-4 小时
联调与修 bug 4 小时
────────────────────────────
单个复杂单据 2.5-3.5 人日排期时按"单据的复杂度"而不是"单据的数量"估:简单档案类(物料、仓库)0.5 人日/个;带明细的单据 2.5–3.5 人日/个;带上下游流转的单据 4–5 人日/个。
二、可行性判定(修正第一版)
| 项 | 第一版 | 修正后 | 依据 |
|---|---|---|---|
| 主从表保存 | ✅ | ✅ 无异议 | 官方文档 /docs/cs/service/add:AddOnExecuting(order, list),base.Add 内部事务,返回 Error 即回滚 |
| 合计 | ✅ | ✅ 双通道确认 | 前端 x.summary + 后端 SummaryExpress / GetDetailSummary<Detail> |
| 单元格合并 | ✅ | ✅ | :spanMethod 官方写法明确 |
| 导入导出 | ✅ | ✅ | Export 有 Limit、ExportColumns、ExcelHeaderMap、ExportCustomValue |
| 审核流 | ✅ | ✅ | AuditWorkFlowExecuting/Executed,lastAudit + RewriteFlow 回退 |
| 打印 | 🟡 | 🟡→✅(降级风险) | 官方有 /docs/print/ 4 篇文档,比第一版判断乐观;仍保留"批量/套打需客户端"的提示 |
| 大数据量表格 | 未评 | ✅ | 官方 /docs/web/general/tableV2 专门讲表格卡顿与大批量编辑 |
| OSS | 🔴 | 🔴 维持 | 官方 upload 文档只有 UploadFolder 本地路径,无任何云存储章节,二次确认 |
结论:技术上完全可行,没有阻塞项。 真正的风险不在"能不能做",在下面第四节。
三、开发流程清单
阶段 0:地基(3 人日)— 不要跳过
| # | 动作 | 产出 | 坑 |
|---|---|---|---|
| 0.1 | 定数据库命名规范:表名 Erp_XXX、主表主键统一、明细表外键 {主表}Id、统一 CreateID/Creator/CreateDate/ModifyID/Modifier/ModifyDate | 《命名规范》文档 | 生成器靠规范批量产出,规范乱了后面每个页面都要手改 |
| 0.2 | 定字段标准:金额 decimal(18,4)、数量 decimal(18,6)、单价 decimal(18,6)、日期统一 datetime | 规范文档 | 精度中途改 = 全库返工 |
| 0.3 | 定单据通用字段:BillNo / BillDate / Status / AuditStatus / AuditId / Auditor / AuditDate / Remark / TotalAmount / TaxAmount | 规范文档 | 所有单据统一,公共基类才能抽 |
| 0.4 | 决策:要不要多租户(TenancyId)、要不要数据权限(UserAuth)、要不要乐观锁(DataVersionField) | 决策记录 | 这三个后期加是灾难级返工,必须现在定 |
| 0.5 | Git 策略:生成文件 vs 手写文件分开看 diff;规定只改 extension/**/*.jsx 和 Services/**/Partial/ | CONTRIBUTING | 见 4.5 |
阶段 1:数据建模(5 人日)
- 用
src/views/builder/database/TableEditorWorkspace.vue或直接在数据库建表(推荐后者 +syncTable同步) - 每一张明细表必须有:主键、主表外键、
SortNo(行号)、CreateDate - 建"单据关系表"
Erp_BillRelation(见 4.3),这是全局最重要的一张表 - 建库存表
Erp_Inventory(物料+仓库+批次 唯一键)、库存流水表Erp_InventoryFlow
阶段 2:代码生成(0.5 人日/单)
用 src/views/builder/coderV3.vue:
LoadTable载入表 → 配置主表字段(编辑类型、查询行、必填、字典)- 配置明细表(勾选一对多)
CreateModel生成实体 →CreateServices生成服务层 →CreateVuePage生成 Vue 页面 →CreateMenuVue生成菜单- 前端路由必须手动加到
src/router/viewGird.js(框架不会自动加,菜单是后端下发的)
检查点:生成的页面能跑通增删改查 + 导入导出,再进入下一阶段。这一步没跑通别往下走。
阶段 3:页面配置(每个单据 3–4 小时)
按顺序做,别跳:
columns列配置:宽度、对齐、formatter(/docs/web/table/formatter)、隐藏列- 查询条件:生成器勾"查询行" +
gridRef.queryFields=[...](/docs/web/table/queryFields) - 默认排序(
/docs/web/table/sort) - 合计:
x.summary=true+ 后端SummaryExpress(/docs/web/table/summary) - 明细表:列、合计(
GetDetailSummary)、实时计算(/docs/web/table/detailCalc) - 导出:
ExportColumns+ExcelHeaderMap
阶段 4:业务逻辑(大头,8–16 小时/单)
全部写在 VolPro.ERP/Services/erp/Partial/{表}Service.cs,前端写在 src/extension/erp/erp/{表}.jsx。
每个单据的固定动作:
- 重写
Add/Update,用AddOnExecuting(order, list)做校验 + 默认值 - 单据编号:
mainEntity.CreateCode()(/docs/cs/dev/code有自增流水号) - 状态机:草稿→待提交→审核中→已审核→已关闭/作废(用统一枚举,别各写各的)
- 上下游:选单加载 + 保存时反写上游数量 + 判定上游是否关单
- 库存:调用统一的库存服务(见 4.2),绝不自己写 SQL 改库存
- 前端校验与联动写在
jsx的methods(onInit/onInited/modelOpenAfter)
阶段 5:审批流挂接(0.5 人日/单)
- 在
src/views/sys/formflow/配流程(节点、审批人、条件分支) SaveModel.IsFlow = true- 重写
Audit,用AuditWorkFlowExecuting/Executed;lastAudit=true时才执行"生效"逻辑(如库存过账) - 反审用
base.RewriteFlow(order)(/docs/cs/service/antiAudit)
关键设计决策:库存是在"保存时"扣减,还是"审核通过时"扣减? 必须在阶段 4 之前定死。推荐:草稿不扣,审核通过才扣(否则作废单会污染库存)。
阶段 6:打印(0.5–1 人日/单)
src/views/print/print.vue设计模板(/docs/print/config)printBefore钩子传参(/docs/view-grid/methods/printParams)- 抽象一层
IPrintService,方便后续从 hiprint 换成服务端 PDF
阶段 7:集成(6 人日)
| 项 | 动作 |
|---|---|
| OSS(自建,3d) | 定义 IFileStorage → 本地实现 + OSS/MinIO 实现 → 配置切换 → 前端 VolUpload.vue 的 uploadOSS() 填实(STS 临时凭证直传) |
| 消息推送 | SignalR 已就绪,接 /hub/message,写待办表 |
| 邮件 | MailHelper + MailBackgroundService 队列,配 appsettings.json 的 Mail |
| 定时任务 | Quartz:库存预警、账龄计算、单据超时关闭;多实例部署要改持久化 JobStore |
阶段 8:权限与菜单(3 人日)
菜单 → 角色 → 按钮权限(authBtn)→ 字段权限(saveAuthFields)→ 数据权限(UserAuth)
阶段 9:测试与上线(10 人日)
- 库存对账报表必须做:理论库存 vs 流水累计,每天跑一次,对不上就报警
- 重点回归:并发保存同一单据、审核中途驳回、上游单据被删除、导入脏数据
- 大数据量压测:单表 10 万行、单单据 500 明细行的页面响应
阶段 10:交付与期初(5–8 人日,2026-10-31 补排)
| 项 | 动作 | 量 |
|---|---|---|
| 期初数据 | 期初库存导入(含成本初始化,走 4.11 SqlBulkInsert + InventoryService 期初专用接口,不留后门直接 INSERT)、期初应收应付、客户/物料档案迁移。必须客户盘点配合,预留 2 轮校验时间 | 3–5 人日 |
| 报表平台 | 进销存汇总表、往来对账单是交付必爆需求。用框架静态页面生成器做通用查询报表,导出 Excel 兜底 | 按张估 0.5–1 人日,预留 3–5 |
| 用户手册/培训 | 按角色写操作手册(打印/导入导出/审核流是提问重灾区),至少 1 场现场培训 | 1 人日 |
四、优化建议(按价值排序)
★★★★★ 1. 先抽公共基类,别急着写第一个单据
这是最大的一个坑。 25 个单据都有单号、状态、审核、明细校验、创建人,如果各写各的,后期改一处要动 25 个文件。
建议先花 8 人日建三个公共件:
ErpBillService<TMaster, TDetail>基类:统一单号生成、状态流转、审核前后钩子、明细通用校验、金额合计回写主表InventoryService:库存唯一入口(见第 2 条)BillRelationService:上下游关系(见第 3 条)
之后每个单据只写"自己特有的规则"。这 8 人日会在第 5 个单据时开始回本。
★★★★★ 2. 库存操作收敛到唯一入口
所有影响库存的动作(采购入库、销售出库、调拨、盘点、报损、退货)只能调用同一个 InventoryService.UpdateStock(...),内部完成:写库存流水 → 更新库存余额 → 计算移动加权成本 → 校验负库存。
严禁任何单据 Service 自己写 SQL 改库存表。 库存对不上是 ERP 项目最常见的死因,而且一旦发生,排查成本极高。
★★★★★ 3. 建一张通用"单据关系表",别为每对上下游写一套
Erp_BillRelation:
SourceType, SourceId, SourceRowId -- 上游单据类型/主键/行
TargetType, TargetId, TargetRowId -- 下游
Qty, ReferencedQty -- 上游数量、已引用数量有了它,"选单加载"和"反写关单"能写成通用逻辑:新单据 A→B 只需配置一行关系,不用写代码。 否则采购订单→入库、销售订单→出库、入库→发票……每对写一遍,10 对就是 10 套几乎一样的代码。
★★★★☆ 4. 金额精度与税率规则,第一天就定死
decimal(18,4) 还是 (18,2)?含税单价还是不含税?税额按单头算还是按行算再汇总(两者会差几分钱)?尾差怎么摊?
这类问题后期改 = 全库 + 全部打印模板 + 全部报表返工。 现在花 2 小时定清楚,写进规范文档。
★★★★☆ 5. 严守"生成区 / 手写区"纪律
只能改这两个地方:
- 前端
src/extension/**/*.jsx(由views/**/*.vue顶部import extend引入) - 后端
Services/**/Partial/和Controllers/**/Partial/
其他任何生成文件都不要手改。 一旦重新生成(比如加了字段),你的改动会无声消失。建议在生成前先 git status 确认干净,生成后立即 diff 检查。
★★★★☆ 6. 不要用框架自带的 ERP 模块当基础
VolPro.ERP 的 42 个实体是演示级:库存计价、成本结转、核销、账期都没做完整。"照着改"比"重新建"更慢,因为你要先花时间分辨哪些是演示假逻辑。
建议:删掉 ERP/MES/dbtest 三个示例模块(router/viewGird.js 注释里官方明说可以删),按自己的建模来。但保留它们作为参考(尤其 ERP_InventoryIo/ 的选单交互范式值得看)。
★★★★☆ 7. 打印和文件存储都要做抽象层
IPrintService:现在用 hiprint,将来可能要服务端 PDF(批量打印/套打需求)IFileStorage:现在本地,将来 OSS/MinIO
两个接口各 1 小时,能避免将来各改 20 个调用点。
★★★☆☆ 8. 明细行多的时候,提前用 tableV2
官方 /docs/web/general/tableV2 专门处理表格卡顿。ERP 单据明细动辄几百行,等卡了再换就晚了——在第一个复杂单据上直接用 V2。
★★★☆☆ 9. 前端列配置抽到 options.js
参考 src/views/erp/erp/ERP_SaleOutOrder/options.js 的做法。同一个物料选择器在 10 个单据里出现,改一次就全生效。
★★★☆☆ 10. 审批流选 FormWorkFlow,不要选 WorkFlow
框架有两套:WorkFlow/(图状流程)和 FormWorkFlow/(表单审批流)。ERP 单据审批用后者,前者是为复杂流程图设计的,用在单据上会过度复杂。
★★★☆☆ 11. 批量操作用 SqlBulkInsert
导入、期初数据、库存初始化,用 /docs/cs/dev/sqlBulkInsert 的批量插入,别一行一行插。
★★★☆☆ 12. 缓存字典,别每个下拉框都查库
物料分类、单位、仓库这类低频变化的数据,用 /docs/cs/dev/cache 缓存,前端 initDicKeys 刷新。ERP 页面下拉框极多,不缓存会明显卡。
★★☆☆☆ 13. 提前规划多组织架构
如果客户有多个公司/工厂,TenancyId + UseDynamicShareDB 必须阶段 0 就定。后期加租户字段要改每一张表和每一个查询。
★★☆☆☆ 14. 做库存对账工具和日志,别等出问题
- 每天定时跑:库存余额 vs 流水累计,差异报警
- 关键操作(库存变动、审核、反审、删除)写审计日志(
VolPro.Core/Middleware/ActionLog.cs已有)
框架级优化清单(2026-10-31 补充,按投入产出分层)
与上面 14 条互补:4.1–4.14 是业务开发建议,这一节是框架本身的改造与补齐。基类三件套见 4.1–4.3,此处不重复。
P0:立项即做(缺了必返工/阻塞上线)
| 项 | 现状(源码证据) | 怎么做 | 量 |
|---|---|---|---|
| OSS 文件存储 | VolUpload.vue 的 uploadOSS() 空函数;后端 Upload() 只写本地磁盘 | 换 MinIO:后端 STS 凭证接口 + 填实前端上传 + $global.oss.url 配 CDN | 1–2 人日 |
| Quartz 持久化 | 内存调度器无 JobStore,多实例部署重复执行 | 补 ADO.NET JobStore(QuartzNETExtension 接入点现成);或任务机单实例 | 2–3 人日 |
| 公共基类三件套 | 见 4.1–4.3 | 同前 | 8 人日 |
| 数据库备份与容灾 | 远程 SQL Server 在云服务器上,全文无备份策略;ERP 数据丢失 = 致命 | 每日全备 + 日志备份 + 恢复演练一次;云厂商快照兜底 | 0.5 人日 |
| 环境分离 | dev 直连远程库,误操作即生产事故 | 至少 dev/prod 两库;DDL 存档已有(erp_slice_01_ddl.sql),建议引入 DbUp 之类工具管 migration | 1–2 人日 |
| 密钥管理 | appsettings.json 的 SQL 密码明文在仓库里 | 移出 Git(环境变量/user-secrets),并先改掉已泄露的密码 | 0.5 人日 |
| 部署方案 | 无 Nginx/HTTPS/日志轮转规划 | 反代 + HTTPS + 前端产物部署 + SeriLog 滚动轮转(现在 log 无限增长) | 2–3 人日 |
P1:防坑改造(第一周做完,省后面的调试命)
- 菜单缓存刷新口子:
Sys_MenuService的 static_menus只能靠"界面保存菜单"或重启刷新(已实际踩坑:改库改Sys_Menu.Auth重新登录无效)。加一个[ApiActionPermission(ActionPermissionOptions.SuperAdmin)] POST api/menu/refreshCache,内部清_menus+ 刷inernalMenu版本号。30 分钟工作量,终结一类排查灾难。 - 请求 DTO 规范:建
VolPro.Entity/Dto/统一放请求 DTO(全显式[JsonPropertyName("camelCase")],否则可能静默绑成 null);在ApiBehaviorOptions把 415/400 转成带字段名的错误信息(现在响应体为空,前端只见"服务器处理异常")。官方示例voidBill的[FromBody] Guid裸标量写法别照抄。 - 可空状态列防御:可空列存 NULL 时 SQL 层
x.Status != 4求值 UNKNOWN 整行被过滤(已实际踩坑)。ERP 单据状态列 DDL 统一NOT NULL DEFAULT 0,从源头消灭三值逻辑。 - 前端模式沉淀 composables:实战手写过的三个模式抽到
src/composables/复用——useSelectionPool(勾选池 Map +switching标志挡空selection-change+toggleRowSelection恢复)、useOptionsPatch(adjustMainOptions运行时改生成配置)、useGridBridge(parentCall跨渲染链桥接)。
P2:质量与运维(数据跑起来之后)
- 库存服务单元测试:框架自身无单测,别跟着裸奔。
InventoryService收敛后(4.2)天然可测,重点覆盖并发扣减、负库存拦截、反审冲销。 - 缓存/SignalR Redis 化:
UseRedis开关现成(CacheContext),多实例部署前打开;SignalR 补 Redis backplane,否则消息只达单机。 - 打印服务端 PDF 通道:hiprint 静默/连续打印依赖本地 socket 客户端,客户现场难部署。加服务端渲染 PDF(配合 4.7 的
IPrintService抽象),批量打单刚需,3–5 人日。 - Swagger 分组 + 权限标注:接口按模块分组,
[ApiActionPermission]反映到 swagger 描述,联调排查契约问题(415/字段名)快得多。 - 浏览器兼容范围确认:客户现场若有老 Chrome/360 兼容模式,vite 默认产物跑不了,需加
@vitejs/plugin-legacy;提前跟客户确认目标浏览器,别等装机当天发现。
不建议做的
- 别重构框架内核(ViewGrid/ServiceBase):收益低、破坏生成器兼容,一切通过 Partial/extension 外挂。
- 别拿
VolPro.ERP示例当产品:同 4.6。 - 别用界面上"直接改库"的方式调权限:改
Sys_Menu/Sys_RoleAuth后必须走菜单保存或重启,否则后端静态缓存持续吐旧数据(见 P1 第 1 条,做完刷新口子后此风险消除)。
五、风险清单
| 风险 | 概率 | 影响 | 应对 |
|---|---|---|---|
| 库存与流水对不上 | 高 | 致命 | 唯一入口 + 每日对账 + 事务 |
| 重新生成代码覆盖手写逻辑 | 中 | 高 | 严守 4.5 纪律 + 生成后 diff |
| 单据明细量大导致页面卡顿 | 中 | 中 | 一开始就用 tableV2 |
| 精度/税率规则中途变更 | 中 | 高 | 阶段 0 定死 |
| Quartz 多实例重复执行 | 中 | 中 | 上生产前改持久化 JobStore 或单实例跑任务 |
| 打印套打需求超出 hiprint 能力 | 中 | 中 | 抽象 IPrintService,留换方案余地 |
| 上下游反写逻辑散落各处 | 高 | 高 | 通用单据关系表(4.3) |
| 前端主子表交互定制超出生成器 | 中 | 中 | 复杂单据预留 2 倍时间 |
| 数据库无备份 | 高 | 致命 | 四.P0:每日全备 + 日志备份 + 恢复演练 |
| 密钥明文入库(SQL 密码在 Git 仓库) | 高 | 高 | 四.P0:移出仓库 + 立即改密 |
六、最小启动建议
如果只想先验证可行性,两周做一个垂直切片:
物料档案 → 采购订单(含明细)→ 采购入库(引用订单、反写、扣库存)→ 审批流 → 打印 → 库存查询带合计
这一个切片会同时验证:主从保存、上下游、库存事务、审批、打印、合计、导入导出——框架 80% 的关键能力。做完它,整体排期的误差能从 ±50% 收敛到 ±20%。不要一上来就铺开做 30 个单据。