def load(self, f, info=False): """ Loads scheduler, shiftcal, & employees from filename @params f: an open file pointer in "rb" mode @params info: T/F whether to print detailed loading info @return: True if no errors occurred, False otherwise """ try: print("Loading Scheduler data...") self.year = pickle.load(f) if info: print(" Read year: %d" % self.year) self.month = pickle.load(f) if info: print(" Read month: %d" % self.month) self.numDays = pickle.load(f) if info: print(" Read number of days: %d" % self.numDays) self.firstDay = pickle.load(f) if info: print(" Read first day: %d" % self.firstDay) print(" Loaded scheduler attributes") if info: print(" Checking employee data") numEmps = pickle.load(f) if info: print(" Read num employees: %d" % numEmps) self.employeeList = [] emp_dict = {} print("") if numEmps == 0: print("No Employee data to load...") else: print("Loading Employees...") print(" %d employees to load\n" % numEmps) for i in range(0, numEmps): emp = Employee("dummy", 1) # dummy employee who will be overwritten by loaded emp if emp.load(f, info): self.employeeList.append(emp) emp_dict[emp.getName()] = emp else: return False print("") print("") self.cal = ShiftCalendar(1, 1970) # dummy shiftCal if self.cal.load(f, emp_dict, info): return True else: print("Error occured loading calendar...") return False except FileNotFoundError: print("Couldnt find scheduler data file...") return False
def __init__(self, month, year): assert type(month) == int and type(year) == int, "month and year should be integers" assert month in range(1, 13) and year >= 0, "month or year out of bounds" self.cal = ShiftCalendar(month, year) self.year = year self.month = month self.numDays = self.cal.numDays self.firstDay = self.cal.firstDay # which weekday month starts on (0-6) self.employeeList = [] # list of employee objects
class Scheduler: """Shift Scheduler""" def __init__(self, month, year): assert type(month) == int and type(year) == int, "month and year should be integers" assert month in range(1, 13) and year >= 0, "month or year out of bounds" self.cal = ShiftCalendar(month, year) self.year = year self.month = month self.numDays = self.cal.numDays self.firstDay = self.cal.firstDay # which weekday month starts on (0-6) self.employeeList = [] # list of employee objects def save(self, f, info=False): """ Saves Scheduler data using python's pickle module @params f: open file pointer in "wb" mode @params info: T/F whether to print additional info while saving. @return: 1 if successful, -1 if unsuccessfull. """ print("Saving Scheduler data...") pickle.dump(self.year, f) pickle.dump(self.month, f) pickle.dump(self.numDays, f) pickle.dump(self.firstDay, f) # NOTE: Cannot pickle employeeList, need to pickle each employee individually and then reconstruct list. if info: print(" Saved scheduler attributes") if info: print(" Checking employee list") # save length of employee list pickle.dump(len(self.employeeList), f) print("") # If not 0 then save each employee if len(self.employeeList) != 0: print("Saving Employee data...") print(" %d employees to save\n" % len(self.employeeList)) for e in self.employeeList: if not e.save(f, info): print("An error occured!") return False print("") else: print("No employee data to save...") # save shiftCal print("") if self.cal.save(f, info): return True else: return False def load(self, f, info=False): """ Loads scheduler, shiftcal, & employees from filename @params f: an open file pointer in "rb" mode @params info: T/F whether to print detailed loading info @return: True if no errors occurred, False otherwise """ try: print("Loading Scheduler data...") self.year = pickle.load(f) if info: print(" Read year: %d" % self.year) self.month = pickle.load(f) if info: print(" Read month: %d" % self.month) self.numDays = pickle.load(f) if info: print(" Read number of days: %d" % self.numDays) self.firstDay = pickle.load(f) if info: print(" Read first day: %d" % self.firstDay) print(" Loaded scheduler attributes") if info: print(" Checking employee data") numEmps = pickle.load(f) if info: print(" Read num employees: %d" % numEmps) self.employeeList = [] emp_dict = {} print("") if numEmps == 0: print("No Employee data to load...") else: print("Loading Employees...") print(" %d employees to load\n" % numEmps) for i in range(0, numEmps): emp = Employee("dummy", 1) # dummy employee who will be overwritten by loaded emp if emp.load(f, info): self.employeeList.append(emp) emp_dict[emp.getName()] = emp else: return False print("") print("") self.cal = ShiftCalendar(1, 1970) # dummy shiftCal if self.cal.load(f, emp_dict, info): return True else: print("Error occured loading calendar...") return False except FileNotFoundError: print("Couldnt find scheduler data file...") return False def addEmployee(self, employee): """ Adds an employee to the scheduler @params employee: an employee object to be added """ if employee.__class__.__name__ == "Employee": self.employeeList.append(employee) print("Employee %s added." % employee.getName()) def removeEmployee(self, employee): """ Removes an employee from the scheduler and deletes any shifts assigned to him/her @params employee: Employee obect to be removed @return: returns 1 on success, -1 for any type of error. """ if employee.__class__.__name__ == "Employee": # IMPROVE THIS TO DELETE ALL THAT EMPLOYEES SHIFTS & RETURN MORE INFO if employee in self.employeeList: self.cal.removeAllForEmp(employee) print("Employee %s removed." % employee.getName()) self.employeeList.remove(employee) return 1 else: print("%s employee does not exist" % employee.getName()) return -1 def createShiftW(self, weekdays, times, lunch=False): """ Creates shifts with given times for each weekday in weekdays @params weekdays: list of weekdays to create shifts for (0-6) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if you want to create a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(weekdays) > 0 and len(times) > 0: for day in weekdays: self.cal.setShiftsByDay(day, times, lunch) return 1 return -1 def createShiftD(self, days, times, lunch=False): """ Creates shifts with given times for each day in days @params days: list of days to create shifts for (1-31) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if you want to create a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(days) > 0 and len(times) > 0: for d in days: self.cal.setSingleDay(d, times, lunch) return 1 return -1 def delShiftW(self, weekdays, times, lunch=False): """ Deletes shifts with given times for each weekday in weekdays @params weekdays: list of weekdays to delete shifts for (0-6) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(weekdays) > 0 and len(times) > 0: for day in weekdays: for t in times: self.cal.deleteShiftEveryWeek(day, t, lunch) return 1 return -1 def delShiftD(self, days, times, lunch=False): """ Deletes shifts with given times for each day in days @params days: list of days to delete shifts for (1-31) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(days) > 0 and len(times) > 0: for d in days: for t in times: if self.cal.deleteSingleShift(d, t, lunch) == -1: print("Failed to delete shift %d - %s" % (d, t)) return -1 return 1 else: return -1 def assignW(self, emp, weekdays, times, lunch=False): """ Assigns employee to shifts with given times for each weekday in weekdays @params emp: Employee object assigned to each shift @params weekdays: list of weekdays to assign shifts for (0-6) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if emp.__class__.__name__ == "Employee" and len(weekdays) > 0 and len(times) > 0: for d in weekdays: for t in times: self.cal.assignShiftEveryWeek(d, t, emp, lunch) return 1 return -1 def assignD(self, emp, days, times, lunch=False): """ Assigns employee to shifts with given times for each day in days @params emp: Employee object assigned to each shift @params days: list of days to assign shifts for (1-31) @params times: list of Time objects, start time of each shift for each day @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if emp.__class__.__name__ == "Employee" and len(weekdays) > 0 and len(times) > 0: for d in days: for t in times: self.cal.assignShift(d, t, emp, lunch) return 1 return -1 def clearW(self, weekdays, times="all", lunch=False): """ Clears shifts with given times for each weekday in weekdays @params weekdays: list of weekdays to clear shifts for (0-6) @params times: list of Time objects, start time of each shift for each day can also be a string "all" indicating all shifts for those weekdays should be cleared @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(weekdays) > 0 and len(times) > 0: if times == "all": times = [None] # more intuitive way to indicate clearing all shifts for day in weekdays: for t in times: self.cal.clearShiftsEveryWeek(day, t, lunch) return 1 return -1 def clearD(self, days, times="all", lunch=False): """ Clears shifts with given times for each day in days @params days: list of days to clear shifts for (1-31) @params times: list of Time objects, start time of each shift for each day can also be a string "all" indicating all shifts for those days should be cleared @params lunch: boolean if referring to a lunch shift or dinner shift (lunch=False) @return: 1 if successful, -1 otherwise """ if len(days) > 0 and len(times) > 0: for d in days: for t in times: self.cal.clearSingleShift(d, t, lunch) return 1 return -1 def matchShift(self, employee, l, t, wd, wn, d): """ Creates a rule from shift info provided and matches with given employee @params employee: Employee object @params d: integer (1-31) indicating day to match @params l: boolean indicating whether to match a lunch or dinner shift @params t: A string representing the time at which shift starts @params wd: integer (0-6) indicating which day of the week shift is on @params wn: integer (1-6) indicating which number week the shift is on @returns: True or False depending on whether employee can work that shift. """ if employee not in self.employeeList: print("Employee not found.") return -1 shift_rule = Rule(lunch=l, time=t, weekday=wd, weeknum=wn, daynum=d) return employee.matchRule(shift_rule) def run(self, depth): """ Run scheduler until finding a complete schedule Should take into account priority & alternate between all employees equally """ # Clear previous assignments self.cal.clearAllShifts() print("\nAll shifts cleared") self.cal.printCal() print("") print(self.employeeList) res = self.assignEmployee(1, self.cal.getDay(1), self.employeeList.copy().sort(), depth) print("\nScheduling Done: %s" % res) return res def assignEmployee(self, daynum, shift_day, employeeList, depth=-1): """ Recursive function, on each call tries to assign employee from employeeList to an open shift in shift_day Then removes employee assigned from employeeList and calls itself until day is filled. If employeeList empty and day not complete, declare failure Else if day is complete, rebuild employee list and call recursively on next day If daynum > number of days in month, check if calendar is complete, else return failure. @params daynum: integer (1 to numDays in month), day we are working on @shift_day: ShiftDay object of that day @employeeList: list of employees ordered by priority @depth: integer indicating how many recursive calls to execute before stopping, used for testing. -1 disables limiting depth @return: True if found a complete schedule, False if not. Modifies the shiftcalendar in place """ # No more shifts left to schedule if self.cal.isComplete(): return True # We are done with all days if daynum > self.cal.numDays: return self.cal.isComplete() # Stop scheduling for testing purposes if depth == 0: return True print("Trying to schedule day %d" % daynum) shift = shift_day.getNextEmptyShift() # No more shifts left for this day, move on to next day if shift == None: print("No more shifts left for this day.") empList = self.employeeList.copy() # get fresh employee list empList.sort() sd = self.cal.getDay(daynum + 1) print("reset emp list to %s" % (str(empList))) return self.assignEmployee(daynum + 1, sd, empList, depth) # Else there is a shift to schedule lunch = shift[0] time = shift[1] weekday = shift_day.weekday weeknum = shift_day.weeknum print("empList: %s" % (str(employeeList))) for e in employeeList: print("---------") print("Trying employee %s" % (str(e))) if self.matchShift(e, lunch, time, weekday, weeknum, daynum): # true if employee can work shift print("%s can work that shift!" % (str(e))) temp = e if lunch: shift_day.setLunchShift(time, e) else: shift_day.setDinnerShift(time, e) newEmpList = employeeList.copy() newEmpList.remove(e) print("new emp list: %s" % (newEmpList)) print("new depth = %d" % (depth - 1)) if not self.assignEmployee(daynum, shift_day, newEmpList, depth - 1): # scheduling was unsuccessful, undo assignment if lunch: shift_day.setLunchShift(time, None) else: shift_day.setDinnerShift(time, None) continue # next employee else: # everything went well return True # We get here if we have gone through all employees so declare failure return False