Cody Pendant has asked for the wisdom of the Perl Monks concerning the following question:

I offered to help someone the other day with a rostering system with a web interface and admin.

Let's assume they have 20 employees in a 24-hour restaurant.

These 20 people can work any one of three shifts a day. We don't need to worry about when they finish. If they start at nine, they always finish at five.

So the problem is, roughly:

And I want to be able to create

Assuming some database back end, how would monks approach this? What would be the objects, and so on? It's not that I don't know how to do it, it's that I can't figure out the most sensible way.

I need to tie a list of people to a list of times indefinitely into the future. Would you for instance have a "person-works" oriented structure and have the manager input "person A works (list of shifts)" or have a "shift-is-worked-by" structure and have them pick a list of employees to roster against "monday 9 AM"?

Thanks in advance.



($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss') =~y~b-v~a-z~s; print

Replies are listed 'Best First'.
Re: Rostering Staff: Architecture? Not strictly Perl
by Abigail-II (Bishop) on Dec 05, 2003 at 12:43 UTC
    It depends a lot on what kind of logic you want to put in the program. Do you want to prevent that a person can be put into two overlapping shifts? Say, employee A starts at 9 AM on Monday, and on 12 AM on Monday? Or to prevent being assigned at a shift starting at 9 PM on Monday, and a shift starting at 3 AM on Tuesday? Is there a minimum time between shifts to be taken care of? Holidays? Vacation time? Checks whether there are always at least N people working, and at most M? Or is it ok if all 20 start the same shift? Or is your program just a dumb front-end to a single, simple table in the database?

    Abigail

Re: Rostering Staff: Architecture? Not strictly Perl
by castaway (Parson) on Dec 05, 2003 at 12:47 UTC
    Thinking about your data a litte, I would say choose the approach which works best for the fact that you are likely to have a flutuating employee list, whereas the shifts arent as likely to change. In my mind, that would explain a 'shift-is-worked-by' structure.

    Or even, a date-oriented structure, since the shifts repeat themselves daily as well. So you'd have a table with shifts and shift IDs, a table with employees and employee IDs, and a table combining the two, mapping date, shift ID and employee ID together. This third table can then continue indefinitely (tho of course you'll probably want to periodically remove old data). It has a double-index of date and shift ID.

    Code-wise, you then calculate 'today and next seven days', look up those dates in the table, translating shift IDs to times, and employee IDs to names as you go, and making some sort of hash:

    %current = ( '2003-12-05' => { { 'time' => '9:00 AM', 'id' => 1, 'employee' => {'id' => 1, 'name => 'Fred Flintst +one', } }, { 'time' => '5:00 PM', 'id' => 2, 'employee' => { 'id' => 4, 'name' => 'Wilma', } { 'time' => '1:00 AM', 'id' => 3, 'employee' => { 'id' => 3, 'name' => 'Barney', } }, '2003-12-06' => { ... }
    Which you can display and/or work on as required. I hope this makes some sort of sense, Im just thinking as I type (which sometimes has weird results..)

    C.

Re: Rostering Staff: Architecture? Not strictly Perl
by jreades (Friar) on Dec 05, 2003 at 13:27 UTC

    There are a couple of other things to think about:

    • One of the issues you haven't addressed is how the changes to a shift/employee propagate -- in other words, if I change the shift assignment would I normally be expecting to change it permanently or just for the next week?
    • This is also a situation where the shift_id solution (despite being more 'database-y') might actually be less efficient than a more simple (but less commonly-used) strategy. In other words, have you considered a bitmap of some sort using bitwise operations to determine shift availability and prevent overlapping shifts or other shenanigans?

    HTH

Re: Rostering Staff: Architecture? Not strictly Perl
by EvdB (Deacon) on Dec 05, 2003 at 13:52 UTC
    It seems to me that the aim of the app is more to help the manager assign the shifts than it is to store them. After all historical copies can be dealt with by printing out the week's roster and the future is not likely to be more than four weeks.

    I would sit down and ask myself what information would be needed to decide who goes when: for example has Frank worked more than 30 hours in the last week? When was Jane's last shift? Does Bob do mornings?

    It will probably also be helpful to create a big overview screen that shows all the shifts currently assigned, and where staff are needed.

    Interesting problem. I think once you have the problem nailed the storage method will suggest itself...

    --tidiness is the memory loss of environmental mnemonics

      (Edit: Oops, misread the previous post, thought it said 'automatically' in there somewhere, on second reading, it doesnt, anyway, argument against that still stands).

      That's not what it looked like to me. If it is, then Im also interested in the result, since I spent an hour or three recently looking for a similar app, to fit some specific requirements, I found a few Windows ones that came close (best: http://www.schedulerlite.com), but nothing that was exact enough, or could be changed to do it. There were a couple of OS apps on freshmeat etc, but nothing remotely useful (for what I needed anyway, no offense meant..)

      While pondering doing it myself, I decided that automatically assigning, while possible, isn't really very practical. You'd need very exact details of who can, when, and for how long. These are bound to change last-minute, someone gets sick, etc. Which would require either a complete recalc, meaning everyones schedules get changed at the last minute (they're going to love that), or that someone sit down and manually shuffle to minimise the knock-on effect. (I'm not going to suppose that you have people sitting around idle that you can swap-in, no business sense in that).

      In short the best way to do it seems to me, to have someone manually assign the shifts, and have the interface just be *really* helpful, ie add up hours/week on the fly, check constraints on the fly, be able to track vacations/stand-ins, when people like to work, drag+drop names to shifts.. and and and.. Manual assignment also has the advantage of the assigner actually knowing vaguely who is going to be when/where.

      SchedulerLite seems nearly perfect, its just missing that 'show total hours/week on the fly' thing, and a couple of other bits. After fiddling around with it a while, I might attempt coding one myself (in perl of course ,) (BTW it also has all the SQL/tables right there, so the OP might grab some ideas from it..)

      C.

        You've all come up with fascinating questions and answers. Thanks a lot.

        I should clarify that the system was never required to do leave, resolve conflicts, remember staff preferences etc, just work as a kind of content-management system for one page, the "who's working when?" page. As Abigail said, just a dumb front end to a database.

        Questions like this are becoming more interesting to me now, I note, whereas before I would have just started writing something right away, I'm now much more concerned with the structure before I start on the code.



        ($_='kkvvttuubbooppuuiiffssqqffssmmiibbddllffss') =~y~b-v~a-z~s; print
Re: Rostering Staff: Architecture? Not strictly Perl
by Beechbone (Friar) on Dec 05, 2003 at 13:42 UTC
    I found this interesting, so I began to hack a bit...

    SORRY, I have to catch my plane (already late), so this goes without cleanup or further comment for now. I will come back to this node tomorrow...

    our $oopser = 0; # protect from endless loops $#empsh = 20; # prefill worktable for my $sh (1 .. 21) { # 21 shifts are 7 days with 3 shifts for my $shid (1 .. 3) { # every shift has 3 mandtory workers # find a free worker my $emp = 0; $oopser = 0; while ($emp == 0) { $oopser++; die if $oopser > 100; $emp = int( 1 + rand(20) ); # take a random worker $emp = 0 if $empsh->[$emp][$sh]; # already assigned } $empsh[$emp][$sh] = "work ($shid)"; # work this shift (8 hours +) $empsh[$emp][$sh+1] = 'free (+)'; # and have 2 shifts rest ( +16 hours) $empsh[$emp][$sh+2] = 'free (++)'; # (German law says 11 hour +s between shifts) $shcount[$sh]++; # no of worker per shift } foreach my $emp (@empsh) { # all other workers are unassigned on t +his shift $emp->[$sh] ||= ' - '; } } my %emplist = map { $_ => $_ } (1 .. 20); # workerlist, delete()able $oopser = 0; while (keys %emplist) { # check for remaining every worker (maybe add +"sort rand()" $oopser++; die if $oopser > 100; my @shnum = (); # no of shifts per worker for my $emp (keys %emplist) { foreach my $tag (@{ $empsh[$emp] }) { $shnum[$emp]++ if $tag =~ /^work/; # count his working shi +fts } if ($shnum[$emp] == 5) { # worked 5 shifts? delete $emplist{$emp}; } # (each worker has to work exactly 5 shifts a week) } EMP: foreach my $emp (keys %emplist) { # who has to work more? my $sh = 1; while ($empsh[$emp][$sh] ne ' - ') { # find a unassigned shift + for this worker if ($sh > 21) { # oops, none found? delete $emplist{$emp}; print STDERR "$emp has underwork\n"; next EMP; # re-run this program then } $sh++; } if ($shcount[$sh] >= 5) { # shift full $empsh[$emp][$sh] = 'free (shift full)'; } elsif ($empsh[$emp][$sh+1] =~ /^work/) { # no 2 shifts rest possible $empsh[$emp][$sh] = 'free (-)'; } elsif ($empsh[$emp][$sh+2] =~ /^work/) { # no 2 shifts rest possible $empsh[$emp][$sh] = 'free (--)'; } else { # hehe, work here! $empsh[$emp][$sh] = 'work (backup)'; $empsh[$emp][$sh+1] = 'free (+)'; $empsh[$emp][$sh+2] = 'free (++)'; } } } # now print print "<table border><tr><td>***"; for my $sh (1 .. 21) { print "<th>-$sh-"; } print "<td>"; for my $emp (1 .. 20) { print "<tr><th>", $emp; for my $sh (1 .. 21) { print "<td>", $empsh[$emp][$sh]; } } print "</table>";
    Yes, it's mess :-)

    Search, Ask, Know