feat(fusion_clock): schedule-driven attendance automation
Reminders, absence detection, late/early penalties, and auto-clock-out are now driven by each employee's real schedule (posted planner entry -> recurring shift), never the global 9-5 default. Employees who aren't scheduled get no reminders/absence. Overtime past the scheduled end is never cut off — auto clock-out only fires at a max-shift safety cap (default raised 12 -> 16h). Team leads build the planner in draft and Post it (publishes + emails employees). - hr.employee._get_fclk_day_plan: explicit `scheduled` flag; posted-only planner entries (drafts ignored), else recurring shift covering that weekday, else not-scheduled; sources 'schedule'/'shift'/'none'. - fusion.clock.shift: day_mon..day_sun weekday pattern + covers_weekday(). - fusion.clock.schedule: draft/posted state + posted_date; planner edits reset to draft; fclk_email_posted_week notification. - Rewrote the reminder / absence / auto-clock-out crons: schedule-gated, per-employee savepoints, OT-aware cap, weekend hardcode removed. - Penalties + all three clock-in paths skip days the employee isn't scheduled. - shift_planner: Post Week route + planner Post button + draft count. - Migration backfills pre-existing schedule entries to 'posted' so they keep driving automation after upgrade. - Tests: resolver matrix, cron gating, OT cap; fixed the existing planner test for the new state/source semantics. Design: docs/superpowers/specs/2026-05-30-schedule-driven-attendance-design.md Frontend footprint kept at zero to avoid colliding with the concurrent employee-portal (payslips) work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,17 @@ class FusionClockShift(models.Model):
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
color = fields.Char(string='Color', default='#3B82F6')
|
||||
|
||||
# Weekday pattern — which days this recurring shift applies as the baseline
|
||||
# when there is no posted planner entry for the day. Default Mon-Fri.
|
||||
day_mon = fields.Boolean(string='Mon', default=True)
|
||||
day_tue = fields.Boolean(string='Tue', default=True)
|
||||
day_wed = fields.Boolean(string='Wed', default=True)
|
||||
day_thu = fields.Boolean(string='Thu', default=True)
|
||||
day_fri = fields.Boolean(string='Fri', default=True)
|
||||
day_sat = fields.Boolean(string='Sat', default=False)
|
||||
day_sun = fields.Boolean(string='Sun', default=False)
|
||||
|
||||
employee_ids = fields.One2many(
|
||||
'hr.employee',
|
||||
'x_fclk_shift_id',
|
||||
@@ -56,6 +67,17 @@ class FusionClockShift(models.Model):
|
||||
for rec in self:
|
||||
rec.employee_count = len(rec.employee_ids)
|
||||
|
||||
def covers_weekday(self, date):
|
||||
"""Return True if this recurring shift applies on the given date's
|
||||
weekday (Mon=0 .. Sun=6)."""
|
||||
self.ensure_one()
|
||||
date_obj = fields.Date.to_date(date)
|
||||
if not date_obj:
|
||||
return False
|
||||
days = (self.day_mon, self.day_tue, self.day_wed, self.day_thu,
|
||||
self.day_fri, self.day_sat, self.day_sun)
|
||||
return bool(days[date_obj.weekday()])
|
||||
|
||||
@property
|
||||
def scheduled_hours(self):
|
||||
"""Return the scheduled work hours for this shift (excluding break)."""
|
||||
|
||||
Reference in New Issue
Block a user