资讯详情 VB6+SQL Server销售管理系统源码:开单、库存与对账实战
📅 2026/11/4 3:32:14
简介一套由VB6.0与SQL Server 2000构建的销售管理系统源代码面向具备VB基础、希望学习数据库桌面应用开发的新手和初级开发者。系统围绕产品销售业务包含产品信息录入、产品管理、查询打印、金额汇总和简单报表等模块可用于理解窗体事件、控件调用、SQL语句执行与数据绑定的实际组织方式。压缩包共51个文件、约1.37MB以frm窗体和frx资源为主另有vbp工程、bas模块、cls类模块、ocx控件、SQL建库脚本及dsr报表定义等基本覆盖一个可运行VB项目的常见组成。读者可结合登录界面和主窗体梳理系统入口利用SQL目录脚本建库并调试数据访问流程再对照报表模块学习查询输出与打印组织。已有442人学习适合作为课程设计参考、小型管理系统原型借鉴以及VB6连接SQL Server的入门练习。1. 从一张手工销售台账说起这套 VB6 SQL Server 源码到底解决什么问题接手某公司销售部门的内部系统维护时我拿到的第一份材料是一张被改了七年的电子表格客户名靠手打退货用负数列在同一行月底对账要三个人核两天。替代方案就是这套 VB6 SQL Server 销售管理系统完整源代码——客户端用 VB6 写界面和业务逻辑数据落在 SQL Server开单、发货、回款、报表串成闭环是一份能跑通全流程的 VB 源码-数据库实例。它适合三类人拿它做毕设或课程设计的学生需要一套结构完整、能演示、能讲清设计的成品接手老旧系统维护的开发者需要一份可对照的业务骨架看清楚单据、库存、应收之间的账是怎么对上的还有想把销售台账从表格里搬出来的小微企业要的是本地部署、改得动、看得见源代码。不适合追求新技术栈的人。VB6 加 SQL Server 的组合在界面表现和跨平台能力上没有任何优势但它的调试链路短、部署依赖少、代码阅读门槛低对着源码逐行跟一遍业务流程比看十篇架构文章都快。后面按需求建模、总体架构、核心模块、避坑排查、验收答辩五步展开每一步都落到可以照着改的代码和表结构上。2. 业务建模与数据库设计客户、商品、单据、回款四类实体的表结构拆法2.1 从流转路径反推实体一张销售单经过谁的手设计表之前先把人理顺。这套系统里的主线是销售内勤录单销售主管审核仓库按审核后的单据出库财务按出库单开票并登记回款退货走反向流程。每一个环节的交接点在数据库里都必须有一个字段或一张表来承载否则事后追不了责。所以订单主表上必须有状态字段取值通常这样规划0 草稿、1 待审核、2 已审核、3 已出库、4 已开票、5 已作废。状态不是给人看的装饰它是权限判断和库存扣减的判断依据——只有状态从 1 变成 2 的那一次操作才允许扣库存从 2 变成 5 的那一次才允许回滚库存。我见过不少同学把状态做成一个纯展示的标签结果作废单据照样扣了库存答辩时被问一句作废了货怎么回来就卡住了。围绕这条主线实体可以归成四类。第一类是主数据客户表、商品表、仓库表、员工与用户表。第二类是单据销售订单主表加明细表退货单同理。第三类是库存商品结存表和出入库流水表。第四类是钱收款单表和应收核销记录。四类实体之间通过主键关联明细表永远只认主表的主键不去关联客户或商品的自然键。这里有个容易忽略的点客户和商品要有停用而不是删除。销售历史里引用了这个客户物理删除会直接把历史单据变成孤儿数据。通常的做法是加一个 enabled 标志位界面的下拉框只加载启用的历史查询照样能带出来。2.2 核心表结构与字段约定金额字段用 DECIMAL 而不是 FLOAT这是硬规矩。浮点数在累加几百行明细之后会出现 0.01 的误差对账时财务一眼就能看出来。金额统一 DECIMAL(14,2)单价可以给到 DECIMAL(14,4) 以支持小数计价。日期时间用 DATETIME只关心日期的用 DATE别拿字符串存日期否则按时间段查询时排序和范围筛选全靠转换函数。下面是我一般会用的订单主表和明细表结构字段名保持 VB6 里习惯的短命名读代码时对得上。-- 销售订单主表一张单据一行金额由明细汇总回写 CREATE TABLE t_sales_order ( order_id INT IDENTITY(1,1) PRIMARY KEY, order_no VARCHAR(20) NOT NULL, -- 单据号唯一索引 customer_id INT NOT NULL, order_date DATETIME NOT NULL, total_amount DECIMAL(14,2) NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0, -- 0草稿 1待审 2已审 3出库 4开票 5作废 remark VARCHAR(200) NULL, create_by VARCHAR(20) NOT NULL, create_time DATETIME NOT NULL DEFAULT GETDATE(), audit_by VARCHAR(20) NULL, audit_time DATETIME NULL ); CREATE UNIQUE INDEX ux_order_no ON t_sales_order(order_no); -- 销售订单明细只认主表主键行号用于界面排序和打印 CREATE TABLE t_sales_order_d ( detail_id INT IDENTITY(1,1) PRIMARY KEY, order_id INT NOT NULL, row_no INT NOT NULL, goods_id INT NOT NULL, qty DECIMAL(14,3) NOT NULL, price DECIMAL(14,4) NOT NULL, amount AS (qty * price) PERSISTED, -- 计算列避免程序算错 CONSTRAINT ck_qty CHECK (qty 0) ); CREATE INDEX ix_order_d ON t_sales_order_d(order_id);amount用持久化计算列而不是每行由 VB 代码算好再写进去好处是无论从界面保存还是从导入脚本写数据金额都不会算错。ck_qty这个 CHECK 约束挡住的是负数量——退货单独走退货单销售单里出现负数基本就是录入错误。外键加不加团队里常年有争论。老项目里常见做法是完全不加靠程序保证一致性理由是性能和维护方便我的做法是核心单据表加外键日志表和流水表不加。加外键的成本是插入顺序必须严格但换来的是不会出现明细表里挂着一个不存在的订单。学生做毕设加上外键在答辩时是个加分项至少说明你想过数据完整性。2.3 单据号生成与库存流水的设计取舍单据号不要用SELECT MAX(order_no)加一这条路上翻车的人太多了。两个人同时点保存拿到的都是同一个最大值第二条插入直接撞唯一索引用户看到的是保存失败实际上是并发。正确的做法是把发号做成一个独立的序列表用一条 UPDATE 语句原子地取号整个过程放在存储过程里。-- 序列表按 前缀日期 分桶一天一个序列 CREATE TABLE t_seq ( seq_key VARCHAR(20) PRIMARY KEY, -- 例如 SO20240612 seq INT NOT NULL ); CREATE PROCEDURE sp_next_order_no prefix CHAR(2), out_no VARCHAR(20) OUTPUT AS BEGIN SET NOCOUNT ON; DECLARE d CHAR(8), seq INT; SET d CONVERT(CHAR(8), GETDATE(), 112); -- yyyymmdd UPDATE t_seq WITH (ROWLOCK) SET seq seq seq 1 WHERE seq_key prefix d; IF ROWCOUNT 0 -- 当天第一张单先插桶 BEGIN INSERT INTO t_seq(seq_key, seq) VALUES(prefix d, 1); SET seq 1; END SET out_no prefix d RIGHT(0000 CAST(seq AS VARCHAR(4)), 4); ENDWITH (ROWLOCK)是给行级锁一个提示避免默认粒度升级成页锁把别的取号请求堵住。seq seq seq 1这种写法在 T-SQL 里能一次完成读旧值 加一省掉一次额外查询。库存部分我倾向于双表t_stock存当前结存用于快速查询和界面显示t_stock_flow存每一笔出入库流水带单据号、方向、数量、操作人和时间。双表必须在同一个事务里写结存用增量更新流水只追加不修改。只存流水不存结存查库存要每次 SUM 一遍商品一多就慢只存结存不存流水出问题的时候查不出是哪张单扣错了只能干瞪眼。这套组合是这类系统里最稳的做法。3. 系统总体架构VB6 客户端与 SQL Server 的职责怎么切3.1 三层结构在 VB6 里的落地形态VB6 项目的文件类型本身就能承担分层。窗体frm放表现层只做控件事件、输入校验和界面状态切换类模块cls放业务规则比如订单能不能审核、金额怎么汇总、库存够不够标准模块bas放数据访问和公共函数。这三类文件在工程资源管理器里分目录摆好比什么架构文档都直观。再正规一点的做法是把业务层编译成 ActiveX DLL客户端只引用接口数据库连接串和 SQL 语句都封在 DLL 里。这么做的好处是业务规则可以复用将来加一个 Web 端或批处理程序不用重写代价是调试变麻烦改一行代码要重新编译注册部署也多一个 DLL 要管。我的经验是毕设和课设用工程内类模块就够了能把分层讲清楚真要交付给客户再拆 DLL。表现层有个纪律要守住窗体里不写 SQL。窗体的职责就是把控件上的值读出来组装成一个业务对象丢给业务层再把返回结果画回界面。一旦frmOrderSave_Click里出现SELECT后面所有改动都会长在事件里最后没人敢动。业务层对象一般这样设计clsOrder负责一张订单的加载、校验、保存clsStock负责库存查询和扣减clsAuth负责权限判断。每个类对外暴露属性对应表字段和方法Load/Save/Audit内部不直接拼字符串统一调数据访问模块。3.2 数据访问层ADO 连接、命令与记录集的统一封装引用里勾上 Microsoft ActiveX Data Objects 2.8 Library后面所有数据库操作都走 ADO。连接字符串单独放在一个模块的常量里不散落在各个窗体中换服务器只改一处。 modDB.bas —— 全局数据访问基础设施 Public Const CONN_STR As String _ ProviderSQLOLEDB.1;Persist Security InfoFalse; _ Initial CatalogSalesDB;Data Source192.168.1.10,1433; _ User IDapp_user;Password******;Connect Timeout8 打开连接CursorLocation 设为 Client配合断开式记录集使用 Public Function DBOpen() As ADODB.Connection Dim cnn As New ADODB.Connection cnn.CursorLocation adUseClient cnn.ConnectionTimeout 8 cnn.Open CONN_STR Set DBOpen cnn End FunctionCursorLocation adUseClient表示游标由客户端 ADO 库维护好处是可以拿到断开式记录集表单加载完就能关连接减少数据库连接占用代价是大数据量查询会把结果全拉到本地内存。这个系统的单据查询一般都有日期和客户条件返回几千行以内客户端游标完全够用。参数化命令是这个模块里最该花时间的地方。VB6 里拼 SQL 字符串是常态也是注入和引号报错的源头客户名里带一个单引号就能让保存失败。用ADODB.Command加CreateParameter把值当参数传这两个问题一起解决。 插入订单主表并返回自增主键供明细表使用 Public Function InsertOrder(cnn As ADODB.Connection, _ ByVal sNo As String, _ ByVal lCustId As Long, _ ByVal dtDate As Date, _ ByVal curTotal As Currency) As Long Dim cmd As New ADODB.Command Dim rs As ADODB.Recordset Set cmd.ActiveConnection cnn cmd.CommandType adCmdText cmd.CommandText INSERT INTO t_sales_order(order_no,customer_id,order_date,total_amount,status,create_by) _ VALUES(?,?,?,?,0,?); SELECT IDENTITY AS new_id cmd.Parameters.Append cmd.CreateParameter(p_no, adVarChar, adParamInput, 20, sNo) cmd.Parameters.Append cmd.CreateParameter(p_cust, adInteger, adParamInput, , lCustId) cmd.Parameters.Append cmd.CreateParameter(p_date, adDBTimeStamp, adParamInput, , dtDate) cmd.Parameters.Append cmd.CreateParameter(p_amt, adCurrency, adParamInput, , curTotal) cmd.Parameters.Append cmd.CreateParameter(p_user, adVarChar, adParamInput, 20, gUserName) Set rs cmd.Execute InsertOrder CLng(rs(new_id).Value) rs.Close End Function参数里的第四个实参是长度adVarChar必须给adInteger和adDBTimeStamp可以省略写空第五个实参是值。同一个Command对象反复执行时要记得cmd.Parameters.Refresh或者重新创建否则参数类型可能对不上。SELECT IDENTITY紧跟在 INSERT 后面同一个批次执行拿到的就是刚插入那一行的自增 ID比再查一次MAX(id)安全得多。如果表上有触发器往别的表插数据IDENTITY会拿到触发器里的 ID这种场景要换成SCOPE_IDENTITY()。3.3 权限控制与操作日志挂在哪一层权限分三级功能权限能不能进这个菜单、数据权限能看哪些客户、操作权限能不能审核、能不能作废。VB6 客户端做前两级比较自然登录成功后把用户的角色和权限位读进全局变量主窗体加载时按权限动态设置菜单项的Enabled和Visible。 登录后加载权限模块级变量 gPerm 用一个 Long 的位掩码存 Public Sub LoadPermission(ByVal sUserId As String) Dim rs As ADODB.Recordset Set rs DBQuery(SELECT perm_mask, user_name FROM t_user WHERE user_id sUserId ) If Not rs.EOF Then gPerm CLng(rs(perm_mask).Value) gUserName rs(user_name).Value End If rs.Close End Sub 位运算判断第 2 位代表审核权限 Public Function CanAudit() As Boolean CanAudit (gPerm And 4) 4 End Function位掩码的好处是加一个权限不用改表结构坏处是可读性差得在代码里写清每一位的含义。学生答辩被问到权限怎么设计的能说清楚位运算和权限位分配比说用了一个权限表要具体得多。关键的一点前端隐藏按钮不等于安全。作废、审核这类会改数据的操作在业务层或存储过程里要再校验一次当前用户是否有权限否则换个客户端直连数据库就能绕过去。操作日志表t_op_log记录操作人、时间、模块、动作、单据号、变更前后的关键值写入时机放在业务层保存成功之后和业务数据同一个事务提交。日志写在界面层的话一旦有人绕过界面直接调业务类日志就漏了。4. 核心模块实现开单、库存扣减与应收对账的三条主链路4.1 销售开单主从表联动录入与一次事务提交开单界面是整套系统里交互最复杂的部分。上面一排是表头控件客户、日期、业务员、备注下面是明细网格 MSFlexGrid列是商品编码、名称、数量、单价、金额。用户在网格里改数量或单价金额列要立刻重算底部合计同步刷新。网格的编辑事件里要注意两点。一是不要在Change事件里做重算用户每敲一个字符都会触发光标会跳应该用LostFocus或者在单元格离开时统一处理。二是金额格式化用Format$(v, 0.00)别直接赋数值否则网格里会出现 12.300000000001 这种尾巴。保存动作必须是一个事务先写主表拿主键再循环写明细最后回写主表合计。中间任何一步失败全部回滚。 保存一张订单主表 明细 合计回写同一事务 Public Function SaveOrder() As Boolean Dim cnn As ADODB.Connection Dim lOrderId As Long, i As Long On Error GoTo EH Set cnn DBOpen() cnn.BeginTrans 开启事务 lOrderId InsertOrder(cnn, m_sOrderNo, m_lCustId, m_dtDate, 0) For i 0 To m_lDetailCount - 1 InsertOrderDetail cnn, lOrderId, i 1, _ m_aGoodsId(i), m_aQty(i), m_aPrice(i) Next i UpdateOrderTotal cnn, lOrderId 用 SUM 回写避免前端算错 cnn.CommitTrans SaveOrder True cnn.Close Exit Function EH: On Error Resume Next cnn.RollbackTrans 任何异常都整体撤销 cnn.Close MsgBox 保存失败 Err.Description, vbExclamation SaveOrder False End Function主表插入时total_amount先传 0等明细全部写完再用UPDATE ... SET total_amount (SELECT SUM(amount) FROM ...)回写。这样做的好处是合计永远等于明细之和不会出现表头和明细对不上的情况——这种对不上在对账时是最难查的因为两边都看起来对。保存前的校验也要列清楚至少一行明细、数量大于零、单价不为负、客户必选、日期不能晚于当前日期加一个合理区间。校验放在业务层的Validate方法里界面只管调它并提示这样换一个界面也能复用同一套规则。4.2 库存扣减与并发把判断写进 UPDATE 的 WHERE 里库存扣减最常见的错误写法是先查一遍库存判断够不够再执行更新。这两步之间有窗口期两个仓管同时出库同一批货都能查到大库存都能通过判断最后扣成负数。这个坑我在早期项目里踩过一次月底盘点差了十几件查了两天才定位到。正确做法是把够不够的判断压进 UPDATE 的 WHERE 条件里让数据库在一条语句内完成判断和扣减再用ROWCOUNT看有没有命中。-- 扣减库存qty n 保证不会扣成负数 UPDATE t_stock SET qty qty - n WHERE goods_id gid AND warehouse_id wid AND qty n; IF ROWCOUNT 0 BEGIN RAISERROR(库存不足出库失败, 16, 1); RETURN; END -- 同一事务里写流水只追加 INSERT INTO t_stock_flow(goods_id, warehouse_id, direction, qty, order_no, op_user, op_time) VALUES(gid, wid, -1, n, orderNo, user, GETDATE());ROWCOUNT 0说明要么库存不够要么商品和仓库对不上两种情况都该让整个出库事务回滚。RAISERROR的第二个参数是严重级别16 表示用户可修正的错误VB 端捕获后弹提示即可不会中断连接。再往前一步是幂等性。同一个出库按钮被连点两次或者网络卡顿用户重复提交会造成重复扣减。解法是在扣减前先判断单据状态UPDATE t_sales_order SET status 3 WHERE order_id oid AND status 2如果ROWCOUNT 0说明这张单已经被处理过或者是待审核状态直接返回。状态字段从 2 到 3 的这个转换本身就是一个只能成功一次的开关用它挡住重复出库比任何前端防抖都可靠。作废和退货走反向流程状态从 3 变 5 时把库存加回去并写一条方向为 1 的流水。反向操作同样要检查当前状态是否真的是 3不能无条件加库存否则作废两次就多出一批货。4.3 应收对账与报表输出从视图到 DataReport财务最关心的是这个客户欠我多少。这个数字不该在报表里现算而应该由数据库视图统一口径所有地方引用同一个视图避免两个报表算出两个数。-- 应收余额视图订单金额 - 已收金额按客户汇总 CREATE VIEW v_ar_balance AS SELECT c.customer_id, c.cust_name, SUM(o.total_amount) AS order_amt, ISNULL(SUM(p.paid_amt), 0) AS paid_amt, SUM(o.total_amount) - ISNULL(SUM(p.paid_amt), 0) AS balance FROM t_customer c LEFT JOIN t_sales_order o ON o.customer_id c.customer_id AND o.status IN (3, 4) -- 只统计已出库和已开票 LEFT JOIN (SELECT customer_id, SUM(amount) AS paid_amt FROM t_receipt GROUP BY customer_id) p ON p.customer_id c.customer_id GROUP BY c.customer_id, c.cust_name;status IN (3,4)这个条件是关键草稿和待审核的单据不算应收作废的不算否则财务看到的欠款永远偏高。这种业务口径一定要写在视图里并加注释放在报表代码里迟早被人改错。报表用 VB6 自带的 DataReport 加 DataEnvironment或者用 MSFlexGrid 直接打印。DataReport 的好处是分页、页眉页脚、分组汇总现成绑定一个 Command 对象就能出结果坏处是部署时要带上MSDERUN.DLL等运行库客户机上缺一个就报错。数据量大时先在存储过程里汇总好再喂给报表别让报表去处理几十万行明细。导出走 Excel 自动化或者直接生成 CSV。Excel 自动化在没装 Office 的机器上会失败稳妥做法是先判断CreateObject(Excel.Application)是否成功失败就退化成 CSV用户至少拿到数据。导出按钮上还要加个条件校验起始日期不能晚于结束日期客户不选就是全部这些提示写在导出之前比导出完发现是空表要好。5. 常见问题与排查VB6 接 SQL Server 最容易翻车的五个点5.1 换台机器就提示未找到提供程序现象很典型开发机上一切正常装到客户机或同学电脑上程序一启动就弹未找到提供程序。该程序可能未正确安装连接直接失败。原因几乎都指向 Provider 字符串和运行库。SQLOLEDB.1是随 MDAC 安装的旧版 OLE DB 提供程序Windows 7 之后系统自带的是MSOLEDBSQL如果代码里写死了SQLOLEDB.1而目标机器上 MDAC 组件不全就会报这个错。另一种情况是项目编译时用了 64 位环境而 VB6 编译出的程序是 32 位两边位数不匹配。解决办法是分两步先在目标机上确认提供程序是否存在用一段几行的脚本枚举ADODB.Connection的Providers集合把名字打印出来再把连接字符串里的 Provider 改成目标机实际存在的那个并把它提升为配置项而不是硬编码。部署包里带上 MDAC 安装程序安装顺序放在主程序之前。位数问题在编译时就要定死VB6 只有 32 位编译器任何 64 位组件都不要引。5.2 两个人改同一张单据后保存的把前面的覆盖了现象是用户反馈我改的备注没了查日志发现两个人相隔几分钟都点了保存后一次把前一次的内容整个盖掉。原因是界面用断开式记录集加载数据UpdateBatch提交时按主键整行覆盖中间别人改的字段自然丢失。这不是 VB6 的毛病是所有读出来改完整行写回模式的通病。解决办法是加乐观锁。主表加一个row_version列每次更新时SET row_version row_version 1 WHERE order_id oid AND row_version oldROWCOUNT 0就说明数据已经被别人改过提示用户刷新后重录。这个改动量很小但能挡掉绝大多数我的修改丢了的投诉。如果业务允许也可以只更新发生变化的字段减少冲突面但那需要逐字段比较代码量会翻倍通常不值得。5.3 中文变成问号排序也不对现象是保存进去的客户名在数据库里查询显示为???或者按名称排序时拼音顺序完全乱掉。原因有两个层面。字段类型如果是VARCHAR而不是NVARCHAR中文在非中文排序规则的库上就会丢字符连接字符串如果没指定字符集ADO 传输时也可能按单字节处理。排序问题则来自数据库的排序规则Chinese_PRC_CI_AS和SQL_Latin1_General_CP1_CI_AS对中文的排序结果完全不同。解决办法是新建库时就把排序规则设成中文规则中文内容字段一律用NVARCHAR参数类型对应adVarWChar而不是adVarChar。已经建好的库要改排序规则代价很大涉及重建索引不如在查询时用COLLATE临时指定。字符集这种事在项目第一天定好后面就不用想定错了后面每次碰到都要重来一遍。5.4 报表一点打印就报错开发机上却是好的现象是 DataReport 在开发环境里预览正常打包安装到客户机上点打印弹找不到方法或数据成员或者直接闪退。原因是报表依赖的运行库没有随程序一起部署。DataReport 需要MSDERUN.DLL、MSDATREP.DLL等文件并且要在目标机上注册如果报表里用了第三方网格或图表控件对应的 OCX 也要一并打包注册。VB6 的打包向导有时会漏掉这些隐式依赖尤其是通过Set动态赋值的对象。解决办法是用打包向导生成安装包后手动检查一遍依赖清单把报表相关的 DLL、OCX 全部列进去在一台干净的虚拟机上做一次完整安装验证。更省事的做法是把报表输出改成 HTML 或直接调 Excel绕开 DataReport 的部署问题代价是打印格式要自己控制。两种都行关键是别在客户现场第一次发现。5.5 打包安装后程序启动即闪退现象是双击图标窗口一闪就没了没有任何错误提示。在开发环境里按 F5 跑得好好的。原因通常是启动对象的设置或者启动时的初始化抛了异常。VB6 工程属性里启动对象如果指向一个已经删除的窗体编译能过但运行会直接退出。另一种是Sub Main里做了数据库连接或配置文件读取失败时没有错误处理异常一路往上抛进程静默结束。解决办法分两步排查。第一步在Sub Main的开头加文件日志把每一步写进一个 txt闪退后看日志停在哪一行第二步在初始化逻辑外面套On Error GoTo把错误号和描述写到日志里再优雅退出。我在交付前一定会在一台没装过开发环境的机器上完整跑一遍安装、启动、登录、开单、出报表这一遍能挡掉八成的现场问题。看起来费时间实际比远程救火省得多。6. 验收与答辩十分钟内证明这套系统真的能跑起来验收这件事评委和客户看的不是代码量而是这条链路有没有断。准备一个十分钟的演示脚本比准备二十页 PPT 有用。6.1 三段式演示顺序第一段走主流程两分钟内完成新建客户、新建商品、录入一张三行明细的订单、审核、出库。每做一步说一句这一步改了哪个表的哪个字段比如出库时说明订单状态从 2 变 3 并写了一条负数库存流水。第二段故意制造异常展示系统的健壮性把某商品库存改成 1下一张单开 5 件看它是否拦住并给出提示。第三段展示数据一致性打开数据库把订单主表合计和明细 SUM 对一遍把库存结存和流水 SUM 对一遍两个数对得上比任何口头说明都有说服力。6.2 验收清单检查项通过标准常见扣分点数据库初始化执行建库脚本后无报错基础数据齐全脚本里写死了本机路径主流程闭环开单→审核→出库→收款 全流程走通出库后状态没变库存一致性结存 初始 流水汇总只更新结存不写流水并发与幂等重复点出库不会重复扣减靠前端按钮置灰防重权限校验无权限账号看不到也点不动只隐藏菜单未校验数据层部署验证干净机器安装后能启动并登录缺运行库、缺报表 DLL答辩时被问到这套系统有什么不足与其说界面不够美观不如说清楚边界单机或小局域网部署、并发量在几十人以内、没有做分布式事务。知道自己方案的边界比硬吹功能全更能让人信服。从那以后我每次交付前都会强制自己在干净机器上把上面这张表走一遍一条不落地打勾哪怕时间再紧。系统跑不起来代码写得再漂亮都是零。希望帮到你。本文还有配套的精品资源点击获取