0
点赞
收藏
分享

微信扫一扫

【原创】python语言实现半自动排班系统

python语言实现半自动排班系统

这里写目录标题

0. 为什么要做这个软件?

相信各位从事人事的dalao们,尤其是门店销售的店长们应该都经历过排班表的痛苦。在网上收集了文本的报班信息(比如说微信聊天)后将其整理为一张表可以说是一个机械重复又费时费力的工作。虽然说可以用共享文档等现有的解决方案,但根据我的了解(以及个人打工经历)大多数店铺采用的还是微信接龙的方式。因此,我设计了一个可以辅助店长们及人事dalao们排班的跨平台软件。

1. 本软件要实现哪些功能?

总目标:将接龙文本制作为排班表格
步骤:

  1. 识别接龙文本中格式多样的报班时间
  2. 汇总报班时间,自动得出报班表,并汇总出工时
  3. 用户对报班表进行修改,最终制作成排班表
  4. 导出表格

以上的功能需要使用的第三方依赖:pandas(整理数据),wxpython(GUI)

2. 具体功能实现

2.1 识别文本中的格式不一的时间表达方式

没有格式统一这样概念的一般员工们在接龙的时候所使用的报班格式是多种多样的,我在软件设计中看到的就包括但不限于:用小数点作分割符,只写一个月份(2.1-3),不按顺序(2.3 晚,2.1-2.2 通)等非常多让程序员们血压暴涨的格式= =,因此,我们如果要实现一个实用化的系统,第一步便是要做到把这些高血压的情况解决掉。

首先,我们收集报班的时候一般都是确定的时间段的,比如2.16-2.28,这个完全可以在UI中要求用户输入信息。所以我们首先可以通过pandas中的date_range方法实现。

def TimeTranser(StartTimeStr,EndTimeStr):
    DatetimeIndex = pd.date_range(StartTimeStr,EndTimeStr,freq='D')
    TidyedTimeBox = [DatetimeIndex[0],DatetimeIndex[-1]]
    for DateTime in DatetimeIndex:
        TidyedTime = "{}.{}".format(DateTime.month,DateTime.day)
        TidyedTimeBox.append(TidyedTime)
    return TidyedTimeBox

现在,我们已经获得了一个时间的“标准答案”TidyedTimeBox,有了判断标准后就是很简单的文本处理啦~

既然我们设定了这个排班助手软件的应用情景是微信接龙,那么就可以充分利用接龙文本的特征:接龙内容部分每行必定为数字加小数点加空格组成。那么我们就可以用这个性质来判定有效文本的开始位置,这里用FirstInt函数表示。

def FirstInt(line):
    LineMess = line.replace('\n','').split('. ')
    if((len(LineMess)) == 1):
        return False,114514
    else:
        number = int(LineMess[0])
        PersonalMesses = LineMess[1].split(' ')
        pre_dels = []
        for n,mess in enumerate(PersonalMesses):
            if(mess == ''):
                pre_dels.append(n)
        for i in pre_dels:
            del PersonalMesses[i]
        return True,number,PersonalMesses

识别完成之后自然就是要将人员的信息进行规范化处理(即将报班分类规整为每个单独的日期),这个步骤实际上是两步,首先分类(早班,晚班,通班等情况),其次规整。分类则以WorkTimeTranslate方法前半部分完成,规整则由TidyTimeStr方法实现:

    def WorkTimeTranslate(self):
        UsefulData = []
        for n in range(len(self.mess)):
            if(n == 0):
                name = self.mess[n]
            else:
                UsefulData.append(self.mess[n])
        AllPass = self.IsAllPass(UsefulData)
        if(AllPass):
            ReturnMess = (name,True)
        else:
            Times = self.TidyTimeStr(UsefulData)
            ReturnMess = (name, False, Times)
        return ReturnMess

   def TidyTimeStr(self,Datas):
        TimeDict = {'通':[],'早':[],'晚':[],'夕':[],'晨':[]}
        TimeString = ""
        for data in Datas:
            if(self.CheckDict(data) == -1):
                if(TimeString == ""):
                    TimeString = data
                else:
                    TimeString = "{}${}".format(TimeString,data)

            else:
                if (TimeString == ""):
                    TimeString = "{}*".format(data)
                else:
                    TimeString = "{}${}*".format(TimeString, data)

        OldData = (TimeString.split('*'))
        NewGroup = []
        for n_data in OldData:
            if(n_data != ''):
                n_data = self.RemoveSysmbol(n_data)
                n_data = n_data.replace('晚插','夕').replace('早插','晨').replace('$通','通').replace('$夕','夕').replace('$晨','晨').replace('$早','早').replace('$晚','晚')
                if(n_data[0] == '$'):
                    n_data = self.remove_char(n_data,0)
                NewGroup.append(n_data)
        KeyBox = self.FindWorkTimeKeyLocation(NewGroup)

        for num, key in enumerate(KeyBox):
            try:
                WorkTimeKey = NewGroup[num][key[1]]
                TimeContent = NewGroup[num].replace('夕','').replace('晨','').replace('早','').replace('晚','').replace('通','')
                TimeContent = self.Point2Comma(TimeContent).replace('$',',')
                TimeTotal = self.TimeTranser(TimeContent)
                TimeDict[WorkTimeKey] += TimeTotal
            except:
                continue
        return TimeDict

