# Baseline vs Delta — Design Issue

## Problem

`original_hours` in `llx_planning_schedule` is intended as an immutable baseline
(set once on first Calc, never overwritten). The delta badge on the capacity grid
shows `planned_hours - original_hours` per day/row.

### What breaks for In Progress MOs with partial completion

Scenario:
```
First Calc (qty=20, done=0):
  INSERT rows with original_hours = planned_hours = e.g. 6h/day
  MO projected finish: 10/04

Night cron after partial work (qty=20, done=12, remaining=8h):
  DELETE future rows (>= today)          ← past rows kept (mosWithWork)
  INSERT new rows from today onward
    original_hours = planned_hours = 8h  ← NEW baseline, stale
  MO projected finish: 13/04

Delta badge on new rows: 8h - 8h = 0    ← says "no change"
Reality: MO is 3 days behind original plan
```

Root cause: when rows are deleted and re-inserted (because `schedule_date >= today`),
the new rows have no memory of what `original_hours` was before the MO fell behind.

There is a partial guard (`startDateShifted` → reset baseline when start date moves),
but it only handles the case where a **locked predecessor** shifts the schedule —
it does not handle the common case where the MO is In Progress and simply running late.

---

## Current behavior summary

| State | original_hours | planned_hours | delta |
|---|---|---|---|
| First Calc, not started | = planned | = planned | 0 ✓ |
| Recalc, not started, no shift | kept from prior | updated | shows real delta ✓ |
| Recalc, not started, start shifted | reset to planned | updated | 0 (expected after deliberate reschedule) ✓ |
| **Recalc, In Progress, partial done** | **reset to remaining** | **= remaining** | **0 ✗** |

---

## Options

### Option A — Simple: stop resetting original_hours on delete/re-insert

Do not reset `original_hours` even when `startDateShifted`. Always use old value if available.

**Problem**: old `original_hours` rows are for different dates. After a schedule shift,
the new rows (different dates) won't match any old key in `existingOriginals[fk_mo][date]`
→ falls back to planned_hours anyway. So this option doesn't actually help for In Progress MOs.

### Option B — Recommended: baseline_end_date per MO (new extrafield)

Add `baseline_end_date` (datetime, nullable) to `llx_mrp_mo_extrafields`.

Rules:
- Set **once** by Calc when MO transitions from "not in schedule" to "has rows" (first Calc).
- Never overwritten by subsequent Calcs.
- Reset to NULL manually by planner when they deliberately reschedule.

Display:
- Board tooltip: `Baseline: 10/04/2026` + `Current end: 13/04/2026` + `Δ +3d`
- Capacity grid: optional late indicator color shift when `current > baseline`

**Scope**: ~1 SQL column, minor change in calc_engine INSERT logic, tooltip render addition.

### Option C — baseline_hours_total per MO

Store total planned hours at first Calc (sum of all `planned_hours` rows for this MO).
Delta = `current_total_hours - baseline_total_hours` expressed as hours gained/lost.

Simpler than Option B but less intuitive — hours delta is harder to read than date delta.

---

## Recommendation

**Option B** — `baseline_end_date` per MO.

It directly answers the planner's question:
> "When was this MO supposed to finish, and how late is it now?"

Implementation steps:
1. Add `baseline_end_date` column to `llx_mrp_mo_extrafields` (migration)
2. In `calc_engine.php` — after computing `moDateMap`, for each MO:
   - If `baseline_end_date IS NULL` → set it to `current end_date`
   - If `baseline_end_date IS NOT NULL` → leave it unchanged
3. Board tooltip — show baseline vs current when they differ
4. Planning page — optional: highlight MOs where `current_end > baseline_end`
5. Admin: manual "reset baseline" button per MO or per WS

---

## Related

- `DESIGN_buffer_changeover.md` — changeover integration into calc
- `calc_engine.php` — `startDateShifted` guard (lines ~420-425)
- `llx_planning_schedule` — `original_hours`, `planned_hours`, `is_frozen` columns
