Files
Odoo-Modules/fusion_clock/docs/superpowers/specs/2026-06-04-remove-planning-dependency-design.md
gsinghpal 9574fa0ae4 docs(fusion_clock): design spec — remove Odoo Planning dependency
Re-fit Planning's role + recurrence + send onto the native per-day
fusion.clock.schedule model; retire fusion_planning into fusion_clock;
make the family Community-installable. Full feature-parity matrix,
data migration, and gated Entech rollout included.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:29:27 -04:00

18 KiB
Raw Blame History

Remove the Odoo Planning dependency from the Fusion Clock family

Date: 2026-06-04 Module: fusion_clock (absorbs fusion_planning, which is retired) Status: Design — awaiting spec review


1. Goal

Make the Fusion Clock product family fully Community-installable (no Odoo Enterprise planning dependency) and simplify the Entech deployment, while preserving every scheduling capability currently delivered through Odoo Planning. No feature is removed; the work is sequenced, not trimmed.

Driver (confirmed): Both — ship to clients without Enterprise and cut the barely-used Planning Gantt out of Entech.

2. Current state (verified)

fusion_clock itself does not depend on planning. Its deps are clean: hr_attendance, hr, portal, mail, resource. The entire Odoo-planning coupling lives in one bridge module, fusion_planning:

Coupling point Where
depends: ['planning'] fusion_planning/__manifest__.py
_inherit = 'planning.slot' (+ x_fc_additional_resource_ids, auto-publish create()) fusion_planning/models/planning_slot.py
inherits planning.planning_view_form, uses planning.group_planning_manager fusion_planning/views/planning_slot_views.xml
uses hr.employee.default_planning_role_id / planning_role_ids, menu under planning.planning_menu_settings fusion_planning/views/hr_employee_role_views.xml
reads planning.slot for the portal Schedule tab (already merges with native schedule) fusion_planning/controllers/portal_schedule.py
the Planning Gantt backend UI (web_gantt, Enterprise) Odoo Planning

Live Entech data (LXC 111, DB admin, Enterprise):

Table Rows
planning.slot 8 (7 published)
planning.role 1
fusion.clock.schedule (native per-day planner) 144
fusion.clock.shift (native templates) 6

The native per-day planner (fusion.clock.schedule + the OWL shift planner) is the real workhorse. Odoo Planning is essentially vestigial here.

No other module in the repo references planning or fusion_planning (the one grep hit in fusion_plating is the English word "Planning" in a selection — noise).

3. Decision & rationale

Chosen approach: re-fit Planning's logic onto the native per-day model (fusion.clock.schedule), extended to full feature parity. Retire fusion_planning by folding everything into fusion_clock.

Rejected alternative: vendor planning.slot + planning.recurrency + planning.planning wholesale. Why rejected:

  1. The Gantt can't come to Community anyway. web_gantt is Enterprise. Vendoring planning.slot's datetime model still leaves us building a non-Gantt UI — so wholesale vendoring buys the heavy data model but not the UI.
  2. Bugs live in the attendance pipeline, not the recurrence engine. fusion_clock's penalties (money), overtime, absence detection, reminders, portal and dashboard all read one contract: hr.employee._get_fclk_day_plan() off fusion.clock.schedule. Option 1 leaves that pipeline's data source untouched and confines new code to isolated, testable features. Wholesale vendoring forces either a dual schedule model or a rewire of that money-critical pipeline onto Planning's datetime model plus a migration of the 144 live rows — the highest-risk change possible, in exactly the code we most need to keep correct.
  3. Reuse is still honoured. We copy the parts that copy cleanly (planning.role near-verbatim, the recurrence field design + repeat semantics, the mail templates) and re-fit only the generation loop — which is less code on the per-day model because it drops the resource-interval/DST math.

Two honest deltas vs Odoo Planning (only these):

  • The Gantt drag-drop board → replaced by the native weekly OWL planner. Capability preserved, UX differs. (Accepted by owner.) Drag-drop is a possible future enhancement, out of scope here.
  • Full resource-calendar-aware generation. Planning's recurrence consults resource work-intervals, flexible-resource flags and contract-end dates when generating. The native re-fit uses the employee weekday pattern and skips approved-leave days. This covers the real case; the heavy resource-calendar engine is overkill at Entech's scale (8 slots). Documented simplification.

