
开头做进销存系统的人不少但真正把“多店”这个需求跑通、能直接拿来改的源码市面上确实不多。我最近亲测了一版多店进销存管理系统的源码从部署到改门店逻辑再到实际录单整体走了一遍后台基础功能齐全门店数据隔离清晰源码结构也适合二次开发。这篇文章就把我实测过程中用到的技术点、数据库设计思路、核心业务逻辑和踩坑记录一次性梳理清楚。这套系统适合两类人一是刚起步的开发者想拿一套完整项目学习进销存里的采购、销售、库存、多门店数据怎么组织二是小企业主或IT负责人需要一套能本地部署、能改业务规则、不被SaaS年费绑死的门店管理系统。里面涉及的代码量不小但关键模块我会拆开讲你照着改就行。1. 项目整体设计与思路拆解1.1 多店进销存到底在解决什么问题先说需求本质。单店进销存管好“进、销、存”三件事就够了但多店进来以后问题立刻变成四层总部怎么看到所有门店的数据门店之间怎么隔离数据调拨怎么处理库存变化以及总部能不能控制门店的采购和售价权限。这套源码的核心设计就是“机构-门店”两级结构。总部能开多个门店每个门店有独立的库存账但所有门店的SKU基础资料、供应商信息、客户信息是共享的。这个思路非常务实如果每个门店都维护一套完全独立的商品档案后面做采购汇总和销售汇总会异常痛苦因为同一种商品可能被录成不同名称、不同单位。1.2 为什么选这套方案而不是直接写一套新系统我实测下来这套源码最大的优势不是功能多而是结构规矩。它用经典的三层架构控制器处理请求、服务层处理业务规则、数据层处理数据库交互。你不需要读懂每一行代码只看service目录里的方法名基本就能推断出这张表在业务流程里的位置。另一个让我决定分享的原因是登录权限部分没有做过度的“魔改”就是标准的RBAC模型。角色表、用户表、菜单表、角色菜单关联表齐全想加一个“店长”角色让它只能看本店数据直接加角色再加数据权限过滤条件就行不需要解开源代码里的硬编码判断。2. 核心功能模块与数据库设计解析2.1 商品档案为什么要分基础表和门店扩展表多店系统最容易被新手搞砸的地方就是商品表设计。这套源码的处理方式是两张表基础商品表存储商品编码、条码、名称、规格、单位、分类门店商品扩展表存储门店ID、售价、会员价、最低库存预警值。基础表只有一份门店若有特殊价格就在扩展表里覆盖。我一开始觉得这设计多此一举但实际录数据的时候才体会到它的价值。你改一次商品单位所有门店都生效你要给A门店定一个促销价完全不影响B门店。更关键的是进货成本不需要放在扩展表里——采购成本属于单据数据属于历史事实商品档案里只放成本和售价的“默认值”这样才能保证每笔业务的毛利算得清。2.2 库存账是怎么做到门店独立的这套系统用了“库存表加流水表”的组合方式。库存表记录当前结存数量按门店、商品、批次维度存储。每一次入库单审核、销售单过账、调拨单确认都会生成库存流水同时更新库存表的结存数量。库存表更新的逻辑是在事务里完成的。比如销售出库系统会先锁住该门店该商品的行记录计算当前可用库存再判断是否够出够用则扣减不够则抛出提示。这么做的好处是高并发录单的时候不会出现超卖。如果你是自己学习这个源码可以重点关注事务注解和行锁的处理位置这是进销存系统最容易出生产事故的地方。2.3 采购入库、销售出库、门店调拨三条核心链路采购入库链路采购订单 → 收货 → 入库单审核 → 增加库存流水同时更新应付账款。销售出库链路销售订单 → 收款 → 出库单审核 → 扣减库存并记录销售成本销售成本按移动加权平均计算。门店调拨链路调出方出库单 调入方入库单 → 一次调拨生成两条库存流水调出扣库存调入加库存总部报表里不产生多余购销数据。三条链路在源码里对应三个服务类接口命名很直接比如StockInService.createStockIn、StockOutService.createStockOut、TransferService.transferBetweenStores。我建议你上手时先别急着改页面直接对着这几个方法读把数据流转画在纸上系统就懂了一半。2.4 多门店报表的汇总口径报表模块是这套源码的亮点。总部看板能看到所有门店的销售总额、毛利总额、库存总额门店看板只看本店数据数据权限靠SQL里的固定条件过滤实现不是前端隐藏那么简单。我特别看了它月结存成本的计算方式。它没有直接月汇总库存流水而是每天定时把门店商品库存快照存进历史表月底再依据期初、入库、出库、调拨四类数据重新核算。这个设计能让盘点误差找出来比如某一笔单据被作废后库存流水有更正快照表能帮你还原当时实际库存是多少。3. 实操部署与源码改造要点3.1 本地部署完整步骤我用的是Windows环境加PHPStudy如果你熟悉Linux加宝塔面板流程基本一致。整个部署过程大约30分钟前提是你会看错误日志。把源码放到网站根目录PHP版本选择7.4以上低版本跑不起来。创建数据库导入项目根目录的SQL文件注意字符集选utf8mb4否则商品名里的生僻字会乱码。修改数据库连接配置配置文件在config/database.php改主机名、库名、账号密码。最常犯的错是端口没写对MySQL不是3306就进不去。配置伪静态规则否则路由跳转会404Nginx和Apache的规则文件项目里都带了。进入后台初始化数据填写管理员账号。默认密码一定改掉这个源码网上的流传版本很多初始密码早就不安全了。启动以后如果页面空白不要急着怀疑源码有问题先去查PHP错误日志。实测最稳定的调试方式是给入口文件加一行错误显示改完日志能看到具体的报错文件位置。注意这套系统没有内置安装向导数据库必须手动导入。有些二次开发版会加安装向导但你手里的源码若没有别浪费时间找直接手动导。3.2 增删门店的正确姿势新增一个门店不是只在门店表插一条记录还要做两件事第一初始化该门店的岗位与店员绑定关系第二给该门店的每个商品初始化门店扩展表记录库存数量默认为0。如果忘了初始化门店商品扩展表后面录单就会遇到“商品未在该门店配置”的报错。源码里其实提供了一个接口用来批量生成门店商品初始数据名字叫StoreProductInitService直接调用即可。我测试时第一次不知道这个接口手写了十几条SQL去插扩展记录结果漏了几个商品录单时才报错。删除门店就更要小心源码里做了软删除不是物理删。因为历史单据、流水都关联门店ID物理删除会让报表统计炸掉。你如果业务上确实要彻底清掉一个门店应以保证历史数据可追溯为前提逐表做归档后再清除。3.3 自定义多仓库逻辑这套源码把“门店”当成了仓库没有独立仓库维度。如果你的业务里一个门店有两个仓库比如前场零售仓和后场囤货仓就需要加一个仓库表把库存表里的门店ID替换成仓库ID再给门店和仓库建立关联。我实测过这个改造方案改动范围主要集中在库存表、库存流水表、调拨单表和报表汇总逻辑。商品扩展表价格不需要动因为价格跟着门店走不跟着仓库走。改完以后界面上的“选择门店”需要变成“先选门店再选仓库”这个前端改动工作量不大但表单校验要跟上防止漏选仓库导致库存串账。4. 关键代码逻辑与二次开发笔记4.1 事务与并发控制的示例解读源码里库存扣减方法很值得学习核心逻辑大概是这样的伪码结构public function deductStock($storeId, $productId, $quantity) { $this-db-beginTransaction(); try { $stock $this-db-table(stock) -where(store_id, $storeId) -where(product_id, $productId) -lockForUpdate() -first(); if ($stock-available $quantity) { throw new \Exception(库存不足); } $this-db-table(stock) -where(id, $stock-id) -decrement(available, $quantity); $this-db-table(stock_log)-insert([ store_id $storeId, product_id $productId, change_quantity -$quantity, type sale, created_at date(Y-m-d H:i:s), ]); $this-db-commit(); } catch (\Exception $e) { $this-db-rollBack(); throw $e; } }lockForUpdate这行是关键。同一个商品同一时间被两台收银机同时打单如果不锁行两个请求都读到库存5都扣减成4实际库存就只剩3了账面永远对不上。行锁虽然会牺牲一点性能但在门店级进销存这个并发行下安全远大于性能优化。4.2 移动加权平均成本的计算细节销售毛利能不能让人信服取决于成本核算。源码用的是移动加权平均法每次采购入库后重新计算一次平均成本。计算公式需要特别注意精度问题源码里对金额字段保留4位小数最终显示保留2位小数中间计算用4位避免每笔销售都出现一分钱误差。我在测试时故意连续入库三批不同单价的商品再分两批销售最后核对月度毛利数据完全对得上。如果你要用这套系统做财务支撑建议不要改成“月末一次加权平均”。这种核算方式会让期间内的销售成本不变月底一次调整总成本财务上确实允许但这套源码的报表逻辑是按移动加权做的硬改会涉及好几个模块的查询逻辑非常容易出错。4.3 二次开发最常改的几个点打印小票格式打印模板在视图层的打印模板目录里用原生HTML加CSS控制没有模板引擎依赖改成58mm或80mm热敏纸宽度时只需调整容器宽度和字体大小。会员储值逻辑源码里会员模块已经带了余额和积分字段二次开发要增加充值赠送只需要在充值方法里增加金额规则判断不需要动表结构。多单位换算源码商品表只有一个单位字段。如果要做“箱/瓶”这种换算建议商品表增加单位换算倍数字段销售单里按倍率换算库存扣减数量而不是单独建单位表改造量更小。审批流目前采购单和调拨单没有审核步骤如果要加建议在单据表加状态字段再写一个审核接口把原入口的审核状态校验放开这样不会影响已有报表统计。5. 实测过程中的典型问题与排查经验5.1 登录后路由404这个问题我遇到时差点以为是源码损坏。排查后发现是伪静态没配置Nginx环境少了配置文件的 include导致所有非首页的URL都走了默认文件路径。处理方式很简单确认站点配置里加载了项目自带的 nginx.conf 规则然后重启服务。另一个隐蔽原因是PHP的pathinfo模式没开。如果你用Apache且修改过php.ini里的cgi.fix_pathinfo一定要确保值是1否则URL里的参数会被错误解析一样会出现404或控制器类找不到的错误。5.2 库存数量对不上账我测试中故意做了三次操作采购入库10件、销售出库3件、调拨出库2件理论上总库存应减少5件。结果报表里显示的总库存只少了2件调拨的数量没从调出门店库存中扣掉。问题出在调拨单的确认环节。源码里调拨单有草稿和确认两个状态我直接在草稿状态看到库存减了但实际后台脚本只处理“确认”状态的数据。这个设计是为了防止调拨单填到一半就把库存锁死但如果前端没有明显标识使用者很容易误以为草稿已经生效。处理方式是调整前端提交按钮把调拨单提交后的跳转逻辑直接指向确认动作并在页面上写明“草稿不扣库存”。5.3 月度报表的期初数不准我导入测试数据时用的方式是把SQL文件直接灌进去但库存快照表里没有对应的历史快照记录。结果月报表的期初库存全是0当月入库只显示新增期初数对不上。解决起来也不难进入后台的“库存快照生成”功能选择导入数据的前一天作为快照日期系统会按当前库存表自动生成快照。这个骚操作在真实运维里也常用比如临时接手一套旧系统导入完历史单据后必须手动重建快照否则报表会从0开始算。5.4 商品条码重复导致录单选错货后台在录入商品时没有做条码唯一校验测试时我故意录入了两条条码相同的商品前台收银按条码检索时返回了最早的一条但后台单据选择的却是另一条两边对不上。建议在商品表给条码字段加唯一索引这条SQL很简单但能规避大量因为条码重复引起的错发货、错记账问题。如果你已经有脏数据先通过分组查询找到重复条码手动合并商品后再加索引。提示任何进销存系统上线前先做一次条码数据清洗。店面里扫错条码产生的账实差异事后追查要比预防代价高得多。6. 这套源码的安全隐患与加固建议这套系统毕竟是开源流传的能下载到的版本攻击者也同样能拿到。我测试时检查了几个高危点默认管理员账号没改、没有登录失败次数限制、部分SQL语句有拼接痕迹。本地学习无所谓但如果你要拿到真实门店环境用上线前至少做四件事改后台路径、换强密码、加操作日志、定期备份数据库。改后台路径是最低成本的安全措施。把后端入口文件名改成一个无规律字符串同时修改前端登录接口指向等于把大门从明处挪到暗处能挡掉绝大多数批量扫描脚本。操作日志很容易被人忽略。这套源码本身带了登录日志功能但没有记录用户新增、修改商品、导出采购单等敏感操作。生产环境下建议扩展一个操作日志表记录操作人、操作时间、请求路由和关键参数这也是以后排查“谁改错价格”“谁删了商品”的唯一线索。数据库备份我用的是系统计划任务每天凌晨执行 mysqldump保留最近7天另外单独用脚本把SQL文件压缩后传到另一台服务器。这样的好处是你不用担心服务器硬盘整个坏了备份文件也一起丢失。本地测试时可以不考虑但真实门店数据丢一天对账和排班都会受影响。7. 个人体会与后续扩展建议这套源码我前后跑了将近两周最大的感受是“结构规矩比功能花哨重要”。功能不够可以加但如果从一开始表设计就混乱后面每加一个小功能都会牵扯到无数地方。这个项目在数据库设计上留的余地很够特别是库存流水和商品扩展表这两个设计直接决定了我后面加促销模块、加多仓库模块时几乎没动老代码。如果你准备拿这套系统做毕业设计或者接客户项目我建议优先做三件事第一把界面语言统一改成你要用的语言因为部分版本混有中英文常量第二重写前端的商品选择弹窗源码自带的那个在商品总数超过5000条时会明显卡顿第三给销售单增加删除原因备注字段进销存系统的单据是不建议物理删除的但业务员认错账时总想删单这时候用“作废并备注”比让程序员写SQL恢复数据靠谱得多。多店进销存这条路一旦跑起来就是一个持续打磨的过程。库存逻辑本身不难真正难的是权限边界怎么划、单据状态怎么管、报表口径怎么统一。这套源码给了你一个能跑的起点剩下的业务细节还是要根据你手里的真实门店流程慢慢调。我先写到这里后面如果有时间会再整理一套前端改造的对照笔记包括收银页面重构和移动端适配的部分。