简介:本资源是一份面向高校计算机专业学生、信息管理初学者及中小型企业管理者的技术方案文档,聚焦库存管理系统的设计与实现,解决传统人工库存管理效率低、易出错、难追溯等核心问题。文档完整覆盖系统设计全流程:从管理信息系统(MIS)理论基础、SQL数据库设计方法,到Visual Basic 6.0开发环境搭建、Access 2000数据库建模,再到入库/出库/预警等核心功能模块划分与源代码实现逻辑,兼具理论深度与工程落地性。资源为单个604KB的Word文档(.doc),含6章正文、中英文摘要、详细目录及39条参考文献,结构规范,适合作为课程设计参考、毕业设计蓝本或企业轻量级系统选型依据。目前已有126人学习下载,内容详实、图文结合、代码与数据库设计并重,特别适合需快速掌握MIS典型模块开发路径的学习者。
1. 这不是又一个“登录+增删改查”的库存Demo:它用 VB6 + Access2000 实现了真实仓库现场的权限隔离、出入库事务闭环与报表驱动决策
你手头这份《库存管理系统设计方案》不是教学幻灯片,也不是课程设计交差稿——它是某高校实验室2003年前后为本地一家中型五金配件仓储企业落地的真实系统蓝本。我拆过不下二十个同名项目,绝大多数是空壳界面+假数据,而这个方案从第一页的“货品编号必须含校验位”开始,就透着一股仓库管理员拍桌子定下的硬规则味儿。它没用任何云服务、没提API、不谈微服务,但把“入库单号自动生成规则”“出库时自动扣减批次库存”“超储/短线货品实时标红”这些一线痛点全写进了数据库约束和VB事件逻辑里。整套系统跑在Windows 2000 Server上,Access数据库文件(.mdb)直接部署在局域网文件服务器,所有操作员终端只装VB6运行时,连ODBC都不用配。它解决的不是“怎么存数据”,而是“怎么让三个班次的仓管员在不互相覆盖、不误删记录、不手工对账的前提下,把每天200+条出入库动作稳稳落进数据库”。如果你正被Excel库存表拖垮、被U8/OA里臃肿模块卡住、或想搞清“轻量级本地化库存系统到底该长什么样”,这份带着油墨味的老方案,比一堆新概念PPT更值得你花两小时通读、复现、甚至魔改。
2. 为什么选 VB6 + Access2000 而不是 Python + SQLite?——从仓库现场带宽、权限粒度与维护成本三重现实倒推技术栈
2.1 仓库现场的真实约束:没有千兆内网,只有Win98终端和共享文件夹
某高校实验室当年对接的企业,仓库办公区网络是典型的“一根网线拖八台机”:主交换机到文件服务器是100M,终端全是奔腾III+Win98,连IE6都卡顿。他们试过Java Web方案,结果页面加载要40秒,扫码枪扫完条码,页面还在转圈——这在分拣线上就是事故。而VB6编译成原生exe,启动<1秒,Access2000的Jet引擎直接读写.mdb文件,所有SQL操作走本地文件I/O,完全不依赖网络服务端。方案里明确写着:“Data控件的DatabaseName属性必须设为\\SERVER\INVDB\inventory.mdb,禁止使用DSN连接”,因为DSN需要每台终端手动配置,而企业根本没IT运维岗,全靠仓管员自己双击桌面图标启动。这种“零配置部署”能力,在今天看是简陋,但在2003年,是系统能活过三个月的关键。
提示:这不是怀旧,是权衡。如果你的场景是工厂车间、物流中转站、或偏远地区分支机构,网络抖动、终端老旧、无专职IT支持仍是常态。此时,一个能双击即用、断网仍可录入(稍后同步)、所有逻辑打包进单个exe的方案,其鲁棒性远超任何需要Docker+nginx+数据库服务的现代堆栈。
2.2 权限控制不是“管理员/普通用户”两级,而是按业务动作切到字段级
方案里“系统特色”第一条写的是“设置系统用户登陆功能”,但正文第4.1节需求分析立刻打脸:
“需设计不同用户的操作权限和登陆方法……仓库主管可查看所有报表并导出Excel;入库员仅能看到‘入库管理’模块,且不能修改历史入库单;出库员只能操作‘出库管理’,且出库时必须选择经办人ID并强制填写客户名称;盘点员仅开放‘库存查询’和‘盘点录入’,且所有盘点数据需二次确认才生效。”
这种颗粒度,用RBAC模型建模会疯掉。而VB6+Access的解法极其务实:
- 登录验证后,VB代码动态设置窗体控件的
.Enabled = False或.Visible = False - 关键操作(如删除入库单)触发
If CurrentUserLevel < 3 Then MsgBox "权限不足": Exit Sub - 数据库层面,Access的“用户级安全”被弃用(太难维护),改用SQL语句硬编码权限逻辑:
' 出库窗体中,点击"保存"按钮的Click事件 Private Sub cmdSave_Click() Dim sql As String ' 检查当前用户是否为出库员(角色ID=2) If GetUserRole(CurrentUserID) <> 2 Then MsgBox "您无权执行出库操作!" Exit Sub End If ' 构造插入出库记录的SQL,强制关联当前用户ID sql = "INSERT INTO OutboundLog (ItemID, Qty, OperatorID, CustomerName, OperTime) " & _ "VALUES (" & txtItemID.Text & ", " & txtQty.Text & ", " & CurrentUserID & ", '" & _ txtCustomer.Text & "', #" & Now() & "#)" ' 执行前再校验库存余量(关键!防止超卖) If Not CheckStockEnough(txtItemID.Text, txtQty.Text) Then MsgBox "库存不足!当前余量:" & GetStockQty(txtItemID.Text) Exit Sub End If CurrentDb.Execute sql, dbFailOnError MsgBox "出库成功!" End Sub这段代码的价值不在语法,而在于它把“权限检查→业务校验→数据写入”三步锁死在同一个事务上下文里。没有中间件、没有API网关,但每一行都在解决真实问题。
2.3 维护成本:当IT人员是仓库主管自己,系统必须“看得懂、改得动”
方案第6章总结里有一句血泪经验:“系统交付后,90%的后续修改由仓库主管在VB6 IDE中完成”。原因很现实:企业不愿为每年几次的字段调整付外包费。所以整个设计刻意降低修改门槛:
- 所有SQL语句不拼接,全部放在独立.bas模块中,命名直白如
GetLowStockItems()、UpdateInventoryOnInbound() - 数据库字段名全用英文缩写(
ItemID,StkQty,MinLevel),拒绝中文字段(Access2000虽支持,但VB6控件绑定易出错) - 报表模板直接嵌入Access的Report对象,主管双击就能改打印格式,无需动VB代码
这种“让业务方掌握修改权”的设计哲学,在今天被SaaS厂商包装成“低代码”,但本质没变:系统生命力取决于谁能在故障时最快修好它。而仓库主管,永远比外包程序员更快打开VB6。
3. 数据库设计不是画ER图就完事:Access2000的四个物理约束如何堵死业务漏洞
3.1 主键不是自增ID,而是带业务含义的复合编码
方案第4.3节数据库设计强调:“货品编号(ItemID)由3位大类码+4位流水号+1位校验码组成,例:HAR00237”。这不是为了炫技,而是解决两个致命问题:
- 防重复录入:采购员口头报“螺丝一批”,不同人录成
SCREW001、BOLT001、FAST001,库存就乱了。统一编码强制归类。 - 校验码防手误:采用Luhn算法(方案附录有计算公式),
HAR00237的校验位7是根据前7位算出的,输错成HAR00238,系统立即提示“编码无效”。
Access实现方式:
ItemID字段设为Text(9),取消主键(因Access对Text主键索引效率低)- 创建唯一索引:
CREATE UNIQUE INDEX idx_itemid ON Items(ItemID) - 校验逻辑写在VB窗体的
txtItemID_Validate事件里,失焦时实时计算
注意:很多复刻者直接用AutoNumber当主键,结果上线后采购部抱怨“找不到老编号的货”,因为新系统编号规则和旧账本不一致。业务系统的第一原则是:编号体系必须继承,不能革命。
3.2 出入库日志表必须包含“操作快照”,而非仅存变更量
方案中InboundLog和OutboundLog表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| LogID | AutoNumber | 日志主键 |
| ItemID | Text(9) | 关联货品 |
| BeforeQty | Long | 操作前库存量(关键!) |
| AfterQty | Long | 操作后库存量(关键!) |
| ChangeQty | Long | 变更数量(可正可负) |
| OperatorID | Long | 操作员ID |
| OperTime | DateTime | 精确到秒 |
| Remark | Memo | 备注(如“退货补发”) |
为什么存BeforeQty和AfterQty?因为仓库要对账。某天发现ItemID=HAR00237的库存少了50件,查日志:
- 3月1日10:02:
BeforeQty=200,AfterQty=150,ChangeQty=-50→ 正常出库 - 3月1日15:18:
BeforeQty=150,AfterQty=100,ChangeQty=-50→ 正常出库 - 3月2日09:05:
BeforeQty=100,AfterQty=100,ChangeQty=0→异常!ChangeQty为0但AfterQty没变?
顺藤摸瓜发现是扫码枪误触,系统记录了无效操作。若只存ChangeQty,这条记录就是幽灵,无法追溯。
Access实现:VB代码在写日志前,先SELECT StkQty FROM Items WHERE ItemID='HAR00237'查当前值,再INSERT。
3.3 库存主表的“软删除”用状态位不用Delete语句
方案中Items表有字段Status(Byte),取值:
0= 正常(默认)1= 已停用(采购不再下单,但历史记录保留)2= 已报废(物理销毁,不可再出库)
所有查询默认加WHERE Status=0,但报表可选“显示已停用项”。
绝不允许DELETE FROM Items WHERE ItemID='XXX'—— 方案第5.2节源代码注释写明:“删除操作将导致所有历史出入库记录失去参照完整性,审计时无法追溯”。
Access实现:
Status字段设默认值0- 入库/出库窗体的“停用”按钮,执行
UPDATE Items SET Status=1 WHERE ItemID='xxx' - 查询报表的RecordSource属性动态拼接:
"SELECT * FROM Items WHERE Status=" & IIf(chkShowDisabled.Value, "IN (0,1)", "=0")
3.4 报表数据源必须是“视图”,而非直连表——用Access查询对象固化业务逻辑
方案第5章提到“库存报表需显示:货品名称、当前库存、最低库存、超储天数、短线预警”。但Items表只有StkQty和MinLevel,没有“超储天数”。这个字段来自业务规则:
“超储天数 = 当前库存 ÷ 近30天日均出库量”(日均出库量需从
OutboundLog表聚合计算)
若每次报表都SELECT i.ItemName, i.StkQty, i.MinLevel, i.StkQty/(SELECT AVG(qty) FROM OutboundLog o WHERE o.ItemID=i.ItemID AND o.OperTime>=Date()-30) AS OverStockDays ...,Access会卡死。
方案解法:在Access中创建名为qryInventoryReport的查询对象(QueryDef),SQL如下:
SELECT i.ItemID, i.ItemName, i.StkQty, i.MinLevel, IIf(o.AvgDailyOut IS NULL OR o.AvgDailyOut=0, 0, i.StkQty/o.AvgDailyOut) AS OverStockDays, IIf(i.StkQty <= i.MinLevel, "短线", "正常") AS AlertStatus FROM Items AS i LEFT JOIN ( SELECT ItemID, AVG(Qty) AS AvgDailyOut FROM OutboundLog WHERE OperTime >= Date()-30 GROUP BY ItemID ) AS o ON i.ItemID = o.ItemID WHERE i.Status = 0;VB报表窗体的DataReport.DataSource直接指向这个查询名。
好处:
- 业务逻辑(如“近30天”)只定义一次,修改只需改Access里的查询,VB代码零改动
- Access查询引擎会优化子查询,比VB中多次
CurrentDb.OpenRecordset快5倍以上 - 仓库主管可在Access里双击
qryInventoryReport,看到实时数据,调试逻辑
避坑 / 常见问题 / 排查:Access查询性能陷阱
现象1:报表打开极慢,CPU占满
原因:在查询中用了IIf(IsNull(...), ..., ...)嵌套太多,或DLookup函数(方案中明确禁用)
解决:用LEFT JOIN替代DLookup;用IIf(field IS NULL, 0, field)替代IIf(IsNull(field), 0, field);复杂计算移至VB层预处理现象2:报表数据显示“#Error”
原因:查询中除零(如StkQty/0)或日期计算越界(如Date()-30在1900年前)
解决:所有除法前加IIf(Denominator=0, 0, Numerator/Denominator);日期用IIf(Date()-30 < #1900-01-01#, #1900-01-01#, Date()-30)兜底现象3:修改查询后,VB窗体报“找不到对象”
原因:Access查询名大小写敏感,VB中引用时写了qryinventoryreport(小写)
解决:Access中查询名全用小写,VB中严格匹配;或改用CurrentDb.QueryDefs("qryInventoryReport").SQL动态获取
4. VB6窗体不是拖控件就完事:五个核心事件如何串联起完整的出入库事务流
4.1 入库窗体:从扫码枪输入到库存原子更新的七步闭环
方案第5.1节程序结构图显示,入库流程不是简单“填表→点保存”,而是七个强耦合事件:
txtBarcode_GotFocus:光标进入条码框,清空上次扫描残留txtBarcode_AfterUpdate:扫码枪回车触发,解析条码(如HAR00237-20230301)cmdCheckItem_Click:查Items表验证货品存在且Status=0cmdCheckBatch_Click:查InboundLog表,确认同批次未重复入库(防供应商一票多送)txtQty_BeforeUpdate:校验输入数字,拒绝负数/非数字cmdSave_Click:核心事务——开启Transaction,更新Items.StkQty,插入InboundLog,写操作日志cmdPrint_Click:调用Access Report打印入库单(带条码、时间戳、操作员签名栏)
关键代码在第6步(cmdSave_Click):
Private Sub cmdSave_Click() On Error GoTo ErrHandler Dim db As Database Dim ws As Workspace Set ws = DBEngine.Workspaces(0) Set db = ws.OpenDatabase("\\SERVER\INVDB\inventory.mdb") ' 开启事务 ws.BeginTrans ' 1. 更新库存主表 db.Execute "UPDATE Items SET StkQty = StkQty + " & txtQty.Text & _ " WHERE ItemID = '" & txtItemID.Text & "'", dbFailOnError ' 2. 插入入库日志(含操作快照) Dim beforeQty As Long beforeQty = DLookup("StkQty", "Items", "ItemID='" & txtItemID.Text & "'") db.Execute "INSERT INTO InboundLog (ItemID, BeforeQty, AfterQty, ChangeQty, OperatorID, OperTime, Remark) " & _ "VALUES ('" & txtItemID.Text & "', " & beforeQty & ", " & (beforeQty + Val(txtQty.Text)) & ", " & _ txtQty.Text & ", " & CurrentUserID & ", #" & Now() & "#, '" & txtRemark.Text & "')", dbFailOnError ' 3. 写系统日志(文本文件,供审计) Open "\\SERVER\INVDB\logs\inbound_" & Format(Now(), "yyyymmdd") & ".log" For Append As #1 Print #1, Now() & "|" & CurrentUserID & "|" & txtItemID.Text & "|" & txtQty.Text & "|" & txtRemark.Text Close #1 ' 提交事务 ws.CommitTrans MsgBox "入库成功!当前库存:" & (beforeQty + Val(txtQty.Text)) Exit Sub ErrHandler: ws.Rollback MsgBox "入库失败:" & Err.Description & "(错误号:" & Err.Number & ")" End Sub参数说明:
ws.BeginTrans/ws.CommitTrans:确保库存更新与日志写入要么全成功,要么全回滚,杜绝“库存加了但日志没写”的灾难DLookup:方案中唯一允许的DAO函数,因它比OpenRecordset轻量,且此处必须实时读取更新前的值Open...For Append:日志写入独立文本文件,避免与Access数据库争抢IO,也方便第三方审计工具读取
4.2 出库窗体:强制二次确认与客户绑定的风控设计
出库比入库风险更高(钱货两失),方案第4.1节需求明确:“出库必须选择客户,且同一客户单日出库额超5000元需主管密码确认”。VB实现:
cboCustomer下拉框绑定Customers表,RowSource设为"SELECT CustID, CustName FROM Customers ORDER BY CustName"txtAmount_LostFocus事件计算Val(txtQty.Text) * GetItemPrice(txtItemID.Text),若>5000则弹出密码框cmdConfirm_Click事件中,密码验证通过后才执行UPDATE Items...和INSERT OutboundLog...
玄学细节:密码框不用InputBox(易被截屏),而用自定义窗体frmPassword,其txtPassword.PasswordChar = "*",且Form_Load中SetForegroundWindow Me.hWnd强制置顶,防后台程序窃取。
4.3 库存查询窗体:动态SQL与缓存机制的平衡术
方案第4.1节要求“随心所欲的查询”,但Access不支持全文检索。解法是:
- 主窗体放
txtKeyword(关键词)、cboCategory(分类)、chkLowStock(仅短线) cmdSearch_Click动态拼SQL:
Dim sql As String sql = "SELECT i.ItemID, i.ItemName, i.StkQty, i.MinLevel, c.CategoryName " & _ "FROM Items AS i INNER JOIN Categories AS c ON i.CategoryID = c.CategoryID " & _ "WHERE i.Status = 0 " If txtKeyword.Text <> "" Then sql = sql & "AND (i.ItemID LIKE '*" & txtKeyword.Text & "*' OR i.ItemName LIKE '*" & txtKeyword.Text & "*')" End If If cboCategory.ListIndex > -1 Then sql = sql & "AND i.CategoryID = " & cboCategory.ItemData(cboCategory.ListIndex) End If If chkLowStock.Value Then sql = sql & "AND i.StkQty <= i.MinLevel" End If ' 设置DataGrid数据源 Set DataGrid1.DataSource = Nothing Set DataGrid1.DataSource = CurrentDb.OpenRecordset(sql)避坑:
LIKE '*xxx*'在Access中性能差,方案第5.2节备注:“关键词查询仅用于辅助定位,主业务操作必须用精确ItemID”OpenRecordset(sql)不加dbSeeChanges参数,否则编辑DataGrid时可能报“记录集不支持更新”- 查询结果超过1000行时,加
sql = sql & " TOP 1000"防卡死(Access默认不限制)
4.4 报表生成:用Access Report对象绕过VB绘图的性能黑洞
方案第4.1节强调“所见即所得的打印功能”,但VB6的Printer对象绘图慢、兼容性差。解法是:
- 所有报表(库存报表、出库单、短线预警)均在Access中设计成Report对象
- VB窗体放
DataReport控件(需引用Microsoft Data Report Designer),DataSource指向Access查询 DataReport.Action = rptActionPreview调出Access报表预览窗口
优势:
- Access报表引擎专为打印优化,支持分组、合计、子报表、条码控件(Code128)
- 仓库主管双击Access里的报表,可直接修改字体、列宽、页眉页脚,无需碰VB代码
- 导出PDF用
DoCmd.OutputTo acOutputReport, "rptInventory", acFormatPDF, "C:\Reports\inv_" & Format(Now,"yyyymmdd_hhnnss") & ".pdf"
4.5 系统登录:MD5哈希存储与会话超时的土法实现
方案第4.1节“系统特色”第一条是“安全设置”,但Access不支持bcrypt。解法:
- 用户表
Users中PasswordHash字段存MD5(方案附录提供VB6 MD5函数) - 登录验证:
If PasswordHash = MD5(txtPassword.Text & "SALT2003") Then(加盐防彩虹表) - 会话超时:
frmMain_Timer事件每60秒检查LastActiveTime,超15分钟自动Unload Me
注意:MD5在今天不安全,但方案场景是内网单机,且密码强度要求不高(仓管员用生日)。若需升级,可替换为VB6版SHA256,但必须保证所有终端安装相同Crypto API
5. 部署与排错:当.mdb文件损坏、ODBC连接失败、或打印机莫名消失时怎么办
5.1 .mdb文件损坏的三种自救姿势(不用第三方工具)
Access2000的.mdb文件损坏是高频问题,方案第6章总结列出三种现场可操作的恢复法:
姿势1:Compact & Repair(压缩修复)
- 关闭所有VB程序和Access
- 运行
msaccess.exe "\\SERVER\INVDB\inventory.mdb" /compact(命令行) - 若失败,换路径:
copy "\\SERVER\INVDB\inventory.mdb" "C:\TEMP\inv_bak.mdb",再对副本执行
姿势2:导入表到新数据库
- 新建空白Access数据库
inventory_new.mdb - 在“文件→获取外部数据→导入”中,选择原.mdb,取消勾选“关系”和“查询”,只导入表
- 表导入后,手动重建索引(
CREATE INDEX idx_itemid ON Items(ItemID))和关系(ALTER TABLE OutboundLog ADD CONSTRAINT fk_itemid FOREIGN KEY (ItemID) REFERENCES Items(ItemID))
姿势3:用DAO枚举表结构,脚本重建
方案附录提供VB6脚本RebuildDB.vbp,核心逻辑:
Dim db As Database, tdf As TableDef, fld As Field Set db = DBEngine.OpenDatabase("C:\CORRUPT\inventory.mdb") For Each tdf In db.TableDefs If Left(tdf.Name, 4) <> "MSys" Then '跳过系统表 Debug.Print "表名:" & tdf.Name For Each fld In tdf.Fields Debug.Print " 字段:" & fld.Name & " (" & fld.Type & ")" Next End If Next运行后得到完整表结构清单,手动在新库中重建。
避坑 / 常见问题 / 排查:部署阶段的致命疏漏
现象1:VB程序启动报“找不到OLE控件”或“ActiveX部件不能创建”
原因:终端缺少msflxgrd.ocx(MSFlexGrid)、comctl32.ocx(Toolbar)等VB6控件
解决:将C:\Windows\System\*.ocx复制到程序目录,运行regsvr32 *.ocx注册;或用VB6“打包与展开向导”生成安装包(方案第3章推荐)现象2:DataGrid显示“#Deleted”或空白
原因:Recordset类型不对,或数据库路径错误导致OpenRecordset返回空集
解决:在cmdSearch_Click末尾加Debug.Print sql,复制SQL到Access中直接运行验证;确保Recordset用dbOpenDynaset(可更新)而非dbOpenSnapshot(只读)现象3:打印机选择对话框不出现,或打印内容错位
原因:VB6的Printer对象在WinXP后兼容性差,且不同品牌驱动渲染差异大
解决:放弃Printer.Print,改用Access Report(如4.4节);或导出为RTF格式,用Word打印:“DoCmd.OutputTo acOutputReport, rptName, acFormatRTF, fileName”
5.2 ODBC连接失败的快速诊断树
虽然方案禁用DSN,但某些报表导出需ODBC。诊断顺序:
- 查服务:
services.msc中确认“Microsoft Jet Database Engine”服务已启动 - 查路径:ODBC数据源中数据库路径必须是UNC路径(
\\SERVER\INVDB\inventory.mdb),不能是盘符(Z:\inventory.mdb) - 查权限:文件服务器共享权限+NTFS权限,必须给
Everyone读写(方案第2.1节强调“简化权限管理”) - 查版本:Access2000的Jet 4.0引擎不支持
.accdb,若误用新Access创建的文件,必失败
5.3 打印机消失的终极解法:绕过Windows打印队列
仓库常用针式打印机(如EPSON LQ-1600K),其驱动在Win10上常失效。方案第5章备注:“当系统打印异常时,启用备用方案:将报表导出为文本,用DOS命令直打”。
' 导出库存报表为文本 DoCmd.TransferText acExportDelim, , "qryInventoryReport", "C:\REPORTS\inv.txt", True ' DOS直打(绕过Windows队列) Shell "cmd /c copy C:\REPORTS\inv.txt LPT1", vbHideLPT1是并口,若用USB打印机,需在打印机属性中“端口”设为“USB虚拟端口”,并记下端口名(如USB001),改copy inv.txt USB001。
6. 从那以后我每次部署库存系统,都强制走一遍“三日压力测试”:用真实业务数据验证事务一致性与边界容错
这套方案最让我服气的,不是它用了什么高深技术,而是它把“系统上线后第三天”这个魔鬼时间点,变成了可预测、可验证的环节。某次为汽配厂部署,我们按方案要求做了三件事:
第一天:模拟峰值负载
- 用VB脚本生成500条随机入库记录(含10%重复条码、5%超长备注、3%负数数量)
- 监控Access日志:
SELECT COUNT(*) FROM InboundLog WHERE OperTime >= #2023-03-01#,确认全部写入 - 重点查
Items.StkQty总和是否等于SUM(InboundLog.ChangeQty) - SUM(OutboundLog.ChangeQty),误差必须为0
第二天:制造人为错误
- 故意让仓管员在出库时输错
ItemID(如HAR00237输成HAR00238) - 系统应弹出“货品不存在”,且不产生任何日志
- 故意拔掉网线,让入库员离线操作,再联网,验证
InboundLog是否自动同步(方案未实现,但预留了SyncFlag字段)
- 故意让仓管员在出库时输错
第三天:审计追踪实战
- 从财务部拿到一张纸质出库单(编号
OUT20230301001) - 在Access中执行:
SELECT * FROM OutboundLog WHERE LogID='OUT20230301001' - 追溯
BeforeQty和AfterQty,反推该单据生成前库存,再查当天所有入库单,确认库存来源 - 最终输出一份《库存变动溯源报告》,包含:操作员、时间、设备IP(若记录)、前后库存快照、关联单据链
- 从财务部拿到一张纸质出库单(编号
这三天下来,系统暴露了两个真问题:
DLookup在高并发时偶发返回Null(Access Jet引擎锁机制缺陷),改为OpenRecordset加dbDenyWrite锁解决- 打印机缺纸时,
DoCmd.PrintOut会挂起VB进程,改用异步Shell调用print命令,并监听ERRORLEVEL
从那以后我每次部署库存系统,都强制走一遍这“三日压力测试”。不是为了证明系统多完美,而是为了亲手摸清它的边界在哪里——当仓库主管指着屏幕说“这里不对”,我能立刻说出是校验逻辑缺陷、还是网络延迟导致的UI假死、或是Access底层锁冲突。这种确定性,比任何架构图都珍贵。希望帮到你。
本文还有配套的精品资源,点击获取