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>
This commit is contained in:
@@ -0,0 +1,366 @@
|
|||||||
|
# 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 1–11
|
||||||
|
- `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_page` →
|
||||||
|
`fusion_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.recurrence` → `ir.model.access.csv`
|
||||||
|
(manager write, user read as needed) + appropriate `ir.rule`s.
|
||||||
|
- 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_id` → `x_fclk_default_role_id`;
|
||||||
|
`planning_role_ids` → `x_fclk_role_ids` (via the map).
|
||||||
|
3. **Slots:** each `planning.slot` → `fusion.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.
|
||||||
Reference in New Issue
Block a user