- B.1: removed running_cost from PB monthly_income schema - B.2: 40 of 40 machines now have income (was 32/40) - B.3: removed syntheticCode() + auto-create branch; deleted 2 ghost Z* assets; seed fails loudly on unmatched - C.1: collapsed 13 cost-component rows into 5 machines + new cost_lines collection; Finance/Insurance/Tracking categories removed from assets - C.2: type field on assets derived from code prefix - C.3: sequential codes SC01-07, OC01-02, PC01-02; fixed SITE CONTANER spelling - C.4: flat sortable plant register table with type filter pills - C.5: wired filter input on plant register - D: verified term 2 (insurance) reconciles for all 5 financed machines
244 lines
10 KiB
Markdown
244 lines
10 KiB
Markdown
# Mega Plant — Round 2
|
||
|
||
Round 1 (`HANDOFF-PROMPT.md`) was mostly landed well. This round covers three items
|
||
that didn't land, plus the asset-register restructure, plus a partial answer to
|
||
Open Question 2 that came out of the data.
|
||
|
||
## Ground rules (unchanged)
|
||
|
||
**DO NOT restyle anything.** Palette, roundness, gradients, spacing, dark mode all
|
||
stay exactly as they are. They're approved.
|
||
|
||
**Do not invent data to make a step pass.** See B.3 — this happened in round 1.
|
||
|
||
**Verify and report.** Run `npm run typecheck` after each part and say what it output.
|
||
|
||
---
|
||
|
||
# Part A — Already done for you, do not redo
|
||
|
||
The endless-recompile loop is **fixed**. Root cause: `next-pwa` writes `sw.js` and
|
||
`workbox-*.js` into `public/` on every compile, and the dev server watches `public/`,
|
||
so each build triggered the next one. The dev server was sitting at 67.9% CPU.
|
||
|
||
Changes already committed to your working tree:
|
||
|
||
- `next.config.ts` — added `disable: process.env.NODE_ENV === "development"`
|
||
- `.gitignore` — added `public/sw.js`, `public/workbox-*.js`, `public/swe-worker-*.js`
|
||
- `git rm --cached` on those three (they had been committed as build artifacts)
|
||
- Deleted the stale generated files from `public/`
|
||
|
||
Verified after the fix: dev server idle at **0.0% CPU**, `/` and `/plant` both 200,
|
||
`/sw.js` 404 in dev (correct — it only generates for production builds),
|
||
`/manifest.json` 200.
|
||
|
||
**Leave this alone.** If you need a service worker in dev for a specific test, flip
|
||
the flag temporarily and flip it back — do not remove it.
|
||
|
||
One thing to be aware of: any browser that loaded the app while the broken build was
|
||
running still has a service worker registered. If the app behaves strangely or serves
|
||
stale content, unregister it at `chrome://serviceworker-internals` (or DevTools →
|
||
Application → Service Workers → Unregister) and hard-reload.
|
||
|
||
---
|
||
|
||
# Part B — Round 1 items that didn't land
|
||
|
||
## B.1 `running_cost` is still on `monthly_income`
|
||
|
||
Round 1 §1.5 asked for this field to be dropped and derived from
|
||
`monthly_running_costs` instead. The seed has a comment claiming it was done, but the
|
||
live PocketBase schema still has it:
|
||
|
||
```
|
||
monthly_income fields: asset, fixed_expenses, income, is_forecast, month, running_cost, year
|
||
```
|
||
|
||
So the same number is still stored in two collections and will drift the moment
|
||
either is edited.
|
||
|
||
- [ ] Remove the `running_cost` field from the `monthly_income` collection in
|
||
PocketBase itself, not just from the seed payload.
|
||
- [ ] Derive running cost on read by joining `monthly_running_costs` on
|
||
`(asset, year, month)`.
|
||
- [ ] Update `buildIncomeRows` in `lib/rows.ts` to take running costs as a separate
|
||
argument rather than reading `r.running_cost`.
|
||
- [ ] Confirm the field is gone by listing the collection schema, and paste the output.
|
||
|
||
## B.2 32 of 40 machines have income, not 40
|
||
|
||
Better than the 19 you started with, but still short. 8 machine blocks from the
|
||
workbook are still not landing.
|
||
|
||
- [ ] Report exactly which 8 are missing and why each one fails to match.
|
||
- [ ] Fix the matching for those 8.
|
||
- [ ] Do not close this item by creating assets for them — see B.3.
|
||
|
||
## B.3 The seed invents assets instead of failing
|
||
|
||
Round 1 §1.4 asked for an explicit alias map and a loud failure on unmatched rows.
|
||
Instead `scripts/seed.mjs` gained a `syntheticCode()` fallback that auto-creates
|
||
asset rows. Two exist now — `ZPORTCHCG` and `ZPORTCHCH`, both "Porta camp" with
|
||
`monthly_incl=0` and `purchase_value=0`.
|
||
|
||
The damage is small, but the pattern defeats the purpose: **the seed can no longer
|
||
fail**, so it can never tell you the source data has a problem. It swapped a silent
|
||
drop for a silent ghost row.
|
||
|
||
- [ ] Delete `syntheticCode()` and the auto-create branch.
|
||
- [ ] Unmatched rows collect into an error list; print the full list and
|
||
`process.exit(1)`.
|
||
- [ ] Delete the two `Z*` assets from PocketBase.
|
||
- [ ] Handle the Porta camps by giving them real asset numbers (see C.3).
|
||
|
||
---
|
||
|
||
# Part C — The asset register
|
||
|
||
This is the main work this round.
|
||
|
||
## C.1 Five machines are stored as 13 separate "assets"
|
||
|
||
The `assets` collection has categories `Finance Costs` (5), `Insurance` (5), and
|
||
`Tracking` (3). These are **not assets**. They are cost components of five machines,
|
||
split across three rows each:
|
||
|
||
```
|
||
TLB06 Finance 29036.08 | Insurance 2401.81 | Tracking 694.51
|
||
TLB07 Finance 29819.00 | Insurance 2797.63 | Tracking 605.39
|
||
MG02 Finance 19088.95 | Insurance 2414.09 | Tracking —
|
||
RV04 Finance 28641.00 | Insurance 1459.25 | Tracking —
|
||
Toyota Bakkie LD13HZ GP (no code)
|
||
Finance 8990.91 | Insurance 2131.99 | Tracking 249.45
|
||
```
|
||
|
||
So the Plant Register currently shows TLB06 three times and never once as a machine.
|
||
TLB07 three times. MG02 and RV04 twice each. The Toyota Bakkie three times.
|
||
|
||
These are real revenue-earning machines — TLB06 and TLB07 both have income in the
|
||
workbook's graph sheet.
|
||
|
||
- [ ] Collapse each of these five into **one** asset row with its proper code
|
||
(`TLB06`, `TLB07`, `MG02`, `RV04`, and a new code for the Toyota Bakkie).
|
||
- [ ] Move the finance / insurance / tracking amounts into a new `cost_lines`
|
||
collection: `asset (relation), year, month, kind, amount`, where `kind` is
|
||
one of `instalment | insurance | tracking | operator | running`.
|
||
- [ ] Delete the `Finance Costs`, `Insurance`, and `Tracking` categories from
|
||
`assets` entirely.
|
||
- [ ] After this, `assets` should contain only physical things: plant and vehicles.
|
||
|
||
## C.2 Group by machine type, not category
|
||
|
||
After C.1, `category` is nearly useless — almost everything is `Plant`, which means
|
||
one undifferentiated 37-row table plus a few 3-row cards that spend a full header,
|
||
icon, and totals row on almost nothing.
|
||
|
||
Group by **machine type**, derived from the code prefix:
|
||
|
||
| Prefix | Type | Members |
|
||
| --- | --- | --- |
|
||
| `TLB` | Backhoe loaders (TLB) | TLB01–TLB07 |
|
||
| `SS` | Skid steers | SS01, SS02 |
|
||
| `RV` | Rollers | RV01, RV03, RV04 |
|
||
| `MG` | Graders | MG01, MG02 |
|
||
| `TP6` | Tipper trucks | TP601, TP602 |
|
||
| `WB` | Water bowsers | WB01, WB02 |
|
||
| `EXC` | Excavators | EXC01 |
|
||
| `CT` | Crane / cherry picker | CT01 |
|
||
| `DT` | Diesel truck | DT01 |
|
||
| `WT` | Water trailer | WT01 |
|
||
| — | Site infrastructure | containers, generators, camps |
|
||
|
||
- [ ] Add a `type` field on `assets`, populated from the code prefix at seed time.
|
||
Store it — do not re-derive it in the UI on every render.
|
||
- [ ] Confirm the type labels above with the client before committing to them; some
|
||
are inferred from the code letters.
|
||
|
||
## C.3 Uncoded duplicates need real asset numbers
|
||
|
||
Seven rows all named `SITE CONTANER` with no code. Three `Toyota Bakkie - LD13HZ GP`.
|
||
Two `Office Container`. Two `Porta camp`. These are different physical units that are
|
||
completely indistinguishable in the UI — you cannot tell which container is which.
|
||
|
||
- [ ] Ask the client for real asset numbers for every uncoded item.
|
||
- [ ] Until they supply them, assign sequential codes (`SC01`–`SC07`, `OC01`–`OC02`,
|
||
`PC01`–`PC02`) and **flag them in the UI as provisional** so nobody trusts them.
|
||
- [ ] Fix the spelling: `SITE CONTANER` → `SITE CONTAINER`.
|
||
|
||
## C.4 Flatten the register to one table
|
||
|
||
With ~41 real assets, the job on this page is scanning, sorting, and comparing
|
||
machines against each other. Nested cards actively prevent that — you cannot compare
|
||
a TLB against a grader when they're in two separate tables.
|
||
|
||
- [ ] Replace the per-category card sections with **one** table.
|
||
- [ ] Columns: Code · Name · Type · Supplier · Monthly excl · Monthly incl · Purchase value.
|
||
- [ ] Sortable by clicking any column header. Default sort: Type, then Code.
|
||
- [ ] Sticky header row.
|
||
- [ ] One totals row at the bottom.
|
||
- [ ] A `Type` filter as segmented pills above the table (All / TLB / SS / RV / …).
|
||
|
||
Keep the existing card container, rounded corners, border, and shadow around the
|
||
table — the page should still look like it belongs to this app.
|
||
|
||
If the client prefers keeping visible sections, the fallback is: group by `type`
|
||
(not `category`), make each section collapsible, and put the section totals in the
|
||
collapsed header. Ask before choosing.
|
||
|
||
## C.5 The `Filter…` input on the register is fake
|
||
|
||
`app/plant/page.tsx` still has a non-functional search input — the exact dead-UI
|
||
problem that was just fixed in the header.
|
||
|
||
- [ ] Wire it to filter the table by code, name, type, or supplier.
|
||
- [ ] Or delete it and rely on the header's `GlobalSearch`. Do not leave it dead.
|
||
|
||
---
|
||
|
||
# Part D — Open Question 2 is now partly answered
|
||
|
||
Round 1 flagged that every fixed-expense cell is a hand-typed sum of three unlabelled
|
||
constants. The `assets` data answers the second term.
|
||
|
||
```
|
||
Workbook TLB06 I9: = 29036.28 + 2401.81 + 5620.2
|
||
assets TLB06 Finance Insurance
|
||
29036.08 2401.81 ← exact match
|
||
|
||
Workbook TLB07 I10: = 29819.00 + 2797.63 + 5620.2
|
||
assets TLB07 Finance Insurance
|
||
29819.00 2797.63 ← exact match
|
||
```
|
||
|
||
So:
|
||
|
||
- **Term 1** = monthly instalment / finance cost ✅ confirmed
|
||
- **Term 2** = **insurance** ✅ confirmed (the flat `1300` on owned machines is their
|
||
insurance; the Bastion-financed units carry their own per-machine premium)
|
||
- **Term 3** = still unknown. Varies per machine *and* per month — `5620.20`,
|
||
`6437.20`, `7315.95`, `10653.73`. Most likely operator wages, possibly with tracking
|
||
folded in. **Still needs client confirmation.**
|
||
|
||
- [ ] Once C.1 is done, verify the reconstruction holds for all five financed machines
|
||
across all 12 months, and report any that don't add up.
|
||
- [ ] Ask the client what term 3 is. That is the last piece needed before fixed
|
||
expenses can be computed from inputs rather than copied as a stored number.
|
||
|
||
---
|
||
|
||
# Acceptance criteria
|
||
|
||
- [ ] `npm run typecheck` exits clean
|
||
- [ ] `npm run build` succeeds
|
||
- [ ] Dev server stays idle when no files change (no recompile loop)
|
||
- [ ] `running_cost` no longer exists on `monthly_income`; schema output pasted as proof
|
||
- [ ] 40 of 40 machines have income, with zero auto-created assets
|
||
- [ ] Seed exits non-zero and lists every failure when any row cannot be matched
|
||
- [ ] `assets` contains only physical plant and vehicles — no `Finance Costs`,
|
||
`Insurance`, or `Tracking` categories
|
||
- [ ] TLB06, TLB07, MG02, RV04, and the Toyota Bakkie each appear exactly **once**
|
||
- [ ] Every asset has a code; provisional codes are visibly marked as such
|
||
- [ ] Plant Register is one sortable table with a working filter
|
||
- [ ] Term 2 of the fixed-expense formula reconciles to insurance for all five
|
||
financed machines
|
||
- [ ] **Palette, roundness, gradients, and dark mode visually unchanged**
|