# The dead list

**Portal:** Manage · **What this is:** everything in this module that still exists but is no longer
load-bearing — and, for each, whether removing it is safe.

Part of the [Engagements & Bookings handbook](/docs/modules_handbook/manage/engagement/readMe.md).

## Why this file exists

Four separate readers auditing this module independently rediscovered the same corpses. That is the
signal they belong in the handbook rather than in each reader's head. The cost of not writing them
down is that the next reform either **preserves a corpse** (because nobody could prove it was dead)
or **deletes something still load-bearing** (because a name looked retired).

Every entry below was verified by opening the code, not inferred from a name.

## Columns that still exist

| Column | Who still WRITES it | Who READS it | Safe to drop? |
|---|---|---|---|
| **`bookings.spa_status`** (uint, NOT NULL, default Pending, **indexed**) | **only** `BookingRepository::importLegacy()` — and `create()` / `update()` deliberately omit it from their whitelists | **nothing.** `Booking::spaState()` derives the answer from the deal's own facts | **Yes, with a migration** — but drop the importer's write in the same change. Pinned dead by `SalesProjectsIndexTest::test_the_stored_spa_status_column_is_ignored` |
| **`bookings.closer_admin_id`** (nullable, indexed, `$fillable`, has a `closer()` relation) | **nobody.** No repository method passes it and no request accepts it | the `closer()` relation exists but is unused; `IdentityChildMap` classifies it `MERGE_SKIP / PURGE_SKIP` | **Not yet** — it is deliberately kept as historical data. The `2026_08_03_300001` docblock says so explicitly when it retired `is_closer_role` |
| **`bookings.commission`** — the old ABSOLUTE override | **nobody.** Removed from every request, whitelist, payload and controller mapping by the 2026-08-10 adjustment change | ⚠️ still **honoured** by `Booking::commissionAt()`, the booking-list sort, the pipeline total and the Sales dashboard, as a legacy value that wins outright | **Not yet.** `2026_08_10_100001` converted every convertible row into a `commission_adjustment` and nulled the column, but a row whose auto figure cannot be computed (no project rate, or no price) has no formula to be an adjustment *on* and keeps its absolute value. Drop it once production confirms none remain — the local snapshot has **0** |
| **`bookings.leader_review_result`** | `BookingRepository::update()` (the modal's field, edit only) | display only — the accessor, the two payloads and the export column | **Keep** — it is not dead, but ⚠️ **it gates no logic anywhere.** Both the `Booking` class docblock and the create-migration docblock claim "a booking cancelled after an unsaved leader review pushes its engagement to Lost"; in code **every** cancel pushes to Lost |

## Constants that cannot be reached

| Constant | Why |
|---|---|
| **`Booking::SPA_NA` (1)** | `spaState()` can only return `2 Pending`, `3 Signed` or `4 Not Signed`. Nothing writes 1 either. It survives so the `SPA_STATUSES` map is complete — leave it, but do not write code that expects it |
| **`Engagement::STATUS_NEGOTIATING` (5)** and **`STATUS_FOLLOWING_UP` (7)** | ⚠️ **Not dead data — dead as a *selection*.** They are absent from `STATUSES`, so `StatusRequest`'s `Rule::in` refuses them; they remain in `ALL_STATUSES` (which the label/colour accessors read) and fold through `COLUMN_STATUS` into With Closer / Booked for counts and board columns. Legacy rows still hold them, and ⚠️ **the legacy CSV importer can still mint one**, because its lookup is built from `ALL_STATUSES` on purpose. See [lifecycle.md](/docs/modules_handbook/manage/engagement/lifecycle.md) |

⚠️ `STATUS_FOLLOWING_UP` is also still **booking-bearing** (it is in
`ChangeEngagementStatus::BOOKING_STATUSES`), so a legacy row on it behaves like a Booked one.

## Columns that are gone — and the code that still mentions them

Dropped by `2026_07_31_100001_retire_legacy_price_and_dead_columns_on_bookings`:

| Column | Note |
|---|---|
| **`bookings.price`** | its values were **backfilled into `net_price`** first, which (being the default commission basis) left the derivation unchanged |
| **`bookings.floor_plan_id`** | pointed at the retired `flg_floor_plans` table and held no data; the modal's field of that name maps to `catalog_floor_plan_id` |
| **`bookings.commission_rate`** | reserved for the legacy import but never written — the rate always comes from the project |

⚠️ **Two live references to `bookings.price` survive, both harmless but misleading:**

- `Manage\Sales\DashboardController` reads `$b->price` in a `?? 0` fallback chain — a missing
  attribute is null, so the chain just falls through.
- `PipelineTab.vue` renders `e.booking.price` behind a `v-if` — the payload never sends the key, so
  the price line on the Lead Show pipeline card is **permanently hidden**. Dead markup, not a crash.

Dropped elsewhere:

| Column | Migration | Note |
|---|---|---|
| `engagements.caller_admin_id` / `closer_admin_id` / `followup_admin_id` | `2026_08_02_100001` | backfilled into `engagement_assignments` rows first |
| `pipeline_roles.is_entry` | `2026_08_03_300001` | retired **with its feature** — the Open Pipeline assignee field, replaced by the per-role `default_admin_id` standing team |
| `pipeline_roles.is_closer_role` | `2026_08_03_300001` | retired **with its features** — the booking list's Closer sort + filter and the closer-of-record default. *"the boss deemed them not worth the concept"* |
| `projects.unit_types` | `2026_06_14_100001` | pre-dates this module |

## A table that no longer exists

**`booking_commission_splits`** — created by `2026_07_30_100006`, **dropped four days later** by
`2026_08_03_100001`. It never gained a single production row; a teammate's cut moved to
`engagement_assignments.role_share`.

⚠️ **A reader tracing migrations in date order hits a `CREATE` for a table that does not exist.**
That is expected, not a broken migration. The drop migration's own note explains it: *"every real
figure derives from `engagement_assignments` × the role sets."*

**There is no stored split table, and there is not meant to be one.** See
[commission.md](/docs/modules_handbook/manage/engagement/commission.md).

## Frontend dead code

| Thing | Status | Evidence |
|---|---|---|
| **`splitPreviewRows()`** in [`closingModes.js`](/resources/js/utils/closingModes.js) | **exported, never imported.** `CommissionSplitEditor.vue` computes its own `ofTotal()`; the only other occurrence in the whole `resources/js` tree is a **comment** in that file naming it | ⚠️ Its documented divergence from PHP (an unheld pool yields a name-less row carrying the whole pool, where PHP emits no row at all) therefore describes a function nothing calls — **not** a live inconsistency to go and fix |
| **The `stages` Inertia prop** on `SalesProjects/Index.vue` and `Show.vue` | **declared and never used.** Both controllers still send `Engagement::STAGES`; neither page references `stages` anywhere beyond the `defineProps` line | Safe to drop from both the props and the two controller payloads |

⚠️ **`PipelineBoard.vue` is NOT dead**, despite living under `Pages/Manage/SalesProjects/Partials/`.
It is imported by `Pages/Manage/Sales/Pipeline.vue` — the kanban board simply moved pages without
moving files.

## Stale comments — how a corpse gets resurrected

A stale comment is worse than a stale doc: it is read *while editing the thing it describes*. These
are the ones found in this module, and they should be fixed alongside any change to the code they
sit on.

| Where | Says | Truth |
|---|---|---|
| `src/Engagement/Booking.php` (the `spaState()` docblock) | *"like `bookings.commission_rate`"* | that column no longer exists — it was dropped on 2026-07-31 |
| `src/Engagement/Engagement.php` (class docblock) | *"five pipeline roles"* | six are seeded, and the vocabulary is admin-editable |
| `src/Engagement/Engagement.php` (two helper docblocks) and `2026_08_02_100001` | *"an `EngagementAssignment::ROLE_*` key"* | **no such constants exist** — roles are `pipeline_roles` rows |
| `SalesProjectsController::STATUS_GROUPS` docblock | *"every one of the 9 statuses is still its own chip"* | it yields **7** chips; 5 and 7 fold in via `COLUMN_STATUS` |
| `SalesProjectsController::BOOKING_SORTS` (`updated`) | *"every write to the engagement touches it"* | `last_activity_at` has exactly four writers; the receipt lanes, the team roll and booking-only edits do **not** bump it |
| `SalesProjectsController::index()` | *"the booking list is this page's DEFAULT (newest booking first)"* | the default is **most recently touched**, changed precisely because booking-date ordering buried new work |
| `2026_07_14_100001` (`lost_stage` column comment) | *"(caller/closer/followup)"* | the value stored today is `appointment` — renamed and backfilled on 2026-08-02 |
| `EngagementsController` class docblock | *"the Caller -> Closer -> Follow-Up pipeline"* | the stage is named **Appointment** now |
| `EngagementRepository::changeStatus()` docblock | *"the required cancellation remark"* | the remark is written only when non-null; the requirement lives in `StatusRequest`, and the repository accepts null |
| `Booking.php` class docblock + the create migration | *"a booking cancelled after an unsaved leader review pushes its engagement to LOST"* | **every** cancel pushes to Lost; `leader_review_result` gates nothing |

## Four bugs that were here — fixed 2026-08-10

Not dead code: working code with a wrong outcome. Recorded because each fix is a rule somebody could
undo without realising, and every one of them failed **silently** — no error, no exception, just a
wrong row.

| Was | Now | Pinned by |
|---|---|---|
| **Remove → re-Add resurrected a hollow Booked deal.** `delete()` soft-deletes the bookings with the engagement; `open()` restored only the engagement and kept its status | `open()` **resets a restored row to `NEW`** and clears the loss record. A live row is still returned untouched | `EngagementLifecycleTest::test_re_adding_a_removed_deal_starts_over_at_new` |
| **A booking-less deal silently discarded a chosen closing mode**, while still settling its receipt against that vanished mode | `ApplyClosingChoice` acts only on a mode the derivation can **reproduce** (the two defaults); the picker is hidden there and the derived mode shown read-only | `CommissionShareTest::test_an_unreproducible_mode_on_a_deal_with_no_booking_is_ignored` |
| **`booking_fee` could not round-trip** — validated `integer`, stored `decimal(12,2)`, so re-saving a booking that had a fee was refused | validated **`numeric`** in all three requests | `EngagementLifecycleTest::test_a_booking_fee_with_cents_round_trips` |
| **Editing a booking from the Lead Show Pipeline tab wiped a manual commission** — `bookingCard()` sent `commission_value` but not the raw figure, so the modal hydrated blank and the save wrote null | `bookingCard()` sends the full commission set (`commission_auto`, `commission_adjustment`, `commission_adjustment_reason`) | `EngagementLifecycleTest::test_the_lead_pipeline_payload_carries_the_commission_and_its_adjustment` |

The rules behind them are written up where they bite:
[lifecycle.md](/docs/modules_handbook/manage/engagement/lifecycle.md) ·
[closing-modes.md](/docs/modules_handbook/manage/engagement/closing-modes.md) ·
[booking.md](/docs/modules_handbook/manage/engagement/booking.md).

## Related chapters
[lifecycle.md](/docs/modules_handbook/manage/engagement/lifecycle.md) ·
[booking.md](/docs/modules_handbook/manage/engagement/booking.md) ·
[commission.md](/docs/modules_handbook/manage/engagement/commission.md) ·
[closing-modes.md](/docs/modules_handbook/manage/engagement/closing-modes.md) ·
[perimeter.md](/docs/modules_handbook/manage/engagement/perimeter.md)