4. End-state architecture

  • fusion_clock becomes self-contained and Community-installable. It owns: native scheduling (existing), roles, recurrence, publish/notify (send), the portal Schedule tab, and open / multi / overnight shift support. Manifest deps unchanged (hr_attendance, hr, portal, mail, resource) — crucially no planning.
  • fusion_planning is retired — its functionality is folded into fusion_clock, then it is uninstalled on Entech.
  • The attendance automation contract (_get_fclk_day_plan) is unchanged in shape; new schedule capabilities resolve into the same single per-day work-window it already returns (see §5.4).

5. Detailed design

5.1 New model: fusion.clock.role (copied from planning.role)

Near-verbatim copy of planning/models/planning_role.py:

  • name — Char, required, translate
  • color — Integer, default random 111
  • active — Boolean, default True
  • sequence — Integer
  • company_id — Many2one res.company (added for fusion multi-company consistency; Planning's role had none)
  • Copy _get_color_from_code(is_open_shift) → returns the fullcalendar-compatible hex used to colour shifts on the portal Schedule tab.
  • Drop resource_ids m2m and slot_properties_definition (unused here).

5.2 hr.employee — native role fields

  • x_fclk_default_role_id — Many2one fusion.clock.role (fills new shifts)
  • x_fclk_role_ids — Many2many fusion.clock.role (allowed roles)

(Migrated from default_planning_role_id / planning_role_ids.)

Employee Roles editor — port fusion_planning/views/hr_employee_role_views.xml to the native fields; reparent the menu from planning.planning_menu_settings to a fusion_clock config menu; gate with group_fusion_clock_manager.

5.3 fusion.clock.shift (existing template) — additions

  • role_id — Many2one fusion.clock.role (a template can carry a default role)

5.4 fusion.clock.schedule (existing per-day) — additions

Part A additions (ship with the core dependency removal; the UNIQUE(employee_id, schedule_date) one-shift/day constraint is kept):

  • role_id — Many2one fusion.clock.role; default from shift_id.role_id or employee_id.x_fclk_default_role_id. Drives portal colour/label.
  • recurrence_id — Many2one fusion.clock.schedule.recurrence (set when a rule generated the row); ondelete='set null'.

In Part A the attendance contract _get_fclk_day_plan is completely unchanged (one posted row per employee per day, exactly as today).

Part B additions (parity for currently-unused Planning capabilities; built after A — see §10):

  • is_open — Boolean; an open / unassigned shift available for self-assign.
  • crosses_midnight — Boolean (overnight support).
  • Constraint changes: replace the hard UNIQUE(employee_id, schedule_date) with a partial unique that still forbids accidental duplicate assigned rows while allowing intentional multiple shifts/day. Exact predicate finalised in the plan; use models.Constraint / models.UniqueIndex per Odoo-19 rules. employee_id becomes not required only when is_open = True (enforced by a Python @api.constrains).
  • Overnight: relax _check_schedule_times to permit end_time <= start_time as crossing midnight (set crosses_midnight); update _compute_planned_hours ((24 - start) + end - break) and _get_fclk_scheduled_times (out datetime is next day).

The attendance contract stays single-window in Part B too. _get_fclk_day_plan(date) still returns one plan per employee per day:

  • 0 assigned rows → not scheduled (unchanged).
  • 1 assigned row → that row (unchanged).
  • N assigned rows for the day → resolve to one work-window = earliest start → latest end across that day's assigned shifts; break = sum of breaks. This keeps penalties/overtime/absence math unchanged in shape while letting managers schedule split shifts and employees see each shift on the portal.
  • is_open rows never feed any employee's plan until self-assigned.

This is the key safety property: multi-shift / overnight / open-shift live in the scheduling + UI + portal layers; the money-critical attendance layer keeps its existing one-window contract.

5.5 New model: fusion.clock.schedule.recurrence (design copied from planning.recurrency)

Fields (copied semantics):

  • repeat_interval — Integer, default 1, CHECK(repeat_interval >= 1)
  • repeat_unit — Selection day/week/month/year, default week
  • repeat_type — Selection forever/until/x_times, default forever
  • repeat_until — Date (required when repeat_type='until')
  • repeat_number — Integer (>= 0)
  • last_generated_date — Date, readonly
  • company_id — Many2one res.company
  • schedule_ids — One2many fusion.clock.schedule

Generation (_generate(stop_date=False)), re-fit of _repeat_slot onto the per-day model — much simpler (no resource-interval/DST math):

  • Seed = the schedule entry the rule was created from (employee, weekday, start/end/break/role).
  • Emit per-day fusion.clock.schedule rows at the cadence (repeat_interval × repeat_unit) up to a horizon = min(repeat_until, today + company.fclk_planning_generation_months, repeat_number cap).
  • Skip dates the employee has an approved fusion.clock.leave.request (the "resource-calendar-aware" simplification).
  • Generated rows are created in draft (must be posted/published to drive automation), carrying recurrence_id.
  • Idempotent via last_generated_date (never regenerate past rows).
  • _stop(from_date) deletes future draft rows of the rule (copy of Planning's _delete_slot); posted rows are kept.

Cron _cron_generate_recurring_schedules (copy of Planning's _cron_schedule_next shape) rolls the horizon forward. Odoo-19: no numbercall; active=True recurring cron.

5.6 Manager UI — native OWL planner extensions

The existing weekly planner (fusion_clock_shift_planner.js/xml + controller shift_planner.py) gains, alongside its current toolbar (Prev/This/Next week, Copy Previous Week, Export XLSX, Save, Post Schedule):

  • Role shown/edited per cell (colour chip from role_id._get_color_from_code).
  • Repeat… control in the cell editor → creates a fusion.clock.schedule.recurrence for that cell and generates rows; a backend list view manages/stops recurrences.
  • Publish & Notify (generalises the existing post_week): pick a date range (default current week) + optional employee subset + optional message → posts matching draft rows and emails each affected employee their posted shifts for the range (see §5.8).
  • Open shifts lane + bulk apply ("Apply Also To" replacement): create an open shift, or apply one cell's shift to several selected employees in one go.

All planner endpoints stay gated by group_fusion_clock_manager (unchanged).

5.7 Portal — fold the Schedule tab into fusion_clock

  • Move the controller /my/clock/schedule into fusion_clock (controllers/portal_clock.py or a new portal_schedule.py), reading only fusion.clock.schedule (drop the planning.slot branch). Role colour/label come from role_id.
  • Move the template fusion_planning.portal_schedule_pagefusion_clock.portal_schedule_page.
  • Add the Schedule nav button inline in each fusion_clock portal page's .fclk-nav-bar (clock, timesheets, reports, payslips list, payslip detail), replacing fusion_planning's cross-module xpath inherits. Keep the .fclk-nav-bar structure stable (no shared-template refactor — see the known Odoo-19 xpath-inheritor gotcha).
  • Self-assign / unassign of open shifts on the portal (respect a company "days before shift" setting, mirroring Planning's allow_self_unassign).

5.8 Mail templates (copied, reworded)

Port Planning's "send schedule" templates (planning/data/mail_template_data.xml) as the basis for fusion_clock's publish/notify email; reword for the Fusion portal link. The native fusion.clock.schedule.fclk_email_posted_week() is generalised to fclk_email_posted_range(employee, start, end). Follow Odoo-19 mail.template rules (no url_encode in QWeb; ctx is env.context).

5.9 Security & menus

  • fusion.clock.role + fusion.clock.schedule.recurrenceir.model.access.csv (manager write, user read as needed) + appropriate ir.rules.
  • Employee Roles editor menu + a Recurrences menu under the fusion_clock configuration menu, gated group_fusion_clock_manager.

6. Data migration & retirement

6.1 Migration (in fusion_clock, guarded, idempotent)

A post-migration step (new module version) that runs only where Planning data exists (if 'planning.role' in env / 'planning.slot' in env):

  1. Roles: each planning.role → find-or-create fusion.clock.role (name + color). Build an id map.
  2. Employee roles: default_planning_role_idx_fclk_default_role_id; planning_role_idsx_fclk_role_ids (via the map).
  3. Slots: each planning.slotfusion.clock.schedule: resource_id→employee, local date + local float start/end (employee tz), break derived from span vs allocated_hours, role_id via map, state = posted if published else draft. Unusual slots (overnight / multi / open) handled by the §5.4 rules; anything unexpected is logged, not dropped. Idempotent via a one-time ir.config_parameter marker.

Volume is tiny (8 slots, 1 role) — fast and low-risk.

6.2 Retire fusion_planning

After fusion_clock provides the Schedule tab + roles + migration, uninstall fusion_planning (its portal templates, nav xpath-inherits and the x_fc_additional_resource_ids m2m are removed). fusion_clock now owns the Schedule tab inline. Optionally uninstall planning / web_gantt afterwards (separate, gated cleanup — destructive, so done last and only on sign-off).

6.3 Community-install guarantee

After the change, docker exec odoo-modsdev-app odoo -d fusion-dev -u fusion_clock must install on Community with no planning present (the migration is guarded; no runtime code references planning.*). Add this as a smoke check.

7. Entech rollout (gated, revert-on-failure)

  1. Backup DB + module dir (outside the addons path).
  2. Clone-verify: clone admin → upgrade fusion_clock (+migration) on the clone → assert: 144 native rows intact, 8 slots + 1 role migrated, roles + recurrence + portal Schedule render, attendance/penalty tests green.
  3. Prod upgrade fusion_clock (stop → -u → start only if RC==0 + "Modules loaded", else restore backup, no restart). Clear asset bundle attachments; restart.
  4. Uninstall fusion_planning.
  5. Optional: uninstall planning / web_gantt (final, on sign-off).

8. Feature-parity matrix

Planning feature Preserved as
Assign shifts, weekly board Native OWL planner (extended)
Gantt drag-drop timeline → native weekly planner (Gantt can't be Community)
Shift templates fusion.clock.shift (exists) + role_id
Roles + colour fusion.clock.role (copied) + portal colour
Employee default/allowed roles x_fclk_default_role_id / x_fclk_role_ids + editor
Recurrence (N day/week/month/year; forever/until/N-times) + cron fusion.clock.schedule.recurrence (copied design)
Send / publish + email Publish & Notify over a range (copied templates)
Multiple shifts/day per-day model + single work-window contract (§5.4)
Overnight shifts crosses_midnight (§5.4)
Open shifts + self-assign/unassign is_open + portal self-assign
Auto-publish on create native option (kept)
"Apply Also To" multi-employee native bulk-apply
Allocated hours, portal My Schedule planned_hours; Schedule tab folded in
Attendance/penalty/overtime/absence UNCHANGED (per-day contract preserved)
Resource-calendar-aware generation simplified: weekday pattern + skip leave

9. Testing strategy

  • Unit: role + colour; recurrence generation across each repeat_type/unit; _stop deletes future drafts only; publish-range posts + emails; migration maps roles/slots/employee-roles; overnight planned_hours + scheduled_times; open-shift self-assign; multi-shift day-plan → correct single work-window.
  • Regression: existing attendance / penalty / overtime / absence / dashboard tests stay green (data source unchanged).
  • Community smoke: install fusion_clock on Community modsdev (no planning).
  • Odoo-19 test runner: --http-port=0 --gevent-port=0, --test-tags /fusion_clock.

10. Sequencing

Decision: Part A and Part B ship together in one release (full Planning parity at once). The A/B labels below are internal build phases for the implementation plan (so each gets its own review checkpoint), not separate deployments.

  • Phase A: drop the planning dep with parity for everything in real use — roles, recurrence, publish/notify, portal fold-in, migration, retire fusion_planning. (Per-day model keeps one-shift/day here.)
  • Phase B: the remaining Planning capabilities — multi-shift/day, overnight, open shifts + self-assign, "Apply Also To" bulk — using the safe single-window attendance contract; isolated from the attendance engine.

Both phases are validated together before the single Entech rollout in §7.

11. Risks & open questions

  • Recurrence correctness is new code — mitigated by isolation + unit tests across every repeat_type/unit and the idempotent last_generated_date guard.
  • Multi-shift day-plan resolution (§5.4) is the subtlest change; covered by a dedicated test asserting the work-window and that penalties are unaffected.
  • Licensing: the role model is generic, the recurrence loop is re-fit/original, and mail templates are reworded — so near-verbatim Enterprise code is minimal. Flag for the resale build; owner's call.
  • Resolved: Parts A and B ship together in one release (§10).

12. Out of scope

  • Drag-and-drop on the native planner (future enhancement).
  • Full resource working-interval / flexible-resource / contract-end recurrence math (deliberate simplification, §3).
  • Uninstalling planning from Entech is optional and gated separately.