简介:这是一套专为小型工厂定制的轻量级ERP系统源码,面向Java初/中级开发者及中小型制造企业IT人员,解决传统工厂在销售、采购、生产、库存等环节数据割裂、流程低效的问题。资源包仅5KB,含3个核心文件:index.html为前端入口页,.gitignore规范版本控制行为,.inscode可能包含IDE配置或代码检查规则,整体结构简洁,便于快速部署与二次开发。目前已有145人学习下载,适合希望掌握SpringBoot+Vue全栈ERP开发范式的学习者。读者可直接获取完整技术架构实现(后端SpringBoot+MySQL、前端Vue+ElementUI+UniAPP)、四大业务模块(CRM/OA/进销存/财务)的集成逻辑,以及标准化接口设计与跨端适配思路,是理解制造业数字化管理落地的典型教学级项目参考。
1. 小型工厂ERP系统源码:不是“拿来即用”的玩具,而是要亲手拧紧每颗螺丝的生产指挥台
你搜到“小型工厂ERP系统源码”,大概率正被三件事压着喘不过气:车间报工靠微信截图、采购入库靠Excel手填、老板突然要查上月某型号产品的总工时和毛利——而你翻遍表格,发现BOM版本对不上、工单状态没闭环、仓库库存数和财务应付账款差了两万八。这不是管理问题,是信息断点在吃掉利润。这套源码不是给你装个软件就完事的“开箱即用”产品,它是一套可拆解、可焊接、可校准的轻量级制造执行中枢,专为50人以内、产线不超过3条、SKU在2000以内的中小型离散制造场景设计。它不追求SAP式的全模块覆盖,但把订单→BOM→工单→报工→入库→成本归集这条主干链做成了可追溯、可回滚、可审计的闭环。适合两类人:一是懂点Python/PHP+MySQL的产线IT支持工程师,能自己调参数、改字段、接扫码枪;二是有制造业经验的创业者,愿意花两周时间带着班组长一起跑通首单全流程。别指望它自动解决人员执行力,但它会把“谁、在什么时间、干了什么事、用了什么料、耗了多少工时”全部钉死在数据库里——这才是小型工厂真正需要的“确定性”。
2. 搭建前必做的三件事:环境裁剪、数据结构校准、权限沙盘推演
2.1 环境部署:为什么必须用PHP 7.4 + MySQL 5.7而非最新版?
这套源码的底层逻辑建立在MySQL的ENUM类型强约束和PHP 7.4的mysqli_real_escape_string安全过滤机制上。我曾试过直接升到PHP 8.2,结果所有BOM展开页面报Fatal error: Uncaught TypeError: mysqli::real_escape_string(): Argument #1 ($string) must be of type string, null given——因为PHP 8.1起废弃了该函数对null的隐式转换,而源码中某处工单状态判断未做空值兜底。正确做法是:
# Ubuntu 20.04 LTS环境下(推荐,避免兼容性黑匣子) sudo apt update && sudo apt install -y php7.4 php7.4-mysql php7.4-curl php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip mysql-server-5.7 # 启动后立即修改MySQL配置(关键!否则中文乱码+日期函数报错) sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf # 在[mysqld]段落末尾添加: character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci default-time-zone = '+08:00' # 重启服务 sudo systemctl restart mysql提示:不要用Docker一键拉取“最新版LAMP镜像”。源码中
/app/config/database.php硬编码了'charset' => 'utf8',若MySQL实际用utf8mb4而PHP连接层仍发utf8指令,会导致BOM表中带emoji的物料描述存入时截断,后续展开BOM直接报错。必须手动确认SHOW VARIABLES LIKE 'character_set%';返回全为utf8mb4。
2.2 数据结构校准:从“标准字段”到“你的车间语言”的翻译表
源码自带的m_product(产品表)有unit(单位)字段,但默认只预置了PCS(件)、SET(套)。而你的冲压车间实际用TON(吨)计原材料,线束车间用ROLL(卷)计线材。强行改代码会破坏升级路径,正确做法是扩展枚举值并同步更新前端下拉:
-- 进入MySQL执行(假设数据库名erp_dev) USE erp_dev; ALTER TABLE m_product MODIFY COLUMN unit ENUM('PCS','SET','TON','ROLL','METER','KG') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 刷新缓存(部分版本需) FLUSH PRIVILEGES;接着修改前端模板文件/public/views/product/create.blade.php(Laravel风格)或/templates/product_form.html(原生PHP)中对应下拉框:
<!-- 找到原下拉框,替换为 --> <select name="unit" class="form-control"> <option value="PCS">件</option> <option value="SET">套</option> <option value="TON">吨</option> <option value="ROLL">卷</option> <option value="METER">米</option> <option value="KG">千克</option> </select>参数说明:
ENUM类型在此处不是偷懒,而是用数据库级约束防止脏数据。比如采购员误选BOX(箱)单位,但系统里根本没有这个枚举值,插入直接失败——这比让错误数据进库再花三天排查溯源强十倍。别怕改表结构,小型工厂数据量小,ALTER TABLE秒级完成。
2.3 权限沙盘推演:用一张表定义谁能看到什么、能改什么
源码权限模型基于RBAC(角色-权限-资源),但默认角色只有admin和user。你需要按真实组织切分:计划员不能删工单,仓管员不能改BOM,质检员只能看到自己班组的报工记录。核心是修改m_role_permission表(权限映射表)和m_user_role表(用户-角色绑定):
| 角色ID | 角色名 | 可访问模块 | 可操作动作 | 数据范围限制 |
|---|---|---|---|---|
| 101 | 计划员 | 工单管理、BOM管理 | 创建/编辑工单、查看BOM | 仅限status='planning'的工单 |
| 102 | 仓管员 | 入库管理、库存查询 | 创建入库单、调整库存 | 仅限warehouse_id=3(北仓) |
| 103 | 质检员 | 报工管理、检验记录 | 提交检验结果、驳回报工 | 仅限team_id IN (5,6)(A/B线班组) |
执行SQL初始化角色(示例):
-- 插入新角色 INSERT INTO m_role (id, name, description) VALUES (101, '计划员', '负责工单排程与BOM维护'), (102, '仓管员', '负责北仓物料收发'), (103, '质检员', '负责A/B线产品检验'); -- 绑定权限(假设工单列表页路由为'/workorder/index',编辑为'/workorder/edit') INSERT INTO m_role_permission (role_id, route, action) VALUES (101, '/workorder/index', 'view'), (101, '/workorder/edit', 'edit'), (101, '/bom/index', 'view'), (102, '/inventory/inbound', 'create'), (102, '/inventory/stock', 'view'), (103, '/reporting/submit', 'create');血泪经验:千万别跳过这步直接给所有人
admin权限!某次上线后仓管员误点了“删除历史工单”,因无数据范围限制,连带清空了三个月的报工记录。后来我们加了硬规则:所有DELETE操作必须携带WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY),且前端按钮需二次弹窗输入当日生产编号才放行。
3. 核心流程跑通:从接单到入库的最小闭环实操
3.1 创建客户与产品:用两条SQL搞定基础数据筑基
很多新手卡在第一步——系统提示“客户不存在”或“物料编码重复”。根源在于源码强制要求客户、产品、仓库三者先存在才能创建销售订单。最简路径是绕过前端表单,直连数据库注入:
-- 1. 创建客户(注意:customer_code必须唯一,建议用公司简称+年份) INSERT INTO m_customer (customer_code, customer_name, contact_person, phone, address) VALUES ('CUST2024A', 'XX机电设备有限公司', '张经理', '13800138000', 'XX市XX区工业路88号'); -- 2. 创建产品(product_code即ERP内部编码,务必与车间看板一致) INSERT INTO m_product (product_code, product_name, unit, spec, category) VALUES ('P-001-MOTOR', 'Y系列三相异步电机', 'PCS', '0.75KW/380V/50Hz', '电机');逻辑说明:
m_customer表的customer_code是销售订单主键so_no的前缀来源(如SO-CUST2024A-2024001),m_product的product_code则直接用于BOM子项关联。这两张表是整个业务流的“根节点”,必须先于任何单据存在。别信前端“快速录入”按钮——它常因JS校验缺失导致空字段插入失败,而SQL命令一次到位。
3.2 录入销售订单:避开“税率自动填充”陷阱的三步法
销售订单(SO)创建界面看似简单,但有个隐藏坑:当选择客户后,系统会根据m_customer.tax_rate字段自动填充订单税率。而源码默认所有客户tax_rate=0,导致财务导出销项税为0。正确操作:
先更新客户税率(进入MySQL):
UPDATE m_customer SET tax_rate = 13.0 WHERE customer_code = 'CUST2024A';创建订单时手动覆盖:在SO表单中,即使税率已显示13%,仍需点击税率输入框,再按回车——触发JavaScript重算含税价逻辑(源码中
calculateTotal()函数依赖blur事件)。检查BOM关联:订单保存后,立即访问
/so/bom?so_id=123(假设刚建的订单ID为123),确认系统是否自动展开BOM。若为空,说明m_product中该产品is_bom_parent=0(非父项),需执行:UPDATE m_product SET is_bom_parent = 1 WHERE product_code = 'P-001-MOTOR';
参数说明:
is_bom_parent是布尔开关,1表示此产品可作为BOM顶层(成品),0表示仅为子项(如螺丝、轴承)。源码逻辑是:只有is_bom_parent=1的产品才能被选入销售订单,且其BOM树才会被自动加载。漏设此项,订单看似创建成功,实则无法驱动后续工单生成。
3.3 生成工单与报工:让车间扫码枪真正“听懂人话”
工单(WO)是连接计划与执行的关键。源码提供两种生成方式:手动创建(适合试产单)和自动展开(适合批量单)。重点说自动展开:
# 假设销售订单ID为123,执行后台命令(Linux服务器) php artisan wo:generate --so_id=123 --plan_date="2024-06-15" # 或Windows下 php artisan.bat wo:generate --so_id=123 --plan_date="2024-06-15"该命令会:
- 读取SO中产品
P-001-MOTOR的BOM(查m_bom_header+m_bom_item表) - 按需求数量反算子项用量(如1台电机需2个端盖)
- 创建工单主表
m_work_order及明细m_wo_item - 自动分配至
line_id=2(假设BOM中指定产线为2号装配线)
报工环节,车间用扫码枪扫工单号(如WO-20240615-001),调用API:
curl -X POST http://your-erp.local/api/reporting/scan \ -H "Content-Type: application/json" \ -d '{"wo_code":"WO-20240615-001","operator_id":105,"qty_good":5,"qty_ng":0}'避坑关键:扫码报工必须传
operator_id(操作员工号),而非姓名。源码用此ID关联m_employee表获取班组、技能等级,用于后续产能分析。若传姓名,API返回{"error":"operator not found"}——因为数据库查的是数字ID字段。
4. 避坑指南:上线前必须验证的5个致命断点
4.1 现象:BOM展开后子项数量为0,但数据库m_bom_item里明明有数据
原因:m_bom_header表中status字段值为draft(草稿),源码逻辑只读取status='active'的BOM版本。
解决:执行UPDATE m_bom_header SET status='active' WHERE product_code='P-001-MOTOR';,并确认valid_from日期早于当前日期。
4.2 现象:采购入库单保存后,库存查询页面数量不变
原因:入库单状态未设为confirmed(已确认)。源码中库存变动仅在m_purchase_inbound.status='confirmed'时触发update_stock()事务。
解决:在入库单列表页,找到刚建的单据,点击“确认”按钮(非“保存”);或直接SQL:UPDATE m_purchase_inbound SET status='confirmed' WHERE id=456;
4.3 现象:导出Excel报表时中文全变成问号(????)
原因:PHPfputcsv()函数未指定BOM头,Excel默认用ANSI编码打开UTF-8文件。
解决:修改导出函数(如/app/Exports/StockExport.php),在__construct()中添加:
$this->headers = ["\xEF\xBB\xBF" . '物料编码', '物料名称', '库存数量']; // UTF-8 BOM头4.4 现象:修改密码后无法登录,提示“密码错误”
原因:源码密码加密使用password_hash($pwd, PASSWORD_DEFAULT),但部分旧版PHP 7.4环境PASSWORD_DEFAULT指向bcrypt,而数据库里存的是argon2i哈希(因之前升级过PHP)。
解决:统一降级为bcrypt,修改登录验证逻辑:
// 在AuthController.php的login方法中 if (password_verify($request->password, $user->password)) { // 原逻辑 } else { // 兼容旧哈希:尝试用bcrypt重新hash比对 if (password_needs_rehash($user->password, PASSWORD_BCRYPT)) { $rehash = password_hash($request->password, PASSWORD_BCRYPT); if (hash_equals($user->password, $rehash)) { // 更新数据库密码为bcrypt格式 $user->password = $rehash; $user->save(); } } }4.5 现象:手机浏览器访问报工页,扫码后页面白屏
原因:前端JS调用navigator.mediaDevices.getUserMedia()请求摄像头权限,但HTTP协议下Chrome禁用此API(必须HTTPS)。
解决:开发阶段用http://localhost可绕过;生产环境必须配Nginx反向代理+Let's Encrypt证书。临时方案:在/public/js/reporting.js中注释掉扫码逻辑,改用手工输入工单号。
5. 成本核算与数据看板:用三条SQL榨干源码里的利润线索
5.1 动态计算单台产品实际成本:不依赖财务模块的硬核公式
源码未内置成本模块,但所有数据都在库里。以电机P-001-MOTOR为例,单台实际成本=材料费+人工费+制造费用。材料费可直接聚合BOM,人工和制造费需关联报工:
-- 执行此SQL(替换日期范围) SELECT p.product_code, p.product_name, ROUND(SUM(bi.qty * pm.price),2) AS material_cost, ROUND(SUM(r.qty_good * e.hourly_rate * 0.5),2) AS labor_cost, -- 假设每台耗0.5工时 ROUND(SUM(r.qty_good * 12.8),2) AS overhead_cost -- 制造费用按12.8元/台摊销 FROM m_product p JOIN m_bom_header bh ON p.id = bh.product_id JOIN m_bom_item bi ON bh.id = bi.bom_header_id JOIN m_product pm ON bi.component_id = pm.id JOIN m_work_order wo ON p.id = wo.product_id JOIN m_reporting r ON wo.id = r.wo_id JOIN m_employee e ON r.operator_id = e.id WHERE p.product_code = 'P-001-MOTOR' AND wo.plan_date BETWEEN '2024-06-01' AND '2024-06-30' GROUP BY p.product_code, p.product_name;参数说明:
e.hourly_rate需提前在m_employee表中维护;12.8是示例制造费率,实际应按月度总制造费用/当月总合格产量计算。此SQL可嵌入自定义报表页,让班组长实时看到“干一台赚多少”。
5.2 构建产线OEE看板:用视图固化关键指标
OEE(设备综合效率)= 时间开动率 × 性能开动率 × 合格品率。源码无现成看板,但可用MySQL视图聚合:
CREATE VIEW v_oee_daily AS SELECT DATE(r.created_at) as report_date, l.line_name, ROUND(AVG(CASE WHEN r.status='completed' THEN 1 ELSE 0 END),3) as time_availability, ROUND(AVG(r.speed_ratio),3) as performance_rate, ROUND(AVG(r.yield_rate),3) as quality_rate, ROUND(AVG(CASE WHEN r.status='completed' THEN 1 ELSE 0 END) * AVG(r.speed_ratio) * AVG(r.yield_rate),3) as oee_score FROM m_reporting r JOIN m_work_order wo ON r.wo_id = wo.id JOIN m_line l ON wo.line_id = l.id GROUP BY DATE(r.created_at), l.line_name;然后在前端页面调用SELECT * FROM v_oee_daily WHERE report_date = CURDATE();,即可展示今日各线OEE。
技巧:
speed_ratio(速度比率)和yield_rate(合格率)字段需在报工时由班组长手动填写,这是倒逼数据质量的关键设计——不填满这两个数,报工单无法提交。
5.3 防止数据污染:给关键表加“后悔药”触发器
曾因误操作清空m_wo_item(工单子项表),导致300个工单BOM丢失。现在所有核心表都加了BEFORE DELETE触发器,自动备份到历史表:
-- 创建备份表 CREATE TABLE m_wo_item_history LIKE m_wo_item; ALTER TABLE m_wo_item_history ADD COLUMN deleted_at DATETIME; -- 创建触发器 DELIMITER $$ CREATE TRIGGER wo_item_before_delete BEFORE DELETE ON m_wo_item FOR EACH ROW BEGIN INSERT INTO m_wo_item_history SELECT OLD.*, NOW(); END$$ DELIMITER ;教训:上线前,我花了整整一天给
m_work_order、m_bom_item、m_reporting三张表加了同类触发器。不是信不过自己,是信不过那个凌晨三点改完代码、脑子发懵的自己。现在每次DELETE前,触发器会默默存一份快照,SELECT * FROM m_wo_item_history ORDER BY deleted_at DESC LIMIT 10;就能找回刚删的数据。希望帮到你。
本文还有配套的精品资源,点击获取