
简介这是一套基于.NET/C#的商品销售管理系统进销存完整源码面向需要完成毕业设计、课程设计或企业进销存项目二次开发的开发者。系统围绕商品、库存、销售、采购和报表统计五大核心模块展开展示了 ADO.NET 数据库交互、MVC 分层架构、多线程并发处理与异常处理等关键技术的实际应用。压缩包共 348 个文件约 23.76MB其中 153 个 .cs 源码文件、51 个 .resx 界面资源文件、25 个 .dll 依赖库并附带 .sln/.csproj 工程文件及 .mdf/.ldf 数据库文件便于直接打开编译和调试。资源内还包含可运行的 exe 和缓存、配置文件适合对照学习完整项目结构。目前已有 227 人浏览学习读者可结合源码深入理解进销存业务流程掌握 C# 企业级应用开发思路并在此基础上进行功能扩展与界面定制。 做了几年企业管理系统进销存这个方向接触了不少说实话“.net商品销售管理系统完整源码”这个标题我一看就知道是什么类型的东西一个典型的Windows桌面端也有一部分是Web版的商品销售管理项目核心模块就是进货、销售、库存三大块也就是行业里常说的进销存。这类项目在学校里是毕业设计的热门选题在公司里是小型商贸企业最常用的业务系统不管是拿来学习.NET编程还是直接改造成商业项目都很有参考价值。这种系统的生命力很强因为逻辑清晰、需求稳定。一个商品从供应商进货到仓库再从仓库销售给客户中间的库存变动、资金往来、单据记录全都围绕“进、销、存”展开。如果你正准备用.NET开发一套这样的系统或者手里刚拿到一套源码不知道怎么跑起来、怎么改这篇文章可以帮你把整条链路理清楚。1. 项目定位与技术选型分析1.1 进销存系统到底在解决什么问题很多人第一次接触进销存会把它想得很复杂实际上它的核心需求非常集中。我用最简单的话总结管住商品在哪、管住钱从哪来、管住单据往哪去。具体拆开看其实是这几类业务场景进货入库采购员从供应商那里进货仓库管理员把货品点收入库系统记录进货数量和进货单价同时自动增加库存。销售出库销售员开销售单客户付款后从仓库取货系统记录销售数量和销售单价同时自动扣减库存。库存管理实时查看每种商品的当前库存量低库存时给出预警定期盘点库存修正误差。往来账目供应商的应付账款、客户的应收账款每一笔进货和销售都会影响账目需要清晰记录。统计报表按月统计销售总额、进货总额、毛利查看热销商品和滞销商品。这些需求放在一起就构成了一个标准进销存系统。标题里说是“商品销售管理系统”本质还是进销存只是更侧重销售端但进货和库存是支撑销售的基础缺一不可。1.2 为什么选.NET来做这类系统.NET在这个领域的优势非常明显。首先Visual Studio提供了从数据库设计到界面开发的完整工具链开发效率高其次无论是WinForms还是ASP.NET WebForms生态里都有现成的报表控件、Excel导出组件、打印组件这些对于进销存这种强单据、强报表的业务来说太重要了。技术选型上如果你拿到的是经典版本大概率是WinForms加SQL Server或Access如果偏现代可能是ASP.NET Core Web API加Vue或直接MVC。我的建议是学习的话优先看WinForms版本因为代码直观、逻辑集中容易读懂进销存的核心流程做商业项目的话用ASP.NET Core Web版本部署到服务器后多台电脑通过浏览器同时访问更符合小公司的实际使用场景。提示标题说“.net”没有限定具体是Framework还是Core也没有限定桌面还是Web。我下面讲解时会覆盖两种形态的常见问题核心业务逻辑是通用的。2. 数据库设计与进销存核心模型2.1 核心表结构设计思路进销存系统的数据库设计是整个项目的地基。见过不少新手直接在窗体里写SQL连表关系都没理清结果做到后面发现库存对不上账。我最推荐的主表结构是这样的商品表Product商品ID、商品编码、名称、规格、单位、进货价、销售价、库存下限。供应商表Supplier供应商ID、名称、联系人、电话、地址。客户表Customer客户ID、名称、联系人、电话、地址、应收款。采购入库单PurchaseOrder单据编号、供应商ID、入库日期、操作员、备注。采购入库明细PurchaseOrderDetail明细ID、单据ID、商品ID、数量、进货单价、金额。销售出库单SaleOrder单据编号、客户ID、销售日期、操作员、备注、应收款。销售出库明细SaleOrderDetail明细ID、单据ID、商品ID、数量、销售单价、金额。库存表StockID、商品ID、当前库存量。库存流水表StockLogID、商品ID、变动类型入库/出库/盘点、变动数量、变动前库存、变动后库存、关联单据编号、操作时间。表结构的关键点在于业务单据要采用“主表加明细表”的结构。主表记录一次进货或销售的整体信息明细表记录这一单里包含哪些商品、各自的数量和价格。这种设计符合实际业务习惯也方便统计和对账。2.2 为什么必须要有库存流水表很多简化版的进销存项目只有一张库存表每次进货就Update库存量每次销售就减库存但这样做有一个致命问题库存错了查不到原因。加了库存流水表之后每一次库存变动都会留下一条记录包括变动前的数量、变动后的数量、关联的单据号。比如账面库存是100实际盘点只有95通过流水表可以直接查到是某次销售重复扣减了还是某次入库没加进去。这就像银行流水一样总账可以错但流水账不能丢。库存变动逻辑我用一段伪SQL来表达核心思想BEGIN TRANSACTION; -- 写入流水 INSERT INTO StockLog (ProductId, ChangeType, ChangeQty, BeforeQty, AfterQty, RelatedBillNo, OperTime) VALUES (ProductId, IN, Qty, BeforeQty, BeforeQty Qty, BillNo, GETDATE()); -- 更新库存 UPDATE Stock SET CurrentQty CurrentQty Qty WHERE ProductId ProductId; COMMIT TRANSACTION;进货入库是加销售出库是减这两种操作都要放在事务里执行保证流水和库存要么同时成功、要么同时失败。这是进销存项目最核心的代码逻辑。2.3 库存预警的简单实现低库存预警是进销存系统很受欢迎的功能。实现方式不复杂在商品表里加一个字段叫MinStock库存表查询时关联比较var lowStockList db.Stock .Join(db.Product, s s.ProductId, p p.ProductId, (s, p) new { s, p }) .Where(x x.s.CurrentQty x.p.MinStock) .Select(x new { 商品编码 x.p.ProductCode, 商品名称 x.p.ProductName, 当前库存 x.s.CurrentQty, 库存下限 x.p.MinStock }) .ToList();查询结果里库存量小于等于下限的商品在界面里用红色或黄色标记出来提醒操作员及时补货。这个功能看着小但实际使用频率很高能帮企业避免热销商品断货的尴尬。3. 核心功能模块实现细节3.1 登录与权限控制进销存系统涉及钱和货权限控制不能马虎。一般的权限模型是用户表、角色表、用户角色关联表、菜单权限表。每个角色能访问哪些菜单、能操作哪些按钮通过权限表来配置。密码存储方面千万不要明文保存密码。最简单的做法是使用MD5加盐或SHA256哈希存储哈希值而不是原始密码。登录时比较哈希结果public static string ComputeSha256Hash(string rawData) { using (SHA256 sha256Hash SHA256.Create()) { byte[] bytes sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData)); StringBuilder builder new StringBuilder(); for (int i 0; i bytes.Length; i) { builder.Append(bytes[i].ToString(x2)); } return builder.ToString(); } }WinForms项目里登录成功后通常用MDI多文档界面作为主窗体根据当前用户的角色动态加载菜单项Web项目里则用Session保存用户信息在页面加载时判断是否有权限访问。权限控制的核心思路是“服务端校验”界面隐藏只是用户体验真正的校验必须放在后端逻辑里。3.2 进货与销售流程的实现进货和销售本质上是同一个流程的镜像进货是商品从供应商进入公司销售是商品从公司流向客户。代码实现上也是一套逻辑。以销售单为例保存一张销售单要做的操作是校验客户是否存在不存在则提示先添加客户。校验销售明细是否为空至少要有一种商品。逐条校验商品库存是否足够不够则提示并中止。保存主表信息获取主表ID。保存明细表数据每条明细关联主表ID。逐条更新库存并写入库存流水。更新客户应收款。这段逻辑用代码写出来是这样的简化示例using (var transaction db.Database.BeginTransaction()) { try { var order new SaleOrder { OrderNo GenerateOrderNo(), CustomerId customerId, SaleDate DateTime.Now, Operator currentUser.Name, Remark remark }; db.SaleOrder.Add(order); db.SaveChanges(); foreach (var item in saleDetailList) { // 检查库存 var stock db.Stock.FirstOrDefault(s s.ProductId item.ProductId); if (stock null || stock.CurrentQty item.Qty) throw new Exception($商品【{item.ProductName}】库存不足); // 保存明细 var detail new SaleOrderDetail { OrderId order.Id, ProductId item.ProductId, Qty item.Qty, SalePrice item.Price, Amount item.Qty * item.Price }; db.SaleOrderDetail.Add(detail); // 更新库存并写流水 int beforeQty stock.CurrentQty; stock.CurrentQty - item.Qty; db.StockLog.Add(new StockLog { ProductId item.ProductId, ChangeType OUT, ChangeQty -item.Qty, BeforeQty beforeQty, AfterQty stock.CurrentQty, RelatedBillNo order.OrderNo, OperTime DateTime.Now }); } db.SaveChanges(); transaction.Commit(); } catch { transaction.Rollback(); throw; } }退货处理也值得说一下。常见做法有两种一种是做红字单据也就是填一张负数数量的进货单或销售单来冲销原单另一种是直接做一张独立的退货单。从开发角度红字冲销代码逻辑上更简单直接从原来的业务流程走一遍只是数量取负数对账时也能准确反映业务变化。如果做独立的退货单类型需要额外增加一张单据表、一组状态逻辑工作量会大一点但业务含义更清晰。3.3 库存查询与销售报表统计库存查询是日常使用频率最高的功能。界面通常是一个DataGridView列出商品编码、名称、规格、单位、当前库存、库存下限、状态。顶部提供关键词搜索和分类筛选。查询的关键是不要用EF一次性加载所有商品数据量大了之后会卡最简单的优化方式是使用分页查询var pageResult db.Stock .Join(db.Product, s s.ProductId, p p.ProductId, (s, p) new { s, p }) .Where(x string.IsNullOrEmpty(keyword) || x.p.ProductName.Contains(keyword) || x.p.ProductCode.Contains(keyword)) .OrderBy(x x.p.ProductCode) .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList();销售报表的统计一般用SQL去聚合比在程序里循环要高效得多。比如按月统计销售总额SELECT CONVERT(VARCHAR(7), SaleDate, 120) AS SaleMonth, SUM(Amount) AS TotalAmount, COUNT(DISTINCT OrderNo) AS OrderCount FROM SaleOrderDetail d INNER JOIN SaleOrder o ON d.OrderId o.Id WHERE SaleDate startDate AND SaleDate endDate GROUP BY CONVERT(VARCHAR(7), SaleDate, 120) ORDER BY SaleMonth;报表导出Excel时如果还在用DataGridView逐行导出就太累了。推荐直接使用NPOI或ClosedXML代码简洁而且不需要在服务器上安装Office。NPOI的优点是完全免费、社区活跃导出的Excel格式兼容性好。4. 开发调试与发布部署的常见坑4.1 运行环境与.NET Framework版本问题拿到一套.NET源码第一步就是确保环境能跑起来。很多老项目的目标框架是.NET Framework 4.5或4.6新电脑可能没装对应的运行时导致打开程序提示缺少组件或者直接闪退。比较常见的现象是Windows系统提示“电脑需要安装.NET Framework 3.5”。这是老WinForms项目常见的情况。解决办法是在控制面板的“启用或关闭Windows功能”里勾选“.NET Framework 3.5”或者下载对应的离线安装包。如果项目目标框架是.NET Framework 4.8直接去官网下载安装即可安装完成后在Visual Studio里重新编译一次确保引用版本正确。注意如果你用的是Visual Studio 2022创建新项目时默认可以选.NET 8.NET Core的延续这和.NET Framework 4.8是两套不同的东西。老源码通常是Framework项目用VS打开后不要强行升级到.NET 8除非你做好了应对API差异的心理准备。先把项目跑通再考虑升级的事情。4.2 Web版本发布后的连接与页面加载问题Web形态的进销存系统发布到IIS后经常遇到一些浏览器网络层面的报错。排查思路比报错本身更重要。比如浏览器控制台报net::ERR_INCOMPLETE_CHUNKED_ENCODING这个错误的常见原因是服务器在返回响应时连接被中断可能是响应过大、代理超时、或IIS配置问题。排查时先看小页面能否正常打开再访问大数据量的页面逐步缩小范围同时检查IIS的请求超时设置和应用程序池的回收策略。再比如net::ERR_CONNECTION_RESET多半是服务端主动断开了连接常见于HTTPS证书不匹配、防火墙拦截、或应用池崩溃。遇到这类问题先看Windows事件日志里的应用程序日志确认是否发生了未处理异常导致进程退出再看看端口是否被占用、防火墙是否放行了对应端口。如果用了Docker部署还会遇到http: request canceled while waiting for connection这类镜像拉取或容器访问超时的问题。大部分原因是网络原因导致的镜像仓库连接超时可以配置国内镜像加速器如果是容器间通信超时重点检查容器网络的DNS配置和端口映射。4.3 并发扣库存与数据备份进销存系统跑起来后最容易出问题的场景是多人同时操作。比如两个销售员同时卖同一件商品各自开销售单如果代码里没有并发控制可能出现超卖。解决思路是在更新库存时使用条件更新或者直接使用数据库行锁。比如UPDATE Stock SET CurrentQty CurrentQty - qty WHERE ProductId productId AND CurrentQty qty;如果影响行数为0说明库存不足或已经被其他事务扣减了业务层再做提示。这种方式比“先查再更新”更安全避免了并发下的数据竞态问题。数据备份这件事也要提一下。很多小公司根本没有定时备份的习惯直到数据库文件损坏才追悔莫及。建议在服务器上设置计划任务每天凌晨备份一次数据库文件保留最近7天的备份。SQL Server可以用维护计划或sqlcmd脚本完成sqlcmd -S .\SQLEXPRESS -U sa -P password -Q BACKUP DATABASE MyStockDB TO DISKD:\Backup\MyStockDB_$(date %Y%m%d).bak备份文件不要和数据库文件放在同一块硬盘上否则硬盘故障时两份数据一起丢失备份就失去意义了。5. 完整源码交付与二次开发建议5.1 一套合格的源码包里应该有什么很多人下载源码后不会跑不是代码有问题而是交付包不完整。一套合格的进销存源码至少要包含这几样东西项目源代码文件VS解决方案.sln和项目.csproj确保打开就能编译。数据库脚本.sql文件包含建库、建表、初始数据。配置文件说明连接字符串在哪里改。部署文档或README写清楚环境要求、运行步骤。示例账号方便直接登录体验。如果你手头的源码缺少数据库脚本可以先用数据库工具反向生成建表脚本或者通过EF自动创建数据库。但最稳妥的方式还是找一份带完整SQL脚本的版本省去自己猜测表结构的麻烦。5.2 二次开发可以从哪些方向扩展基础进销存跑通之后根据实际业务需求常见的扩展方向有这几个单据打印销售单、进货单、送货单的模板打印可以用FastReport或RDLC报表实现一维码、二维码也能直接打印。条形码/二维码支持商品表增加条码字段出入库时使用扫码枪自动识别效率和准确率能提升很多。多仓库管理库存表增加仓库ID字段实现多仓库存独立管理不同仓库之间的调拨也需要对应业务逻辑。会员与折扣客户表增加会员等级字段销售单自动计算折扣会员积分累计兑换。对接小程序或公众号Web系统预留API接口让客户能在线查看订单、余额销售人员可以在手机上查询库存和价格。扩展时要注意保持原有核心逻辑的稳定尤其是库存扣减和单据生成的逻辑不要轻易改动。新增功能尽量作为模块独立开发通过接口调用核心服务避免把原有代码改得面目全非。根据我的实操经验拿到任何一套进销存源码我建议你按这个顺序来研究先跑通数据库脚本再登录系统熟悉界面然后把进货、销售、库存三个核心流程完整走一遍最后再去看代码。这样你心里先有了业务地图再看代码时就能迅速定位每段逻辑对应的是什么业务场景远比从头到尾逐行读代码高效得多。进销存这套东西业务逻辑大于技术细节搞懂了业务写代码只是时间问题。本文还有配套的精品资源点击获取