RuoYi二次开发KS进销存系统:库存扣减与单据流转实战

发布时间:2026/9/28 9:33:45
RuoYi二次开发KS进销存系统:库存扣减与单据流转实战 简介这是一套基于Java与RuoYi框架二次开发的KS进销存系统源码面向小型经销商、初创商贸团队及需要轻量级系统转型的开发者。它区别于流程繁重的专业进销存软件采购、销售、库存三大模块与业务流程简洁清晰更贴近小公司实际运作在保证高效的同时满足日常经营需求。资源包共936个文件约10.94MB其中304个Java源文件承载后端业务逻辑149个JavaScript脚本与101个Vue组件构成前端交互界面另有39个XML配置、28个HTML页面、20个CSS样式及13个VM模板文件并附带PNG、SVG、GIF等图片素材与SQL脚本、JAR依赖目录结构完整便于二次开发与功能扩展。目前已有833人学习下载。读者可借此掌握RuoYi二次开发的工程组织方式理解进销存核心模块的代码实现并直接以现有源码为基础搭建或改造适配自身业务的小型经销存系统。1. 基于 RuoYi 二次开发的 KS 进销存系统从跑通到改出业务味很多 Java 后端同学第一次接触进销存不是从零写而是拿到一套 RuoYi 脚手架想在上面长出「采购—入库—销售—出库—库存—往来账」这条完整链路。标题里的 KS 进销存系统本质就是一套基于 Java Spring Boot MyBatis Vue 的 RuoYi 二次开发产物RuoYi 负责权限、菜单、代码生成、日志这些通用底座KS 负责把商品、仓库、供应商、客户、单据这些业务对象塞进去。它适合两类人一是做课程设计或项目实训、需要一套能跑能改的 Java 源码二是中小团队想快速搭一套内部用的进销存不想从权限体系开始造轮子。真正难的不是把项目跑起来而是理解 RuoYi 的代码生成器产出和你手写业务之间的边界知道哪些该复用、哪些必须自己重写否则改到后面会发现自己在一堆自动生成的模板代码里迷路。2. RuoYi 底座与 KS 业务模块的边界划分2.1 先看清 RuoYi 给了什么、没给什么RuoYi 这类脚手架的核心价值是把「用户、角色、菜单、部门、字典、操作日志、代码生成」这套后台管理通用能力做完了。你拿到源码后登录、鉴权、分页、导出、权限注解基本都是现成的。但它不会给你进销存的业务语义什么是商品 SKU、什么是批次、库存该按仓库还是按库位算、采购单和入库单是一对一还是一对多这些 RuoYi 一概不管。所以二次开发的第一步不是写代码而是划边界。我一般会先把 KS 的业务对象列出来再对照 RuoYi 已有的表结构判断哪些能复用、哪些要新建。常见的划分方式是层级RuoYi 已有KS 需要新建权限sys_user / sys_role / sys_menu无组织sys_dept可复用为门店/仓库归属字典sys_dict_type / sys_dict_data商品分类、单据状态走字典业务无商品、仓库、供应商、客户、采购单、销售单、库存流水日志sys_oper_log库存变动单独记业务日志这张表的意义在于凡是能塞进字典的枚举就别新建表。单据状态、结算方式、出入库类型全部走 sys_dict_data前端直接用 RuoYi 的字典组件渲染省掉大量重复的下拉框代码。2.2 用代码生成器产出第一版 CRUD再手工改业务RuoYi 的代码生成器是二次开发效率的关键。以商品表为例先在数据库建表字段设计要一次到位因为生成器只认表结构CREATE TABLE ks_product ( product_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 商品ID, product_code VARCHAR(64) NOT NULL COMMENT 商品编码, product_name VARCHAR(128) NOT NULL COMMENT 商品名称, category_id BIGINT DEFAULT NULL COMMENT 分类ID, unit VARCHAR(16) DEFAULT 件 COMMENT 单位, purchase_price DECIMAL(12,2) DEFAULT 0.00 COMMENT 参考进价, sale_price DECIMAL(12,2) DEFAULT 0.00 COMMENT 参考售价, status CHAR(1) DEFAULT 0 COMMENT 状态(0正常1停用), del_flag CHAR(1) DEFAULT 0 COMMENT 删除标志, create_by VARCHAR(64) DEFAULT COMMENT 创建者, create_time DATETIME DEFAULT NULL COMMENT 创建时间, update_by VARCHAR(64) DEFAULT COMMENT 更新者, update_time DATETIME DEFAULT NULL COMMENT 更新时间, remark VARCHAR(500) DEFAULT NULL COMMENT 备注, PRIMARY KEY (product_id), UNIQUE KEY uk_product_code (product_code) ) ENGINEInnoDB COMMENT商品表;建表时几个参数必须注意del_flag和create_by/create_time这几列是 RuoYi 的约定字段生成器会自动把它们映射成 BaseEntity 的字段少了它们生成的代码会缺逻辑删除和审计信息。product_code加唯一索引是因为进销存里商品编码重复是灾难宁可让数据库拦。金额字段统一用DECIMAL(12,2)不要用 float库存和金额的精度问题后期极难排查。建完表进「系统工具 → 代码生成」导入表、预览、下载把生成的controller/service/mapper/domain和 Vue 页面按模块放进去。这一步产出的代码能直接跑通增删改查但它只是骨架没有库存联动、没有单据流转、没有并发扣减。接下来才是真正的二次开发。2.3 库存扣减为什么不能直接 update新手最容易翻车的地方是把库存当成商品表的一个字段出库时直接update ks_product set stock stock - #{num}。单机低并发下能跑一旦有并发或需要追溯立刻出问题你不知道这批货是从哪个仓库出的、什么时候出的、对应哪张单。正确做法是库存拆成两张表一张ks_stock存当前结存商品仓库维度一张ks_stock_record存每一次变动流水。扣减时先写流水再更新结存并且用带条件的 update 保证不会扣成负数// 扣减库存带库存充足校验返回影响行数 Update(UPDATE ks_stock SET stock_num stock_num - #{num}, update_time NOW() WHERE product_id #{productId} AND warehouse_id #{warehouseId} AND stock_num #{num}) int deductStock(Param(productId) Long productId, Param(warehouseId) Long warehouseId, Param(num) Integer num);这里的关键是AND stock_num #{num}这个条件。它把「判断库存够不够」和「扣减」合并成一条原子 SQL影响行数为 0 就说明库存不足直接抛业务异常回滚。如果先 select 判断再 update中间的时间窗在高并发下必然超卖。流水表则记录变动前后的数量、单据类型、单据号方便对账。这套结构比单字段库存多写一点代码但它是进销存能不能上生产的分水岭。3. 单据流转采购入库与销售出库的落地实现3.1 单据主从表设计与状态机进销存的核心是单据。采购单、入库单、销售单、出库单结构上都是「主表 明细表」。以采购入库为例主表存供应商、仓库、单据号、状态、总金额明细表存商品、数量、单价、金额。设计时有两个决定要提前定第一采购单和入库单是否合并。小系统常见做法是合并成一张「采购入库单」简化流程规范做法是采购订单和入库单分开支持分批入库。课程设计或内部小工具我建议合并能省掉大量状态同步逻辑。第二状态怎么流转。用字典定义状态主表存状态码流转时校验前置状态// 入库单审核只有草稿状态才能审核审核后写库存 Transactional(rollbackFor Exception.class) public void approve(Long orderId) { KsInboundOrder order inboundMapper.selectById(orderId); if (!0.equals(order.getStatus())) { throw new ServiceException(只有草稿状态的单据可以审核); } ListKsInboundItem items itemMapper.selectByOrderId(orderId); for (KsInboundItem item : items) { // 入库是加库存同样走带条件的原子更新 stockMapper.addStock(item.getProductId(), order.getWarehouseId(), item.getNum()); stockRecordMapper.insertRecord(item, order, IN); } order.setStatus(1); inboundMapper.updateById(order); }Transactional必须加且rollbackFor Exception.class否则库存写了一半单据状态没改数据就脏了。状态校验放在最前面避免无效单据走到库存操作。审核动作里同时写结存和流水保证两者一致。3.2 单据号生成与并发下的唯一性单据号看起来简单实际是个坑。常见格式是CG 日期 流水号比如CG20240520001。如果用「查当天最大号 1」的方式生成并发下必然重号。可靠做法有两种一是用数据库序列表配合行锁二是用 Redis 原子自增。我一般用 Redis按天设 key当天首次访问设过期时间public String generateOrderNo(String prefix) { String date LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); String key ks:order:no: prefix : date; Long seq redisTemplate.opsForValue().increment(key); if (seq ! null seq 1L) { // 首次生成设置两天过期避免 key 无限堆积 redisTemplate.expire(key, Duration.ofDays(2)); } return prefix date String.format(%04d, seq); }increment是原子操作天然防重。过期时间设两天而不是当天是为了跨零点时旧 key 还能自然淘汰不会因为临界点导致序号跳变。如果项目没引入 Redis退而求其次用数据库的SELECT ... FOR UPDATE锁一张序列表但性能差很多高并发下会成为瓶颈。3.3 前端单据页面的复用套路RuoYi-Vue 的前端是 Vue Element UI代码生成器产出的页面是标准列表 弹窗表单。单据页面和普通 CRUD 的差别在于明细行主表表单里要嵌一个可增删的明细表格。我的做法是不改生成器模板而是在生成的页面基础上把明细部分抽成一个子组件主表单通过 props 传商品列表和明细数组。明细行的商品选择用 RuoYi 的字典或远程搜索组件选中商品后自动带出单位、参考价。数量、单价变化时实时算金额和合计。这些是纯前端逻辑不涉及后端改动但能显著提升可用性。注意明细的校验数量必须大于 0单价不能为负提交前前端先校验一遍后端再校验一遍前端校验是体验后端校验是底线。4. 二次开发中最容易踩的五个坑4.1 现象改了实体类字段页面不生效原因RuoYi 的代码生成器产出的是静态代码你改了数据库或实体但 Mapper XML 里的 resultMap、前端表单的字段列表不会自动更新。解决字段变更后要么重新用生成器生成覆盖要么手工同步三处——domain 类、Mapper XML 的 resultMap 和 SQL 列、Vue 页面的表单和表格列。我习惯改表后先重新生成一份对比避免漏改。4.2 现象库存对不上结存和流水汇总差几件原因多半是某次库存操作只更新了结存没写流水或者事务没生效导致部分提交。解决所有库存变动必须走同一个 service 方法方法内先写流水再更新结存统一加事务。排查时用流水表按商品仓库汇总和结存表比对差值定位到具体单据。建议加一个定时对账任务每天跑一次汇总比对。4.3 现象单据审核后库存加了但取消审核库存没减回去原因只写了正向流程没写反向流程。进销存里审核和反审核必须成对。解决反审核时校验单据是否已被下游引用比如入库单已被结算没有引用才允许然后反向操作库存并写一条反向流水。反向流水不要删原流水保留完整轨迹。4.4 现象并发下同一商品超卖原因库存判断和扣减分离或者用了没有条件校验的 update。解决回到 2.3 的原子 SQLstock_num #{num}这个条件不能省。另外注意事务隔离级别MySQL 默认 RR 下配合行锁能保证但如果用了读写分离或缓存要额外考虑一致性。4.5 现象菜单配了但页面 404 或按钮不显示原因RuoYi 的权限是菜单 按钮两级前端路由和后端权限标识要对应。新增模块时菜单里的路由地址、组件路径、权限标识三处必须和代码里的PreAuthorize(ss.hasPermi(ks:product:list))一致。解决新增模块后先在「菜单管理」里配好目录、菜单、按钮权限标识按模块:对象:操作命名再给角色分配。按钮不显示基本就是权限标识对不上或角色没分配。5. 把 KS 进销存改造成能长期维护的样子跑通增删改查只是起点真正决定这套二次开发值不值得投入的是它能不能扛住业务变化。我的经验是抓住三个习惯。第一业务逻辑全部下沉到 servicecontroller 只做参数校验和权限注解。RuoYi 生成的 controller 很薄但很多人图省事把库存计算、单据校验写进 controller后期复用和测试都痛苦。单据审核、库存变动这类逻辑统一放 service加事务controller 只调一行。第二所有枚举走字典所有金额走 BigDecimal。字典让状态、类型可配置不用改代码BigDecimal 避免金额精度问题运算时统一setScale(2, RoundingMode.HALF_UP)。这两条看着基础但进销存里金额和状态出错排查成本极高。第三给库存和单据加对账与日志。库存对账定时任务、单据操作日志、库存流水这三样是出问题时的后悔药。我一般会在库存流水表上加索引(product_id, warehouse_id, create_time)对账和追溯都靠它。验证一套 KS 进销存是否合格不用看功能列表跑一个场景就够同一商品在两个仓库各有库存开一张跨仓库的销售单并发提交两笔出库看库存是否准确扣减、流水是否完整、单据状态是否正确。这个场景能过基本可以放心往上加业务。我自己第一次做进销存时就是栽在并发扣减上后来把原子更新和流水表补上才踏实。希望帮到你。本文还有配套的精品资源点击获取