可以看到,代码中最重要的思路其实是三个:

  1. 运行中将文本的分割符号全部统一为$符号(基于这个符号在这种语境下几乎不被使用考虑)和*符号,前者为时间切割符后者为段落切割符,统一符号之后就会非常好处理。
  2. 利用python中Dict类型的特性,将时间字符串分类为预设好的类别中(因为类别的预设是简单且有限的),这样我们就可以唯一地处理时间信息,且在写表前不再需要整理类别。
  3. 通过判断时间切割符前后文直接得出实际上是否真正需要分割还是只是文本不规范。

2.2 将整理好的内容生成带工时的报班表

通过以上操作,我们已经将个人的文本分类放入了TimeDict中,接下来只需要一些简单的操作就可以把一个人的信息整理完成了(这一部分技术含量不高我就不写进来了,完整代码见文末)。这一部分在代码中整体作为一个ReadMess方法:

def ReadMess(mess,timebox):
    numbers = []
    ReturnMesses = []
    for line in mess:
        line = line.replace('班','').replace('到','-').replace('晚插','夕').replace('早插','晨')
        ReturnMess = FirstInt(line)
        # print(ReturnMess)
        if(ReturnMess[0]):
            numbers.append(ReturnMess[1])
            UsefulMesses = ReturnMess[2]
            ReturnMesses.append(MessTranser(UsefulMesses,timebox).forward())
    TotalTable = GetTotalTable(ReturnMesses,timebox)
    return len(numbers), TotalTable

在信息全部搜集完成后,接下来就是建表啦!我们这里使用的是wxpython,辅助使用了wxFormBuilder。
表格的创建本身是并不困难的,毕竟我们在前面已经获取了完整且规整的报班信息。一个萝卜一个坑把信息按次序排成表格就好,这里我使用了MakeTable方法解决问题:

    def MakeTable(self):
        TimeDict = {}
        for person in self.MemberTable:
            name = person[0]
            self.PeopleDict[name] = []

        for num, time in enumerate(self.TimeData):
            TimeDict[time] = num

        for name in self.InputTableData:
            WorkTimeGroup = []
            for i in range(len(self.TimeData)):
                WorkTimeGroup.append('')
            for worktime in self.InputTableData[name]:
                Times = self.InputTableData[name][worktime]
                for time in Times:
                    WorkTimeGroup[TimeDict[time]] = worktime
            for num,worktime in enumerate(WorkTimeGroup):
                if(worktime == ''):
                    WorkTimeGroup[num] = '/'
                else:
                    WorkTimeGroup[num] = "{}班".format(WorkTimeGroup[num])

            self.PeopleDict[name] = WorkTimeGroup

        for num, name in enumerate(self.PeopleDict):
            RowNum = num
            for ListNum, TimeWorktime in enumerate(self.PeopleDict[name]):
                # print(RowNum,ListNum)
                try:
                    self.DataTable.SetCellValue(RowNum,ListNum,TimeWorktime.replace('夕','晚插').replace('晨','早插'))
                except:
                    continue
        self.UpDateTable()

细心的朋友们可能已经发现了,方法最后有一个UpDateTable方法,顾名思义,这个方法是用来更新数据表的,可问题是刚刚创建表格为什么要更新数据表呢?原因其实很简单,这个方法实际上不只是更新数据表,它真正的作用是计算工时:

    def UpDateTable(self):
        for num,name in enumerate(self.PeopleDict):
            TotalGroup = []
            try:
                for cell in range(len(self.TimeData)):
                    CellData = self.DataTable.GetCellValue(num,cell).replace('晚插','夕').replace('早插','晨')
                    TotalGroup.append(CellData)
                TotalWorkTime = 0
            except:
                continue
            for Work in TotalGroup:
                try:
                    TotalWorkTime += self.worktimetable[Work]
                except:
                    continue
            self.DataTable.SetCellValue(num,len(self.TimeData),str(TotalWorkTime).replace('夕','晚插').replace('晨','早插'))

