Files
LynkedUpPro_CRM/docs/superpowers/plans/2026-05-29-plan2-unify-routing.md
T

124 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_023kl_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_001022, 039044) 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 (T3T6) 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((actualCostbudget)/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_023kl_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 the unified `/projects/:projectId` route (Option B)
**Files:** `src/App.jsx` only. **Phase B (parallel).**
We adopt the final Option-B scheme for the projects route now — a single, role-prefix-free URL — so won leads and projects share one page. The **attribute-based access guard arrives in Phase 8**; for the interim, gate with a coarse `allowedRoles`.
- [ ] **Step 1:** Add a route `/projects/:projectId``OwnerProjectDetail`, gated `allowedRoles={['OWNER','ADMIN','FIELD_AGENT']}` (interim coarse guard — Phase 8 replaces it with the ABAC policy guard). Leave the existing `/owner/projects/:projectId` route in place for now (Phase 8 removes the duplicate). Do NOT add `/admin/projects` / `/emp/fa/projects` role-prefixed copies.
- [ ] **Step 2:** `npm run build``✓ built`.
- [ ] **Step 3:** commit `git commit -m "feat(routes): add unified /projects/:id route for the shared 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 the unified `\`/projects/${lead.projectId}\`` (no basePath); else keep `\`${basePath}/leads/${lead.id}\`` (pre-sale leads stay role-prefixed until Phase 8).
- [ ] **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 and pipeline leads (links ~lines 280/366 and 499/565). Point ALL project links AND won pipeline leads (those with a `projectId`) at the unified `/projects/:id`. 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_023038 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).