docs: Plan 2 — unify lead/project + routing (PRJ-2026-### ids), with parallel-safe lane map
This commit is contained in:
@@ -0,0 +1,121 @@
|
|||||||
|
# Plan 2 — Unify Lead↔Project + Routing/Naming
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). Follow the execution protocol (`2026-05-29-execution-protocol.md`) — parallel only where lanes are disjoint.
|
||||||
|
|
||||||
|
**Goal:** Make a *won* kanban lead the same entity as a project — one data model, one rich detail page, clean `/…/projects/:id` routes, and unified `PRJ-2026-###` ids.
|
||||||
|
|
||||||
|
**Architecture:** Migrate `proj_###`→`PRJ-2026-###` (with inbound FKs), convert the 16 won kanban leads (kl_023–kl_038) into full `projects` records (normalizing field names + back-filling missing project fields) linked back to their kanban lead, route all won jobs to the existing rich `OwnerProjectDetail` under owner/admin/field-agent prefixes, and relink the kanban drawer + project list. Pre-sale leads (kl_001–022, 039–044) stay as leads on the lighter `LeadProjectPage`.
|
||||||
|
|
||||||
|
**Tech stack:** React 18, Vite 7, mock context store, pnpm. No test harness → verify via `npm run build` + grep/reconciliation + dev-run.
|
||||||
|
|
||||||
|
**Spec/master:** `docs/superpowers/specs/2026-05-29-demo-data-coherence-design.md`, `docs/superpowers/2026-05-29-MASTER-PLAN.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lane / ownership map (for safe parallelism)
|
||||||
|
|
||||||
|
| Task | Lane (exclusive files) | Phase |
|
||||||
|
|------|------------------------|-------|
|
||||||
|
| T1 Id migration | `src/data/mockStore.jsx` | A (sequential) |
|
||||||
|
| T2 Convert won leads → projects | `src/data/mockStore.jsx` | A (sequential, after T1) |
|
||||||
|
| T3 Detail page role access | `src/pages/owner/OwnerProjectDetail.jsx` | B (parallel) |
|
||||||
|
| T4 Routes | `src/App.jsx` | B (parallel) |
|
||||||
|
| T5 Kanban relink | `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx` | B (parallel) |
|
||||||
|
| T6 Project list relink | `src/pages/owner/OwnerProjectList.jsx` | B (parallel) |
|
||||||
|
| T7 Verify | (read-only) | C |
|
||||||
|
|
||||||
|
**Phase A (T1, T2) is strictly sequential** — both edit `mockStore.jsx`. **Phase B (T3–T6) may run in parallel** — disjoint files, and all depend only on Phase A being committed. **T7** runs last.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field normalization map (T2 — kanban project data → project schema)
|
||||||
|
|
||||||
|
| Kanban (`KANBAN_PROJECT_DATA` / lead) | Project schema |
|
||||||
|
|----|----|
|
||||||
|
| `estimatedAmount` | `budget` and `approvedBudget` |
|
||||||
|
| `completionPct` | `completionPercentage` |
|
||||||
|
| `workTimeline[]` | `activityTimeline[]` (same item shape) |
|
||||||
|
| milestone `{date}` | milestone `{dueDate}` (keep id/name/status; `assignedTo` = contractor id) |
|
||||||
|
| invoice `{description, paidDate}` | invoice `{submittedBy, datePaid}` (keep id/amount/status/dueDate; submittedBy = contractor/sub id) |
|
||||||
|
| rfi `{openedDate, closedDate}` | rfi `{dateOpened, dateClosed}` |
|
||||||
|
| changeOrder `{requestedBy, date}` | changeOrder `{dateSubmitted}` (keep id/title/amount/status/description) |
|
||||||
|
| `contractorName`/`contractorPhone` | `contractorId` (map "Mike Brennan"→`con_001`; keep others if a different contractor) |
|
||||||
|
| lead `name`/`address`/`phone`/`email` | becomes the project's customer/`address`; keep a `customer` block |
|
||||||
|
|
||||||
|
**Back-fill (required so the rich tabs aren't broken/empty):** `ownerId` (assign own_001 or own_002), `phase`, `committedCost` (≈ between actualCost and budget), `spent`=`actualCost`, `variancePercent`=round((actualCost−budget)/budget×100,1), `margin`, `commissionType`/`commissionRate`/`commission`, `teamMembers[]` (≥ sales rep p1/p2 + contractor con_001), `subcontractorIds[]`, `riskLog`/`issueLog` (may be `[]`), `paymentSchedule[]`, `openRFIs`/`changeOrderCount`/`pendingInvoiceCount` (computed from the arrays), `budgetBreakdown[]` (Plan-1 invariants: allocated→budget, committed→committedCost, actual→actualCost, committed≥actual), `startDate`/`endDate`, `healthScore`. **Reuse the existing `KANBAN_PROJECT_DATA` numbers; only fill genuine gaps.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Migrate project ids `proj_###` → `PRJ-2026-###`
|
||||||
|
|
||||||
|
**Files:** `src/data/mockStore.jsx` only.
|
||||||
|
|
||||||
|
- [ ] **Step 1:** `git grep -n "proj_0" -- src/data/mockStore.jsx` to enumerate every `proj_###` — both the `projects[].id` definitions AND inbound FKs (`projectId: 'proj_###'`) in tasks, schedule, `MOCK_SUBCONTRACTOR_TASKS`, and anywhere else.
|
||||||
|
- [ ] **Step 2:** Replace each `proj_001`→`PRJ-2026-001`, … `proj_012`→`PRJ-2026-012`, **consistently across the whole file** (definitions + all FKs) so every reference still resolves. Use replace_all per id.
|
||||||
|
- [ ] **Step 3 (verify):** `git grep -n "proj_0" -- src/data/mockStore.jsx` → NO matches. Every `projectId` FK now reads `PRJ-2026-###`.
|
||||||
|
- [ ] **Step 4:** `npm run build` → `✓ built`, zero errors.
|
||||||
|
- [ ] **Step 5 (reconcile FKs):** confirm no subcontractor task / schedule entry references a `projectId` that doesn't exist in `projects` (grep the new ids; each FK must match a project id).
|
||||||
|
- [ ] **Step 6:** commit `git commit -m "refactor(data): migrate project ids to PRJ-2026-### (with inbound FKs)"`.
|
||||||
|
|
||||||
|
## Task 2: Convert the 16 won kanban leads into project records
|
||||||
|
|
||||||
|
**Files:** `src/data/mockStore.jsx` only. **Runs after T1.**
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Read `KANBAN_LEADS_INITIAL` for kl_023–kl_038 and their `KANBAN_PROJECT_DATA` entries.
|
||||||
|
- [ ] **Step 2:** For each of the 16, append a new object to the `projects` array with id `PRJ-2026-013`…`PRJ-2026-028`, mapping fields per the Normalization Map and back-filling per the Back-fill list. Add `sourceLeadId: '<kl_id>'` to the project, and `projectId: '<PRJ id>'` to the corresponding kanban lead (so the board links to the project). Keep the kanban lead's `columnId` (it still shows in Signed/Progress/Complete).
|
||||||
|
- [ ] **Step 3:** Assign realistic `ownerId` (split across own_001/own_002), `teamMembers` (canonical people), and a `contractorId` (con_001 unless the data names another). Vary `phase`/`status` to match the lead's stage (signed→Scope Approved/Contract Signed, progress→Work In Progress, complete→Completed).
|
||||||
|
- [ ] **Step 4 (reconcile):** every new project satisfies Plan-1 financial invariants (Σbreakdown columns == totals; committed≥actual; spent==actualCost; variancePercent sign correct). Write a throwaway node check to /tmp if helpful; do NOT commit it.
|
||||||
|
- [ ] **Step 5:** `npm run build` → `✓ built`, zero errors.
|
||||||
|
- [ ] **Step 6:** commit `git commit -m "feat(data): convert won kanban leads (kl_023-038) into unified project records"`.
|
||||||
|
|
||||||
|
## Task 3: Let owner/admin/field-agent view a project (relax owner-only lookup)
|
||||||
|
|
||||||
|
**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. **Phase B (parallel).**
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Find the lookup `projects.find(p => p.id === projectId && p.ownerId === user?.id)` (~line 296). Relax it so ADMIN/OWNER/FIELD_AGENT can view: match by id, and only enforce ownerId when `user.role === 'OWNER'` (owners see their own; admin/field-agent see any). Keep the not-found fallback.
|
||||||
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
|
- [ ] **Step 3:** commit `git commit -m "fix(projects): allow admin/field-agent to open project detail, not just owner"`.
|
||||||
|
|
||||||
|
## Task 4: Add `/…/projects/:projectId` routes for all three role prefixes
|
||||||
|
|
||||||
|
**Files:** `src/App.jsx` only. **Phase B (parallel).**
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Add routes rendering `OwnerProjectDetail`: `/admin/projects/:projectId` (ADMIN, OWNER) and `/emp/fa/projects/:projectId` (FIELD_AGENT, ADMIN, OWNER), mirroring the existing `/owner/projects/:projectId`. (Optionally also `/admin/projects` and `/emp/fa/projects` → `OwnerProjectList`.)
|
||||||
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
|
- [ ] **Step 3:** commit `git commit -m "feat(routes): add /admin and /emp/fa projects routes for the unified detail page"`.
|
||||||
|
|
||||||
|
## Task 5: Relink the kanban drawer so won leads open the project page
|
||||||
|
|
||||||
|
**Files:** `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx`. **Phase B (parallel).**
|
||||||
|
|
||||||
|
- [ ] **Step 1:** In `LeadInfoDrawer.jsx` (~line 302) the "More Details" button does `navigate(\`${basePath}/leads/${lead.id}\`)`. Change it so: if the lead has a `projectId` (won), navigate to `\`${basePath}/projects/${lead.projectId}\``; else keep `\`${basePath}/leads/${lead.id}\``.
|
||||||
|
- [ ] **Step 2:** Check `KanbanPage.jsx` for any other lead→detail navigation and apply the same conditional.
|
||||||
|
- [ ] **Step 3:** `npm run build` → `✓ built`.
|
||||||
|
- [ ] **Step 4:** commit `git commit -m "feat(kanban): won leads open the unified project page; pre-sale leads stay on lead page"`.
|
||||||
|
|
||||||
|
## Task 6: Relink the project list's pipeline entries
|
||||||
|
|
||||||
|
**Files:** `src/pages/owner/OwnerProjectList.jsx` only. **Phase B (parallel).**
|
||||||
|
|
||||||
|
- [ ] **Step 1:** This page lists both construction projects (→ `/owner/projects/:id`) and pipeline leads (→ `/owner/leads/:id`, lines ~499/565). For pipeline leads that now have a `projectId` (won), link to `/owner/projects/:projectId` instead. Pre-sale leads keep `/owner/leads/:id`.
|
||||||
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
|
- [ ] **Step 3:** commit `git commit -m "feat(projects): project list links won pipeline jobs to the unified project page"`.
|
||||||
|
|
||||||
|
## Task 7: Verify (read-only)
|
||||||
|
|
||||||
|
- [ ] `git grep -n "proj_0" -- src` → none (ids fully migrated).
|
||||||
|
- [ ] Every kanban lead kl_023–038 has a `projectId` resolving to a real project; every new project has a `sourceLeadId` resolving to a kanban lead.
|
||||||
|
- [ ] All projects (old + new) satisfy Plan-1 financial invariants (dispatch a read-only reviewer to re-check sums, as in Plan 1).
|
||||||
|
- [ ] `npm run build` clean.
|
||||||
|
- [ ] Dev run: from the kanban board, opening a Signed/In-Progress/Complete lead's "More Details" lands on `/…/projects/PRJ-2026-###` showing the full rich detail (10 tabs); opening a pre-sale lead still shows the lighter lead page. Owner, admin, and field-agent can each open a project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-review checklist
|
||||||
|
- [ ] No `proj_###` left anywhere in `src`.
|
||||||
|
- [ ] 16 new projects appended; ids `PRJ-2026-013..028`; each linked to its kanban lead both ways.
|
||||||
|
- [ ] Financial invariants hold for all ~28 projects.
|
||||||
|
- [ ] Build clean; won leads route to project page; pre-sale leads route to lead page; all three roles can view.
|
||||||
|
|
||||||
|
## Notes for Plan 3
|
||||||
|
After this lands, Plan 3 targets the unified model: kanban depth (realistic details on the pre-sale leads), the 10 curated threaded demo-case scenarios (chosen from the ~28 projects), and computed `selectors.js` dashboards. The lighter `LeadProjectPage` is now pre-sale-only; converging it fully into one component is deferred (Phase 4 adds the lead-side extras — stage mover, hail history, progression — onto the unified page).
|
||||||
Reference in New Issue
Block a user