最近项目中有需要实现排班功能,就是有3个班组,实现三班倒的排班功能,目的在于后续统计每个班次生产量。
1:功能设计
结合实际情况,这样设计系统:首先设计一张班组表,用于班组的增删改查。班组成员的信息在这个张表中不做体现。
再设计一张上班时段管理表,用于配置上班时段。
再设计一种排班表,用于配置那个班在那天上那个时段。
再设计一种班时表,班时表真实的记录各个班车上班详情。班时表每月可以生动或者自动生成下月的。
2:设计实现
2.1:班组表
班组表主要时班组名称和生效状态,再后生成班时表时,值选择生效的班组。
2.2:上班时段表
记录时段上班时段。
例如三班倒的班时,A、B、C,他们分别都上8个小时的时长,但各自的开始和结束时间不一样;
再如两班倒的A12、B12,他们分别上12个小时的时长,也是开始的结束时间不一样。
具体怎么定义,可以根据车间实际的工作时段自行定义,只要确保班时名称和班时编号不重叠就可以了。
2.3:排班表
这个就是根据各个班组和班组对应上班时段和上班日期确定对应的记录。
例如:班组2-1,上两班倒,上2休1,白班一天、夜班一天。
配置方法如下:
首先新增一条记录:选择班组2-1,上班序号设置1,这个标识第一天,上班时段选择A12,即白班从8:00倒20:00。
再新增一条记录:选择班组2-1,上班序号设置2,这个标识第二天,上班时段选择B12,即晚班班从20:00倒8:00。
再新增一条记录:选择班组2-1,上班序号设置3,这个标识第三天,上班时段不选择,即标识无上班时段,即休息。
设置完毕后,排班记录如下:
2.4:班时表
当基础数据设置完毕后,就可以生成班时表。班时表可以手动生成也可以自动生成,每次生成从当前时间开始未生成的时间启倒下月结束时间段的数据。如果下月数据已经生成,就不再生成。
生成逻辑时:首先查找所有生效的班组,然后查找所有生效的排班记录,然后查找各个班组最晚一条班时记录。然后根据各个班组最晚一条排班记录中的上班序号,再生效的排班记录中匹配的对应的记录,软后开始以排班记录中该班组的最大上班序列未周期,以最晚排班记录中的上班序列再加1作为启动点,以下月月末未结束时间,开始循环生成本班组的班时表。
还是以班组2-1为例,灰色底色的是标识该班时已经过了当前时间,该班时不要修改,只能查看。绿色底色标识,该天为休息时间。
如果仔细看,会发现再12月31日和1月1日交接时间点上,该班的排班不连续,正常如果31号为白班,那么1号应该为夜班。但班时表上却为休息,并且后续排班都按这个时间点开始重新轮回。这是什么原因?其实再1月1日的排班是手动修改的。过程是这样,在生成1月份的班时前,先手动增加料一条1月1号的记录,这条记录,没有和12月31日的记录接续,直接重新排序了一条,后续在生成班时时,就从最晚的记录开始接续执行。这样,1月份的班时就按1月1日的开始接续了。
如果没有手动修改,那就会按12月31日的开始接续。例如我们看看班组2-2:
3:程序实现
本系统是基于net6实现的,各个页面模型相对比较加单,技术核心其实在班时的生成。
生成时,首先查询有效班组
其中查询最晚班时
在查询排班记录
让后根据班组、排班记录和最晚班时生成新的班时。
写的过程中发现一个排班bug,大家可以看看问题再哪。
{
//查找班组
var queryCT = DC.Set<Classteam>()
.Where(x => x.status == PolicyStatus.canuse)
.Select(x => new Classteam_View
{
ID = x.ID,
Name = x.Name,
Code = x.Code,
status = x.status,
B_datetime = x.B_datetime,
E_datetime = x.E_datetime,
})
.OrderBy(x => x.Name)
.ToList();
//查找最晚班时
var queryCW = DC.Set<Classwork>()
.Select(x => new Classwork_View
{
ID = x.ID,
Name_view = x.classteam.Name,
seq = x.seq,
Name_view2 = x.workpolicy.Name,
Last_datetime = x.Last_datetime,
})
.OrderBy(x => x.ID)
.GroupBy(x => new { x.Name_view }, x => x.Last_datetime)
.Select(x => new {
Name_view = x.Key.Name_view,
Last_datetime =x.Max()
})
.ToList();
var queryCWLast = new List<Classwork_View>();
for(int i = 0; i < queryCW.Count; i ++){
var queryCWtmp = DC.Set<Classwork>()
.CheckContain(queryCW[i].Name_view, x => x.classteam.Name)
.CheckEqual(queryCW[i].Last_datetime, x => x.Last_datetime)
.Select(x => new Classwork_View
{
ID = x.ID,
classteamId = x.classteamId,
Name_view = x.classteam.Name,
seq = x.seq,
workpolicyId = x.workpolicyId,
Name_view2 = x.workpolicy.Name,
Last_datetime = x.Last_datetime,
})
.ToList();
//queryCWLast.Concat(queryCWtmp);
queryCWLast.AddRange(queryCWtmp);
}
//查找排班
var queryCP = DC.Set<ClassPolicy>()
.Where(x => x.status == PolicyStatus.canuse)
.Select(x => new ClassPolicy_View
{
ID = x.ID,
classteamId = x.classteam.ID,
Name_view = x.classteam.Name,
seq = x.seq,
workpolicyId = x.workpolicy.ID,
Name_view2 = x.workpolicy.Name,
status = x.status,
B_datetime = x.B_datetime,
E_datetime = x.E_datetime,
})
.OrderBy(x => x.Name_view)
.ToList();
DateTime begindatetime = DateTime.Now.AddDays(1 - DateTime.Now.Day);
DateTime lastday = begindatetime.AddMonths(1).AddDays(-1).Date;
DateTime nextlastday = begindatetime.AddMonths(2).AddDays(-1).Date;
string strlastday = lastday.ToString("yyyyMMdd");
string strnextlastday = nextlastday.ToString("yyyyMMdd");
//
DateTime startdatetime = DateTime.Now.Date;
int maxCP = 0;
int startCP = 1;
int classworknoinfoflag = 1;
int classworkcreateflag = 0;
for (int i = 0; i < queryCT.Count; i++)//班组
{
startdatetime = DateTime.Now.Date;
maxCP = 0;
startCP = 1;
classworknoinfoflag = 1;
classworkcreateflag = 0;
Dictionary<int, ClassPolicy_View> dictCP = new Dictionary<int, ClassPolicy_View>();
for(int j = 0; j <queryCP.Count; j++)//排班计划
{
if (queryCP[j].Name_view == queryCT[i].Name)
{
dictCP.Add(queryCP[j].seq, queryCP[j]);
if (maxCP < queryCP[j].seq)
maxCP = queryCP[j].seq;
}
}
if (dictCP.Count == 0)//无排班计划
continue;
Dictionary<int, ClassPolicy_View> dictCPsort = dictCP.OrderBy(p => p.Key).ToDictionary(p => p.Key, o => o.Value);
for (int k = 0; k < queryCWLast.Count; k++)//班组工作
{
if (queryCWLast[k].Name_view == queryCT[i].Name)
{
classworknoinfoflag = 0;
if ((queryCWLast[k].Last_datetime?.ToString("yyyyMMDD").CompareTo(strnextlastday)) != 0)
{
startdatetime = (DateTime)queryCWLast[k].Last_datetime;
startCP = queryCWLast[k].seq;
}
else
{
如果下一月班组工作已经有了,就不要在生成了
classworkcreateflag = 1;
}
}
}
if (classworkcreateflag == 1)
continue;
if(classworknoinfoflag == 1)
{
//之前无班时
Console.WriteLine("no info");
}
TimeSpan midTime = nextlastday - startdatetime;
for (int x = 0; x < midTime.Days; x ++)
{
startdatetime = startdatetime.AddDays(1);
startCP++;
if (startCP > maxCP)
startCP = 1;
ClassPolicy_View CP_view;
bool result = dictCPsort.TryGetValue(startCP, out CP_view);
if (!result)
continue;
Classwork classwk = new Classwork
{
classteamId = CP_view.classteamId,
classteam = CP_view.classteam,
seq = startCP,
workpolicyId = (CP_view.workpolicyId == null)?null: CP_view.workpolicyId,
workpolicy = CP_view.workpolicy,
Last_datetime = startdatetime
};
DC.AddEntity(classwk);
}
DC.SaveChanges();
}
}