Files
Odoo-Modules/fusion_clock/views/clock_schedule_views.xml
gsinghpal 2aaa1a57e7 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>
2026-05-30 21:54:05 -04:00

135 lines
5.8 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_fusion_clock_shift_planner" model="ir.actions.client">
<field name="name">Shift Planner</field>
<field name="tag">fusion_clock.ShiftPlanner</field>
</record>
<record id="view_fusion_clock_schedule_list" model="ir.ui.view">
<field name="name">fusion.clock.schedule.list</field>
<field name="model">fusion.clock.schedule</field>
<field name="arch" type="xml">
<list>
<field name="schedule_date"/>
<field name="employee_id"/>
<field name="department_id"/>
<field name="is_off"/>
<field name="shift_id"/>
<field name="start_time" widget="float_time"/>
<field name="end_time" widget="float_time"/>
<field name="break_minutes"/>
<field name="planned_hours"/>
<field name="state" widget="badge" decoration-success="state == 'posted'" decoration-warning="state == 'draft'"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
<record id="view_fusion_clock_schedule_form" model="ir.ui.view">
<field name="name">fusion.clock.schedule.form</field>
<field name="model">fusion.clock.schedule</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="employee_id"/>
<field name="schedule_date"/>
<field name="is_off"/>
<field name="shift_id"/>
</group>
<group>
<field name="start_time" widget="float_time"/>
<field name="end_time" widget="float_time"/>
<field name="break_minutes"/>
<field name="planned_hours" readonly="1"/>
</group>
</group>
<group>
<field name="note"/>
<field name="state"/>
<field name="posted_date" readonly="1"/>
<field name="department_id" readonly="1"/>
<field name="company_id" readonly="1" groups="base.group_multi_company"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_fusion_clock_schedule_search" model="ir.ui.view">
<field name="name">fusion.clock.schedule.search</field>
<field name="model">fusion.clock.schedule</field>
<field name="arch" type="xml">
<search>
<field name="employee_id"/>
<field name="department_id"/>
<field name="schedule_date"/>
<filter name="off" string="OFF" domain="[('is_off', '=', True)]"/>
<filter name="working" string="Working" domain="[('is_off', '=', False)]"/>
<separator/>
<filter name="posted" string="Posted" domain="[('state', '=', 'posted')]"/>
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
<filter name="group_department" string="Department" context="{'group_by': 'department_id'}"/>
<filter name="group_date" string="Date" context="{'group_by': 'schedule_date'}"/>
</search>
</field>
</record>
<record id="action_fusion_clock_schedule" model="ir.actions.act_window">
<field name="name">Scheduled Shifts</field>
<field name="res_model">fusion.clock.schedule</field>
<field name="view_mode">list,form</field>
</record>
<record id="view_fusion_clock_schedule_audit_list" model="ir.ui.view">
<field name="name">fusion.clock.schedule.audit.list</field>
<field name="model">fusion.clock.schedule.audit</field>
<field name="arch" type="xml">
<list create="0" edit="0" delete="0">
<field name="changed_at"/>
<field name="employee_id"/>
<field name="schedule_date"/>
<field name="old_value"/>
<field name="new_value"/>
<field name="changed_by_id"/>
<field name="department_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</list>
</field>
</record>
<record id="view_fusion_clock_schedule_audit_form" model="ir.ui.view">
<field name="name">fusion.clock.schedule.audit.form</field>
<field name="model">fusion.clock.schedule.audit</field>
<field name="arch" type="xml">
<form create="0" edit="0" delete="0">
<sheet>
<group>
<group>
<field name="changed_at"/>
<field name="changed_by_id"/>
<field name="employee_id"/>
<field name="schedule_date"/>
</group>
<group>
<field name="old_value"/>
<field name="new_value"/>
<field name="department_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_fusion_clock_schedule_audit" model="ir.actions.act_window">
<field name="name">Schedule Audit</field>
<field name="res_model">fusion.clock.schedule.audit</field>
<field name="view_mode">list,form</field>
</record>
</odoo>