简介:一套面向制造型企业的生产管理系统源代码,覆盖生产计划、物料需求、库存管理、进度跟踪、质量控制、订单和报表等核心业务,适合需要实现车间信息化、开展二次开发或学习传统ASP开发流程的技术人员参考。资源包共165个文件,约1.33MB;其中97个ASP文件承载主要业务逻辑,配合JavaScript、CSS完成前端交互,另有GIF/JPG图片、DB/MDB数据库、EXE程序及说明文档,整体结构清晰,便于按功能模块查找。目前已有2002人学习浏览。通过源码可了解MRP物料需求计算、库存出入库与预警、质检与不良品处理、订单状态流转等典型模块的实现思路;数据库文件和可执行程序有助于在本地搭建演示环境,快速验证并修改功能点。此外,还可从页面布局、权限控制、数据表关系等维度学习传统ASP项目的分层组织方式,为后续技术改造或功能扩展提供参考。
1. 生产管理系统源代码:有代码不等于能上线,先想清楚车间怎么说话
经营一个几十人规模的机加工厂,最烧钱的往往不是设备,而是每天下班前统计“今天到底干完了哪几张工单”。生产管理系统源代码这个词,听起来像是一套现成的软件,可真正要解决的是:把车间里那张一直被手写、被拍照、被口头传递的派工单,变成一台机器能读懂的流程。很多开发者拿到代码包,跑通登录页就以为上线了,结果用起来发现系统里的库存数和车间实物永远对不上。这篇笔记想聊的是,拿到或准备做生产管理系统源代码之前,先想明白业务在数据库里长什么样,再谈表结构和接口。适合两类人:一类是给中小工厂做信息化改造的开发,另一类是工厂内部想自研的技术负责人。不建议一上来追求大而全,先把订单、物料、报工这条主链走通,够用就好。
2. 先拆业务再碰代码:生产管理系统源代码的领域模型与数据库设计
2.1 把车间语言翻成表:物料、BOM、工序与工作中心
做生产管理系统源代码之前,我习惯先问车间主任三个问题:物料档案谁维护?换料时流程怎么走?报工是个人报还是班组报?这三个问题的答案直接决定数据库表怎么设计。最常见的做法是先建十张核心表:物料、物料分类、BOM、工序、工艺路线、工作中心、工单、工单工序、报工记录、库存。见过不少翻车例子,上来直接写业务代码,等到要算生产成本时发现没有工作中心表,又回头补数据,还不如一开始就规划进去。
先给一张物料表的基础建表语句:
CREATE TABLE material ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, material_code VARCHAR(32) NOT NULL COMMENT '物料编码', material_name VARCHAR(128) NOT NULL COMMENT '物料名称', spec VARCHAR(64) DEFAULT NULL COMMENT '规格型号', uom VARCHAR(16) NOT NULL DEFAULT 'PCS' COMMENT '单位', category_id BIGINT NULL COMMENT '物料分类ID', is_purchased TINYINT NOT NULL DEFAULT 0 COMMENT '1=外购 0=自制', default_warehouse VARCHAR(16) DEFAULT 'WH01' COMMENT '默认仓库', status TINYINT NOT NULL DEFAULT 1 COMMENT '1启用 0停用', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_material_code (material_code), KEY idx_category (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物料主数据';逻辑说明:material_code 加唯一索引,编码一旦发布不允许在系统里改,这是行业惯例,改编码比删表重灌还麻烦。uom 默认 PCS,但对涂料、线缆这种按公斤、按米计量的物料,单位不一致会让后面库存和成本聚合非常痛苦,我一般宁可用两天时间统一计量单位,也不要留二十种单位到报表阶段再换算。is_purchased 和 default_warehouse 是给后续采购和领料逻辑留的钩子,外购物料不需要工艺路线,自制件才需要展开 BOM。category_id 不要建字符串目录树,最可靠的做法是单独建 category 表,用 parent_id 自关联形成树,查询和权限控制都方便。
2.2 工单与报工:定义生产管理系统源代码的业务主链
生产管理系统源代码的核心主链是“销售订单 → 生产工单 → 工序报工 → 成品入库”。工单是车间调度和执行的最小单位,报工是现场反馈到系统里的关键动作。工单表至少要包含:工单号、物料、计划数量、计划开始结束时间、优先级、状态、来源订单号。报工表记录每道工序的实际开始时间、结束时间、合格数量、不合格数量、操作人、工作中心。
工单状态流转我一般这样设计:
CREATE TABLE work_order ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, wo_no VARCHAR(32) NOT NULL COMMENT '工单号', material_id BIGINT NOT NULL COMMENT '物料ID', plan_qty DECIMAL(12,3) NOT NULL COMMENT '计划数量', completed_qty DECIMAL(12,3) NOT NULL DEFAULT 0 COMMENT '累计完工数量', status VARCHAR(16) NOT NULL DEFAULT 'CREATED' COMMENT 'CREATED=已创建 RELEASED=已下达 IN_PROGRESS=生产中 COMPLETED=已完工 CANCELLED=已取消', sales_order_no VARCHAR(32) DEFAULT NULL COMMENT '来源销售订单号', due_date DATE DEFAULT NULL COMMENT '交期', priority TINYINT NOT NULL DEFAULT 5 COMMENT '数字越大优先级越高', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_wo_no (wo_no), KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产工单';逻辑说明:工单状态用字符串比数字可读性好,前后端联调时不会出现“3 到底代表什么”的歧义。uk_wo_no 唯一键必须加,哪怕开发阶段用不到,上线后任何外部系统对接都要靠工单号做关联,重复工单号是灾难。completed_qty 保存累计值,不要用计划数量减已报工数量来算剩余,因为报工可能回退、质检可能判废,减法容易把账算乱。priority 用 tinyint 默认 5,范围 0 到 9 足够,不要设计成“高/中/低”字符串,排序和筛选都不好写。
报工表要单独建,因为一道工序可能被多次报工,比如上午干 200 件、下午干 150 件,不能覆盖式更新。报工记录里带上实际工时,后续算工时工资和成本都有依据。最开始我没加工序字段,后来统计各工序产能时只能靠报工备注反推,非常痛苦。
2.3 预留扩展位:批次、序列号与追溯字段设计的两个原则
做生产管理系统源代码,第一版可能不需要批次追溯,但表设计时最好留好位置。很多行业,尤其是汽配和电子装配,客户会要求“我这批货是哪张工单、哪个批次、用了哪一批次原材料”都能查到。虽然初版用不上,但字段可以先加:
ALTER TABLE work_order ADD COLUMN batch_no VARCHAR(32) NULL COMMENT '批次号', ADD COLUMN serial_track_flag TINYINT NOT NULL DEFAULT 0 COMMENT '1=工序级序列号追踪';两个原则必须坚持。第一,编号字段预留两倍长度。比如现在工单号规则是 WO2024001,觉得 20 位够了,但将来可能要加工厂代码、产品线代码,宁可 VARCHAR(32) 也不要 VARCHAR(16)。第二,时间字段统一用 DATETIME 且全部存服务器本地时间,不要一部分存 UTC、一部分存北京时间,到对账时坑到怀疑人生。另外,所有表都加 status 字段做软删除,生产数据不能 DELETE,一旦删了,工单、报工、库存之间的追溯链就断了,这条血泪经验后面还会提。
3. 核心闭环实现:从销售订单到成品入库的代码怎么写
3.1 最小可运行技术栈与工程骨架
第一个问题往往是“用什么写”。做中小工厂的生产管理系统源代码,我常用的方案是 Flask + PostgreSQL,或者 Spring Boot + MySQL,看团队熟悉什么。第一版强烈建议单体应用,不要一上来就拆微服务,理由放在后面避坑章讲。下面以 Python Flask 为例,工程骨架一般是这样:
production-mgr/ ├── app.py ├── config.py ├── models.py ├── schemas.py ├── services/ │ ├── order_service.py │ ├── bom_service.py │ ├── work_order_service.py │ └── inventory_service.py ├── api/ │ ├── work_order_api.py │ └── report_api.py ├── db.py ├── requirements.txt └── migrations/逻辑说明:services 目录放业务逻辑,api 目录只放 HTTP 路由和参数校验,这种方式在单体阶段花不了多少时间,后续要拆服务时边界已经画好了。models.py 放 SQLAlchemy 模型,直接映射第 2 章设计的表。很多开发者习惯把 SQL 写在业务文件里,短期快,但 BOM 展开、工单状态流转这种逻辑一复杂,代码很快就变成一坨。迁移脚本用 Alembic 管理,改表结构不要手工在数据库里执行 ALTER,团队里两个人都能对上迁移记录。
3.2 创建工单与拆分工序:把 BOM 和多工序跑通
现在实现最关键的动作:销售订单转工单。业务规则是:订单下发的物料如果是自制件,读取 BOM 表展开,生成一张工单,同时按工艺路线生成多道工单工序。请求进来先校验物料类型,再锁住 BOM 行避免并发修改,最后在事务里创建工单和工序。
def create_work_orders_from_sales_order(db_session, sales_order_no): order = get_order_or_404(db_session, sales_order_no) bom_items = get_bom_flat(db_session, order.material_id) if not bom_items: raise BusinessError('该物料没有维护BOM,无法下达生产') try: with db_session.begin(): wo = WorkOrder( wo_no=generate_work_order_no(), material_id=order.material_id, plan_qty=order.quantity, status='CREATED', sales_order_no=sales_order_no, due_date=order.due_date, ) db_session.add(wo) db_session.flush() for step_no, bom in enumerate(bom_items, start=1): wos = WorkOrderStep( work_order_id=wo.id, step_no=step_no, process_code=bom.process_code, workcenter_id=bom.workcenter_id, plan_qty=order.quantity, status='PENDING' ) db_session.add(wos) return wo.id except IntegrityError: db_session.rollback() raise BusinessError('工单创建失败,请检查物料编码和BOM数据')参数说明:db_session.begin() 开启事务,事务里任何一步失败都会回滚,不会出现“工单建好了但工序没生成”的脏数据。flush() 是为了拿到 wo.id 作为外键,如果不 flush,下面 WorkOrderStep 的 work_order_id 还是 None。get_bom_flat 的作用是把多层 BOM 递归压平成工序列表,这一步是生产管理系统源代码最容易翻车的地方,递归深度超过 5 层时一定要做缓存或者改成迭代展开。
3.3 报工与库存联动:数字不能和账面脱节
报工是现场生产管理系统源代码每天使用频次最高的操作。报工不仅要更新工单工序的完成数量,还要同时处理库存:半成品工序完工后转入下一工序的在制品暂存库,最后一道工序完工后成品入成品库。这两个动作必须在同一个数据库事务里完成,否则就会出现“工单显示完工了,但仓库没收到货”的经典问题。
def report_completion(db_session, wo_no, step_no, qty, operator): wo = db_session.query(WorkOrder).filter_by(wo_no=wo_no).with_for_update().first() if not wo: raise BusinessError('工单不存在') if wo.status == 'COMPLETED': raise BusinessError('工单已完工,禁止补报工') step = db_session.query(WorkOrderStep).filter_by(work_order_id=wo.id, step_no=step_no) step = step.with_for_update().first() try: with db_session.begin(): report = WorkReport( work_order_id=wo.id, step_no=step_no, qty=qty, operator=operator, report_time=datetime.now() ) db_session.add(report) step.completed_qty += qty if step.completed_qty >= step.plan_qty: step.status = 'DONE' wo.completed_qty += qty if wo.completed_qty >= wo.plan_qty: wo.status = 'COMPLETED' inventory = get_inventory_record(db_session, wo.material_id) inventory.available_qty += qty except IntegrityError: db_session.rollback() raise BusinessError('报工失败,库存更新冲突,请重试')逻辑说明:with_for_update() 是行级锁,防止两个工人同时对同一张工单报工导致数量叠加出错。报工成功后同时增加工单累计完成量、工序完成量和成品库存,三处数据在一个事务里提交,逻辑上才一致。参数 qty 必须大于 0,且在接口层做一次校验,数据库层要不就用 CHECK 约束,不然业务层漏了,脏数据直接进库。这里有个细节:成品入库放在报工接口里对简单工厂够用,但如果有独立质检环节,应该加一个“质检通过后才入可用库存”的状态,否则不良品直接进可用库存,发货时才发现数量不对。
4. 把源代码跑起来:初始化、权限与第一个测试数据
4.1 数据库初始化与种子数据:一张最简物料清单从哪来
代码拿到手第一件事不是跑接口,而是初始化数据库。生产管理系统源代码和普通 Web 系统不同,没有任何主数据时连工单都建不了。最小主数据集合是:三个用户(管理员、计划员、操作工)、两张物料(一件外购件、一件自制件)、一张 BOM、一台工作中心。初始化脚本这样写:
export DATABASE_URL=postgresql://scm_user:scm_pass@localhost:5432/production_mgr flask db upgrade python seed_demo.pydef seed(): db.session.add(Material(material_code='M-0001', material_name='铝型材6063', uom='KG', is_purchased=True)) db.session.add(Material(material_code='M-0002', material_name='加工面板', uom='PCS', is_purchased=False)) db.session.add(Process(process_code='CUT', process_name='切割')) db.session.add(Process(process_code='CNC', process_name='CNC加工')) db.session.add(Workcenter(code='WC-01', name='1号加工中心')) db.session.commit()参数说明:种子数据的物料编码要有规则感,M 开头代表物料,后面是流水号,不要混用中文。Process 和 Workcenter 必须建,工单工序依赖它们。有人会问为什么不用 CSV 导入,第一版种子数据量小,代码直接写清楚比 Excel 导入多一道看不见的转码风险。初始化完成后验证一下:登录系统能看到两张物料,且 M-0002 能查到底层 BOM。
4.2 权限模型:操作工、计划员、管理员三类角色的最小实现
中小工厂不需要复杂的权限体系,三类角色足够。操作工只能报工和查看自己的报工记录;计划员能建订单、开工单、查库存;管理员有全部权限。用一个装饰器实现是最简单可靠的方式:
def role_required(*roles): def wrapper(fn): def decorator(*args, **kwargs): if not current_user: abort(401) if current_user.role not in roles: abort(403, description='没有操作权限') return fn(*args, **kwargs) return decorator return wrapper @app.route('/api/report', methods=['POST']) @role_required('operator', 'planner', 'admin') def report_api(): pass @app.route('/api/work-order', methods=['POST']) @role_required('planner', 'admin') def create_wo(): pass逻辑说明:生产管理系统源代码里权限最好不要自己造轮子,但角色也不要设计得太细。每多一个角色,意味着多一套菜单配置和接口校验。第一版把“谁能报工、谁能开工单”守住就行。这个装饰器写法里有个细小但实用的点:abort(401) 和 abort(403) 分开处理,前端才能区分“没登录”和“没权限”,不然全部返回 401,操作工看到报错也不知道是登录掉了还是权限不够。
4.3 本地联调最小步骤:要验证的五个关键动作
数据库初始化完,按顺序验证五个动作,任何一个出问题都先停下排查,不要往下走。
| 顺序 | 验证动作 | 预期结果 |
|---|---|---|
| 1 | 管理员登录 | 能看到主菜单,接口返回 200 |
| 2 | 创建自制物料及 BOM | 物料列表出现 M-0002,BOM 有两条子项 |
| 3 | 计划员创建销售订单 | 订单号生成,状态为已审核 |
| 4 | 下达工单并报工 | 工单状态从 CREATED 变 IN_PROGRESS,报工后库存增加 |
| 5 | 操作工越权访问采购菜单 | 返回 403,前端提示“没有操作权限” |
这五个动作覆盖生产管理系统源代码的完整主链:主数据、订单、工单、报工、权限。第 4 步如果报工后库存没变化,优先检查事务是否提交、库存初始化记录是否存在。很多项目数据库里没有预置库存为 0 的初始记录,报工成功但 inventory 行不存在导致外键报错。排查时看两个表:work_report 有没有新记录,work_order.completed_qty 有没有增加。最怕的是只更新了一个表,另一个没更新,这就是明显的事务边界没划对。
5. 生产管理系统源代码落地避坑:五条血泪经验
5.1 坑一:物料编码没定好就写代码,翻车只能从头改
现象:项目做到第二个月,物料表里出现“螺丝002”“螺丝 3”“不锈钢螺丝”三种写法,仓库人员各用各的,系统里筛选物料时一屏都放不下。原因是上线前没有定编码规则,开发阶段图省事直接用了 Excel 里的中文名。解决方式是重做物料编码:类别前缀 + 流水号,导入前写一次校验脚本,凡是编码重复、中文名带空格的直接拒收。这个坑最伤的地方不在编码本身,而在于所有工单、库存、报工都已经引用了旧的物料 ID,改编码等于全表更新外键。所以一开始那两天的时间,一定要花在编码规范上。
5.2 坑二:报工数据双写导致账面混乱,事务边界没划清
现象:车间反馈“系统里库存明明显示 500 件,实物盘点是 480 件”,查报工记录发现有一条报工 40 件的数据,工单数量加了,但库存扣减的代码在另一个接口里,那个接口当天因为参数校验失败被前端吞掉了异常。原因就是发版时把报工和库存更新拆成了两个事务,中间没有做补偿机制。解决方式:报工、工单累计、库存更新强制放同一个数据库事务里,再在应用入口跑一次单元测试,模拟提交成功后库存必须同步。这类问题在开发环境很难复现,因为只有并发或者异常路径才会触发,但上线第一个月就一定会出现。
5.3 坑三:源数据脏到跑不动,Excel 的 BOM 不是真 BOM
现象:导入 BOM 后,工单展开时发现子件数量有的写“1/2”,有的写“0.5 公斤”,有的干脆空着,最后生产计划一算,严重缺料。原因是工厂原有的 BOM 是从设计图纸导出后人工粘到 Excel 里的,合并单元格和备注混在一起。生产管理系统源代码的 BOM 导入不能直接照搬 Excel 原表,必须经过清洗转换。解决方式是做一个导入校验页,先加载 Excel 预览,标出不合格行,人工修正后再正式导入,同时数量字段统一用 DECIMAL 存储。千万别让用户直接粘贴到网页表格里提交,中间转码很容易丢失小数点精度。
5.4 坑四:开发环境能跑、车间用不了,浏览器和扫码枪全是坑
现象:系统做完了,演示环境用的是 Chrome 最新版,一切正常。到了车间现场发现扫码枪把二维码内容扫进输入框后自动带一个回车,直接把表单提交了,页面刷新资料没存上。原因是扫码枪默认配置就是在条码后发送回车键,但表单没有阻止默认回车行为。解决方式是给报工输入框绑定 keydown 事件,如果 event.key 是 Enter 就调用查询接口,而不是触发表单提交。车间里还有一部分旧电脑用 IE 或老版本 Edge,Vue 全家桶在那边会白屏,需要提前定浏览器兼容目标,我一般直接规定“车间终端只装 Chrome 和对应驱动版本”,并写进交付文档。
reportInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); queryWorkOrderByScanCode(reportInput.value.trim()); } });5.5 坑五:第一版就上微服务,维护成本直接压垮
现象:某个开发团队拿到生产管理系统源代码需求后,觉得“以后要接 MES、要对接 ERP”,第一版就拆出七个服务,网关、注册中心、消息队列全上了。结果正式上线后,光服务部署和环境配置就花了三天,现场报工出现延迟,排查链路要翻三层日志,最后团队里三个核心工程师都走光了。原因不是微服务方向错,而是第一版的用户数和业务复杂度根本撑不起分布式成本。解决方式:单体应用 + 模块化代码结构,把 services 之间的调用约定直接定义为内部函数接口,将来真的需要拆分时,再按接口边界把模块提出来。只要是几十人规模的工厂,单体应用处理每分钟几千次请求毫无压力,完全不需要在创业阶段自找麻烦。
6. 让生产管理系统源代码值得投入:报表、接口与二次开发的底子
生产管理系统源代码做完报工闭环只是开始,真正让老板觉得值得的关键是“今天车间干了多少活、哪个工序积压了”。最直接有效的一步是做一个按工作中心和日期的产报汇总接口,给老板一个能看懂的数字。
一个简单的日产量看板查询:
SELECT wc.code AS workcenter_code, DATE(r.report_time) AS report_date, SUM(r.qty) AS total_qty, COUNT(DISTINCT r.work_order_id) AS wo_count FROM work_report r JOIN work_order_step wos ON r.work_order_id = wos.work_order_id AND r.step_no = wos.step_no JOIN workcenter wc ON wos.workcenter_id = wc.id WHERE r.report_time >= %(start_date)s AND r.report_time < %(end_date)s GROUP BY wc.code, DATE(r.report_time) ORDER BY report_date DESC, total_qty DESC;这段 SQL 能直接回答“每个工作中心每天的产出”。注意 JOIN 条件里同时带工单号和工序号,否则多工序工单会出现重复计数。REPORT 时间和工单创建时间跨天时,按报工时间统计才符合实际。报表接口返回 JSON 统一结构,code、message、data 三件套,前端不用为每个接口单独做异常处理。
二次开发的底子要留好两个东西。一个是所有写操作接口都记录操作人和操作时间,将来追溯谁改错了立刻能查到;另一个是给外部系统留一个只读的 API 前缀,比如 /api/v1/external/,专门给 ERP 同步库存和订单状态用。我吃过一次亏,第一次做系统时没有留外部接口,后来要对接某进销存软件,对方说要提供库存查询接口,我硬补了两周才完成。现在做生产管理系统源代码,我第一件事就是把 /api/v1/external/ 这个接口前缀和鉴权逻辑写进项目模板里,哪怕暂时为空接口,扩展点先留着。
权限和报表都过了测试,再把操作工的报工页面优化一下:大按钮、大字体、扫码自动查询、成功后给出声音反馈。车间人员不会看密集的表格,这个环节做好了,系统才真正能落地。我习惯在交付时让计划员用真实工单跑满一周,期间只解决现场反馈的问题,不新增任何功能。希望帮到你。
本文还有配套的精品资源,点击获取