Flow
What I want the planner to do
1Spot no room
When the latest start is before the earliest, treat the shift as one that cannot be placed.
2Say why
Return that shift with a reason instead of a made-up time.
3Round inward
Round the earliest limit up and the latest down before picking a time.
4Check the plan
Every break inside the shift, in order, not overlapping, from both save paths.
5Keep the batch
Save the shifts that fit and hand back the ones that do not.
Restoring the break planner to my shift scheduler happened this week. Reading the code, I found a way for it to give someone a break that ends after their shift is over.
Squeezing each break between two limits
Each shift gets three breaks: a short one, lunch, then another short one. For each break, the planner picks a target time. It works out the earliest the break can start and the latest it can start and still leave room for the rest. It pushes the target in between those two. Then it rounds to a neat grid of minutes.
A short shift leaves the limits backwards
On a short shift, the latest start comes before the earliest start. There's no good time at all. The code doesn't treat that as a problem. It just picks the earliest time and moves on:
if hi < lo:
hi = lo # no room, so take the earliest
That answer looks fine, but it's wrong. The lunch then starts after that first break, which already ate the room, so it has no room either, and the last break is pushed back the same way. As far as I can tell from the math, the last break ends up running past the end of the shift. I haven't tried a short shift yet.
The rounding adds a bit more. It happens last, so it can push a time a few minutes past its limit even when there was room.
Two paths, two rules
A manager can also set breaks by hand. That path checks that every break is inside the shift and says no if it isn't. The planner's save path doesn't check. So the planner can save a break the hand-edit screen would refuse.
The table also doesn't store which break is lunch. When it reads breaks back, it guesses from which ones are paid. On a broken plan that guess can be wrong.
Saying so per shift
If there's no room, the planner should say so for that shift and give a reason, not make up a time. Round the limits inward before using them, so rounding can't step outside. And run one check on the finished plan, from both paths, before saving: every break inside the shift, in order, not overlapping.
If one shift in a big batch can't fit, I'd rather save the rest and hand back that one with its reason than throw out the whole batch.