现在我们已经获得了一张很漂亮的表格啦~(效果看第三部分实战展示)

2.3 帮助用户最终制作成排班表

在上面的步骤完成后,实质上我们得到的是一张“报班表”,何谓报班表呢?其实就是每一位员工申请的上班时间,而通常情况下店长对在岗人员的需求是远小于员工们的总申请的(这种现象在有兼职人员的零售或餐饮店中特别常见),因此汇总报班而成的“报班表”还需要店长进行筛选增删变成“排班表”。这一步其实有两种做法,第一种是直接在本程序中进行修改,第二种是输出一张Excel后再更改。考虑到本程序的使用场景中要求实现工时计算的功能,因此采用第一种方法更好实现(第二种方法其实也可以实现,我的思路是输出一张自带函数的格式Excel文档,但这更繁琐且可能涉及跨语言比较麻烦),因为上面的UpDateTable方法中就已经有扫描全表并更改信息的能力(好吧其实这个函数就是在工作进行到这一步才写出来的当然会有啊= =),因此这里我依旧采用上面的UpDateTable方法来进行表格更新和工时计算,并且我专门设置了一个更新键来激活这一函数。因此现在的操作逻辑变为:

输出报班表 -> 更改表格内单元格信息 -> 点击更新按钮计算工时

2.4 导出表格

在我们完成了排班表后,我们当然还需要把表格导出为表格文件进行打印或发布,这个非常简单,CSV格式的话可以使用python内置的函数实现,导出为Excel文件也只需要Pandas依赖库就可以轻松实现。由于我打工的店有格式表格,输出的表格还需要下一步编辑而不是直接发布,因此我选择了更简单的CSV(毕竟代码越少bug越少,虽然这样似乎不够炫技但是给店里实际应用的软件可靠性还得是第一位的),在代码中我写了一个WriteCsvTable方法来实现,GUI中本方法由“导出表格”button激活:

    def WriteCsvTable(self,DataTable):
        SavePath = self.SavePath
        NowTime = time.localtime()
        filename = "{}-{}-{}-{}-{}导出表".format(NowTime.tm_year, NowTime.tm_mon, NowTime.tm_mday,NowTime.tm_hour, NowTime.tm_min)
        CSVPath = "{}/{}.csv".format(SavePath,filename)

        csv_str = []

        for line in DataTable:
            line_str = ""
            for cell in line:
                line_str += "{},".format(cell)
            csv_str.append(line_str)

        with open(CSVPath,'w',encoding='gbk') as csv:
            for line in csv_str:
                csv.writelines(line)
                csv.write('\n')

到这里,我们的软件就完成啦!接下来就打包好给店长用吧~

3. 实战演示

接下来是实际使用~(测试数据是我根据实际报班文本随机改编的,仅具备测试意义,与店铺实际排班无关,请不要过度揣测店铺运营情况)
输入信息

报班表

排班表

最终的csv
任务目标全部完成~店长也非常满意!成功啦!

4. 总结和反思

学到的经验

  1. 和客户沟通的重要性:这一次的软件设计让我感受最深的就是作为程序员和实际用户之间的协调。我最开始的程序构想与现在做出来的东西是有很大不同的,通过和店长及副店长的几次沟通后才确定了用户真正的需求。我们作为开发者看问题的角度和我们的用户是很不一样的,想当然的做软件容易翻大车。
  2. 开发系统尽量和用户系统保持一致:本软件其实有一个bug,就是在Windows系统上不能够进行表格修改,原因我到目前也还没有找出来,目前猜测有可能是因为在Windows系统中wxpython有与Mac os不同的地方。虽然我店通过格式表格内修改排班可以克服这个问题,但是作为开发者我还是非常不乐意见到这个情况的。因此日后在开发软件时一定要尽可能的保持开发平台和应用平台的系统一致,这次的教训真的够大了!!!

可以改进的地方(期待大神们给出指导意见!!)

  1. 软件目前仅支持Linux系统与Mac os系统,Windows系统中不能编辑表格,这里可以进行针对性优化。
  2. 更改完表格后可以直接修改工时而不用专门弄出button,这一步多少有点画蛇添足的意味(实际上这是一个妥协,因为我没有找到单元格数据更新的触发事件)。
  3. 界面过于简陋。
  4. 导出只有CSV表格明显不够,应该增加Excel和PDF的导出能力
  5. 可以应对的文本格式还是有限,比如用小数点进行时间分割的情况还是无法解决(比如2.1 2.3.4.5表示2月1日-5日这类情况),我猜这里可能需要应用到AI技术(我虽然也在学AI但我的侧重点是CV,这方面就真的无能为力了)
举报

相关推荐

0 条评论