简介:生产计划排程1.1.rar 是一套基于 VB6.0 开发的生产计划与排程桌面应用源码,面向学习工业信息化、ERP/MES 排程逻辑的开发者与相关专业学生。系统围绕产能分析、计划导入与生产计划控制三大模块展开,通过 Access 等数据库存储产能信息、物料清单与历史记录,并借助 SQL 脚本完成建库与初始化,是理解制造业排程算法的完整实践案例。压缩包共 68 个文件,约 2.37MB,包含 8 个 frm 窗体、3 个 frx 资源文件、2 个 vbp 工程与 2 个 bas 模块等 VB 源码,另有 8 个 xls 排产数据表、2 个 sql 脚本、2 份 doc 系统分析文档及 27 个 ico 图标,覆盖界面、数据与文档各层。目前已有 535 人学习下载。读者可从中获取可运行的排程工程、数据库结构定义、产能分析表格与系统分析笔记,用于二次开发或课程设计参考。
1. 生产计划排程1.1.rar:一个把数据库操作压进排程逻辑的实战包
车间里最怕的不是订单多,而是排程靠 Excel 手工拉、改一个工单牵动十几张表、第二天计划员请假整条线就停摆。这份「生产计划排程1.1.rar」就是冲着这个场景来的——它把生产计划排程的核心逻辑和数据库操作绑在一起,用一套可跑通的源码把「订单→工序→设备→工时→排程结果」这条链路固化下来。适合谁?一是正在做 MES 或 APS 模块、需要一份能改能调的参考实现的开发者;二是被 Excel 排程折磨、想看看程序化排程到底怎么落库的计划岗转技术的人。它不解决所有排程问题,但能让你把「排程结果怎么存、怎么查、怎么回滚」这件事先跑通。
2. 拆开压缩包先看数据库:表结构与排程字段的对应关系
拿到一个排程类资源,我一般不会先跑主程序,而是先把数据库脚本翻出来。排程系统的玄学多半不在算法,而在表设计——字段少一个,后面排程结果就存不进去;字段类型选错,工时一算就丢精度。这一章先把库表结构和排程逻辑的映射关系理清,后面写代码才不会来回改表。
2.1 从压缩包到可连接数据库:环境准备与脚本导入
解压后通常能看到类似sql/或db/的目录,里面是建表脚本。常见做法是先用 MySQL 或 SQL Server 建一个空库,再按顺序执行脚本。我一般会先确认脚本里的字符集和引擎,避免导入到一半报错。
# 解压后先看目录结构,确认脚本位置 unzip 生产计划排程1.1.rar -d plan_schedule cd plan_schedule ls -R | head -50 # 建库(以 MySQL 为例),字符集跟脚本保持一致 mysql -u root -p -e "CREATE DATABASE plan_schedule DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入建表脚本,注意脚本里如果有 USE 语句要对应库名 mysql -u root -p plan_schedule < sql/schema.sql # 导入基础数据(设备、工序、日历等) mysql -u root -p plan_schedule < sql/base_data.sql逻辑说明:先解压再建库,顺序不能反。参数上,utf8mb4是为了兼容可能出现的特殊字符,如果脚本里写的是utf8,以脚本为准,别强行改,否则外键或索引长度可能对不上。导入基础数据这步很多人跳过,结果主程序跑起来查不到设备列表,排程直接空转。
提示:导入前先
source一遍脚本看有没有语法错误,比导入到一半失败再回滚省事。
2.2 核心表字段解读:订单、工序、设备、排程结果四张表怎么连
排程系统的库表通常围绕四类实体:订单表、工序表、设备/资源表、排程结果表。下面这张表是我从这类资源里总结出的常见字段映射,具体字段名以你拿到的脚本为准,但对应关系基本一致。
| 业务实体 | 常见表名 | 关键字段 | 排程中的作用 |
|---|---|---|---|
| 订单 | order_info | order_id, product_id, qty, due_date | 决定排程优先级和交期约束 |
| 工序 | process_route | route_id, process_seq, std_hours | 决定每道工序的标准工时和先后顺序 |
| 设备/资源 | resource_info | resource_id, capacity, calendar_id | 决定可用产能和排程时间窗 |
| 排程结果 | schedule_result | schedule_id, order_id, resource_id, start_time, end_time | 落库排程输出,供查询和调整 |
字段解读上,std_hours和capacity是最容易出问题的地方。std_hours如果是整数类型,半小时的工序就会被截断成 0 或 1,排程结果全偏。capacity如果没跟日历表关联,周末和节假日也会被算成可用产能,排出来的计划看着漂亮,实际执行不了。排程结果表里的start_time和end_time建议用datetime而不是date,否则同一天多工序排程会互相覆盖。
2.3 用 SQL 验证排程数据完整性:三个必查查询
表建好、数据导入后,别急着跑主程序。先用几条 SQL 确认数据能不能支撑排程。这三条查询是我每次拿到排程类资源都会跑的。
-- 查询1:检查有没有订单缺少工艺路线,缺了排程会跳过该订单 SELECT o.order_id, o.product_id FROM order_info o LEFT JOIN process_route p ON o.product_id = p.product_id WHERE p.route_id IS NULL; -- 查询2:检查设备产能是否都关联了日历,没关联的排程时会报空 SELECT r.resource_id, r.capacity, r.calendar_id FROM resource_info r WHERE r.calendar_id IS NULL OR r.calendar_id = ''; -- 查询3:检查排程结果表是否已有历史数据,避免重复排程叠加 SELECT COUNT(*) AS existing_schedule FROM schedule_result WHERE schedule_date = CURDATE();逻辑说明:查询1 用左连接找「有订单没工艺」的孤儿数据,这类订单在排程时会被静默跳过,计划员以为排了其实没排。查询2 找没关联日历的设备,排程算法算时间窗时会拿到空值,轻则报错重则死循环。查询3 是防止重复排程,很多排程程序每次运行是追加写入,不先清当天数据就会越排越多。参数上,CURDATE()换成你要排程的目标日期即可。
3. 排程主逻辑怎么跑:从订单读取到结果落库的完整链路
库表理清后,这一章看排程主逻辑。这类资源的排程算法通常不会太复杂,常见做法是「按交期排序 + 按工序顺序找可用设备时间窗 + 写入结果表」。重点不在算法多高级,而在每一步的数据库操作有没有做对——读订单、锁设备、写结果,任何一步的事务没处理好,排程结果就会脏。
3.1 排程入口与参数配置:先改哪几个值再运行
主程序入口一般是一个带main的类或一个启动脚本。运行前先找配置文件,常见是config.properties或application.yml。我一般会先改这几个值:数据库连接、排程目标日期、是否清空历史结果。
# 数据库连接,按你本地环境改 db.url=jdbc:mysql://localhost:3306/plan_schedule?useUnicode=true&characterEncoding=utf8 db.user=root db.password=your_password # 排程参数 schedule.target.date=2025-06-01 schedule.clear.history=true schedule.max.parallel.orders=50逻辑说明:schedule.target.date决定排程从哪天开始算,不设的话有些程序默认取系统当天,跨天运行会排错日期。schedule.clear.history建议第一次运行时设为true,把目标日期的历史排程结果清掉再排,避免叠加。schedule.max.parallel.orders控制同时参与排程的订单数,设太大内存吃紧,设太小排程慢,一般 50 到 200 之间按机器配置调。
注意:改完配置先别急着跑全量,拿一条订单试排,确认结果表有数据且时间合理,再放开跑。
3.2 订单排序与工序展开:排程顺序的代码实现
排程顺序直接决定结果好坏。常见做法是按交期升序排,交期相同的按订单优先级排。工序展开则是把每个订单的工艺路线按process_seq拆成一道道工序,逐道找设备时间窗。
# 伪代码示意,实际语言以资源为准 def load_orders(target_date): # 按交期升序、优先级降序读取待排订单 sql = """ SELECT order_id, product_id, qty, due_date, priority FROM order_info WHERE status = '待排程' AND due_date >= %s ORDER BY due_date ASC, priority DESC """ return db.query(sql, [target_date]) def expand_process(order): # 展开工艺路线,按工序顺序返回 sql = """ SELECT process_seq, process_id, std_hours FROM process_route WHERE product_id = %s ORDER BY process_seq ASC """ return db.query(sql, [order['product_id']])逻辑说明:load_orders里的status = '待排程'是过滤条件,已排程的订单不重复参与。ORDER BY due_date ASC, priority DESC是先保交期再保优先级,如果你的业务是优先级优先,把两个字段顺序调换即可。expand_process按process_seq升序展开,保证工序先后不乱。参数上,std_hours读出来后要转成分钟或秒参与时间计算,别直接拿小时数做加减。
3.3 设备时间窗查找与结果写入:事务与锁的处理
找到工序后,要在设备日历里找一段连续可用时间。这一步的数据库操作最容易翻车——多个订单同时抢同一台设备,不加锁就会排重。
-- 查找设备在目标日期内的可用时间窗(简化示意) SELECT r.resource_id, c.work_start, c.work_end FROM resource_info r JOIN resource_calendar c ON r.calendar_id = c.calendar_id WHERE r.resource_id = %s AND c.calendar_date = %s AND NOT EXISTS ( SELECT 1 FROM schedule_result s WHERE s.resource_id = r.resource_id AND s.schedule_date = c.calendar_date AND s.start_time < c.work_end AND s.end_time > c.work_start );逻辑说明:NOT EXISTS子查询用来排除已被占用的时间段,判断条件是「已有排程的开始 < 当前窗口结束 且 已有排程的结束 > 当前窗口开始」,这是标准的时间段重叠判断。写入结果时,常见做法是把「查时间窗 + 写结果」放在同一个事务里,并对resource_info对应行加行锁,防止并发排重。
# 事务内写入排程结果 try: db.begin() # 加行锁,锁住设备行 db.execute("SELECT resource_id FROM resource_info WHERE resource_id = %s FOR UPDATE", [resource_id]) # 再次确认时间窗未被占用 if not check_time_window(resource_id, start_time, end_time): raise Exception("时间窗已被占用") db.execute(""" INSERT INTO schedule_result (schedule_id, order_id, resource_id, start_time, end_time, schedule_date) VALUES (%s, %s, %s, %s, %s, %s) """, [schedule_id, order_id, resource_id, start_time, end_time, target_date]) db.commit() except Exception as e: db.rollback() log.error("排程写入失败: %s", e)参数说明:FOR UPDATE是行锁,只在事务内有效,所以begin和commit必须包住。check_time_window是二次确认,因为加锁前可能已有其他事务写入。schedule_id建议用序列或 UUID,别用自增在主从环境里容易冲突。
4. 避坑与排查:排程结果对不上时先查这五处
排程类资源跑不起来或结果不对,九成不是算法问题,而是数据或事务问题。这一章列五个我实际踩过的坑,按「现象→原因→解决」写,你对着查能省不少时间。
4.1 现象:排程结果为空,程序不报错
原因:订单表的status字段值和程序里过滤条件不一致,比如程序查待排程,数据里存的是待排或0。解决:先跑一条SELECT DISTINCT status FROM order_info看实际值,再对照代码里的过滤条件改,别凭猜。
4.2 现象:同一设备同一时间段出现两条排程
原因:写入时没加行锁,或者事务隔离级别太低,两个排程线程同时查到同一空窗。解决:把「查时间窗+写结果」包进事务,对设备行加FOR UPDATE,并把数据库隔离级别调到READ COMMITTED以上。
4.3 现象:排程时间算出来是负数或超大值
原因:std_hours字段类型是整数,半小时工序被截断成 0,或者时间加减时单位不统一,小时和分钟混用。解决:把std_hours改成decimal(10,2),代码里统一转成分钟计算,最后再转回时间格式。
4.4 现象:程序跑一半卡死,日志停在查设备时间窗
原因:设备日历表里有work_start大于work_end的脏数据,时间窗查找进入死循环。解决:跑一条SELECT * FROM resource_calendar WHERE work_start >= work_end把脏数据找出来修掉,并在代码里加校验,发现异常窗口直接跳过并记日志。
4.5 现象:重复运行排程,结果表数据翻倍
原因:程序每次运行是追加写入,没有按目标日期清历史结果。解决:运行前先执行DELETE FROM schedule_result WHERE schedule_date = '目标日期',或者把配置里的schedule.clear.history设为true。删之前先备份,别把要保留的手工调整记录也删了。
5. 进阶用法:把排程结果反写回订单状态并做交期预警
跑通基础排程后,这个资源还能往下挖一层——排程结果不只是存起来看,可以反写回订单状态,并基于排程结束时间做交期预警。这一步做完,整个链路才算闭环。
5.1 排程完成后回写订单状态
排程结果落库后,把对应订单的status从待排程改成已排程,并记录排程完成时间。这样下次运行排程时不会重复处理同一批订单。
-- 回写订单状态,只更新本次排程涉及的订单 UPDATE order_info o JOIN ( SELECT DISTINCT order_id FROM schedule_result WHERE schedule_date = '2025-06-01' ) s ON o.order_id = s.order_id SET o.status = '已排程', o.schedule_time = NOW() WHERE o.status = '待排程';逻辑说明:用JOIN子查询限定只更新本次排程涉及的订单,避免误改其他订单。WHERE o.status = '待排程'是二次保险,防止把已排程的订单状态覆盖。参数上,schedule_date换成你的目标日期,NOW()是数据库当前时间,如果跨时区部署要改成统一时区函数。
5.2 交期预警查询:找出排程结束晚于交期的订单
排程跑完后,最该看的是哪些订单排完已经超过交期。这条查询直接给出预警清单。
-- 找出排程结束时间晚于订单交期的订单 SELECT o.order_id, o.due_date, MAX(s.end_time) AS last_end_time, TIMESTAMPDIFF(HOUR, o.due_date, MAX(s.end_time)) AS delay_hours FROM order_info o JOIN schedule_result s ON o.order_id = s.order_id WHERE s.schedule_date = '2025-06-01' GROUP BY o.order_id, o.due_date HAVING MAX(s.end_time) > o.due_date ORDER BY delay_hours DESC;逻辑说明:先按订单分组取最后一道工序的结束时间MAX(s.end_time),再跟交期比。TIMESTAMPDIFF算延迟小时数,正数表示超期。HAVING过滤出超期订单,按延迟时长降序,计划员一眼看到最急的。参数上,如果交期只到日期没到时间,比较时要把交期补成当天 23:59:59,否则会误判。
5.3 一个我常用的验证习惯
每次改完排程逻辑或调完参数,我不会直接看结果表总数,而是随机抽三条订单,手工按工艺路线和工时算一遍结束时间,再跟schedule_result里的end_time对。对不上就查是工时读错、日历没算对还是时间窗找错。这个习惯帮我抓过好几次「算法看着对、数据悄悄错」的问题。从那以后我每次动排程相关代码,都强制走一遍「抽三条手工验算」的流程,比看日志快。希望帮到你。
本文还有配套的精品资源,点击获取