feat(fusion_clock): native recurring shifts engine [A4-A5]

fusion.clock.schedule.recurrence (repeat every N day/week/month/year;
forever/until/N-times) re-fit from planning.recurrency onto per-day rows;
daily generation cron; _fclk_on_leave skip; planner Repeat…/Stop-repeat
UI + endpoints; recurrence + role indicators on cells.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 20:49:26 -04:00
parent b4ca85e291
commit 734b3b94fd
12 changed files with 591 additions and 0 deletions

View File

@@ -377,6 +377,7 @@ class FusionClockSchedule(models.Model):
'role_id': schedule.role_id.id or False,
'role_name': schedule.role_id.name or '',
'role_color': schedule.role_id._get_color_from_code() if schedule.role_id else '',
'recurring': bool(schedule.recurrence_id),
}
plan = employee._get_fclk_day_plan(date_obj)
@@ -399,6 +400,37 @@ class FusionClockSchedule(models.Model):
'role_color': '',
}
@api.model
def fclk_attach_recurrence(self, schedule, repeat_vals):
"""Attach a recurrence rule to a seed schedule cell and generate it
forward. ``repeat_vals`` mirrors the recurrence fields."""
schedule = schedule.sudo()
if not schedule:
raise ValidationError(_("Pick a shift to repeat first."))
rule = self.env['fusion.clock.schedule.recurrence'].sudo().create({
'repeat_interval': int(repeat_vals.get('repeat_interval') or 1),
'repeat_unit': repeat_vals.get('repeat_unit') or 'week',
'repeat_type': repeat_vals.get('repeat_type') or 'forever',
'repeat_until': repeat_vals.get('repeat_until') or False,
'repeat_number': int(repeat_vals.get('repeat_number') or 1),
'company_id': schedule.company_id.id or self.env.company.id,
})
schedule.recurrence_id = rule.id
rule._generate()
return rule
@api.model
def fclk_clear_recurrence(self, schedule):
"""Detach + stop the recurrence on a seed cell (keeps posted rows)."""
schedule = schedule.sudo()
rule = schedule.recurrence_id
if rule:
rule._stop(fields.Date.today())
schedule.recurrence_id = False
if not rule.schedule_ids:
rule.unlink()
return True
@api.model
def fclk_email_posted_week(self, employee, week_start, week_end):
"""Email one employee a summary of their POSTED shifts for the week."""