docs: expand spec with project lifecycle, inspections, scope-variance, estimate versions, sub-task stage machine, 10 demo cases
This commit is contained in:
@@ -4,121 +4,157 @@
|
|||||||
**Branch:** revamp
|
**Branch:** revamp
|
||||||
**Goal:** Turn the CRM's mock data into one coherent fake roofing company ("LynkedUp Pro Roofing") that works as both a **live sales demo** (believable, no empty/broken views, numbers that feel real) and an **investor pitch** (breadth, depth, analytics wow). Coherence end-to-end is the wow: drilling from any dashboard KPI lands on the exact records that compose it.
|
**Goal:** Turn the CRM's mock data into one coherent fake roofing company ("LynkedUp Pro Roofing") that works as both a **live sales demo** (believable, no empty/broken views, numbers that feel real) and an **investor pitch** (breadth, depth, analytics wow). Coherence end-to-end is the wow: drilling from any dashboard KPI lands on the exact records that compose it.
|
||||||
|
|
||||||
**Approach:** A — Canonical-company foundation, then layer up. Analytics are **computed** from the canonical data (with one curated exception: purely-visual long-range trend lines).
|
**Approach:** A — Canonical-company foundation, then layer up. Analytics are **computed** from canonical data (one curated exception: purely-visual long-range trend lines).
|
||||||
|
|
||||||
|
**Sequencing decision:** Data coherence first (Phases 1–6), then the three interactive subsystems (Phases 7–9).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Canonical identity (foundation)
|
## 1. Canonical identity (foundation)
|
||||||
|
|
||||||
**Problem (from audit):** the same people exist under three incompatible ID schemes, so cross-module lookups silently fail; Owner #2 has two names; admins have placeholder names; subcontractor tasks reference non-existent ids (`owner_001`, `ADM01`).
|
**Problem (audit):** the same people exist under three incompatible ID schemes; cross-module lookups silently fail; Owner #2 has two names; admins have placeholder names; subcontractor tasks reference non-existent ids (`owner_001`, `ADM01`).
|
||||||
|
|
||||||
**Decision:** unify on the `MOCK_USERS` scheme (already used by auth and `projects`):
|
**Decision:** unify on the `MOCK_USERS` scheme (auth + `projects` already use it):
|
||||||
- Owners: `own_001` (Justin Johnson), `own_002` (Diana Reeves)
|
- Owners `own_001` (Justin Johnson), `own_002` (Diana Reeves); Admins `a1` (Adam Admin), `a2` (Amanda Manager), `a3` (Arthur Director); Field agents `e1`–`e5`; Sales/personnel `p1` (Jesus Gonzales), `p2` (Sarah Sales); Contractor `con_001`; Subcontractors `sub_001`–`sub_005`.
|
||||||
- Admins: `a1` (Adam Admin), `a2` (Amanda Manager), `a3` (Arthur Director)
|
|
||||||
- Field agents / canvassers: `e1`–`e5` (Frank Agent, Fiona Field, Fred Flyer, Felicity Fast, Felix Fixer)
|
|
||||||
- Sales reps / personnel: `p1` (Jesus Gonzales), `p2` (Sarah Sales)
|
|
||||||
- Contractor: `con_001` (Mike Contractor); Subcontractors: `sub_001`–`sub_005`
|
|
||||||
|
|
||||||
**Changes:**
|
**Changes:** remap `orgMembers[].userId` and all subcontractor-task references (`assignedBy`/`actorId`/`senderId`) to canonical ids; reconcile names/emails (Owner #2 = Diana Reeves everywhere; real admin names; consistent Justin email); add a `resolvePerson(id)` selector returning `{ id, name, email, role }` from a single merged index.
|
||||||
- Remap `orgMembers[].userId`: `owner_001→own_001`, `owner_002→own_002`, `FA00N→eN`, `ADM0N→aN`. Keep `orgMembers[].roleKey` as the org-role taxonomy (OWNER/ADMIN/SALES_REP/CANVASSER/SUBCONTRACTOR).
|
|
||||||
- Reconcile names/emails: `own_002` = **Diana Reeves** everywhere; admins = real names (not "Admin One/Two/Three"); Justin's email consistent (`justin@lynkeduppro.com`).
|
|
||||||
- Remap subcontractor-task references: `assignedBy`/`actorId`/`senderId` `owner_001→own_001`, `ADM01→a1`; correct the denormalized `*Name` strings to match.
|
|
||||||
- Add a `resolvePerson(id)` selector in the store (returns `{ id, name, email, role, avatar? }` from a single merged index) so every module resolves people the same way.
|
|
||||||
|
|
||||||
**Acceptance:** no reference to `owner_001`/`FA0*`/`ADM0*` as an *id* anywhere; every `userId`/`assignedBy`/`teamMembers[].userId`/`ownerId`/`contractorId`/`subcontractorIds[]` resolves via `resolvePerson`/the relevant collection; `own_002` shows one name across all screens.
|
**Acceptance:** no `owner_001`/`FA0*`/`ADM0*` used as an *id*; every person reference resolves; `own_002` shows one name everywhere.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Coherence fixes (the four approved tiers)
|
## 2. Coherence fixes (four approved tiers)
|
||||||
|
|
||||||
### 2a. Feature-breaking data
|
### 2a. Feature-breaking data
|
||||||
- **Leads List page:** seed the store's `leads` array (currently `useState([])`) with ~28–35 records in the page's shape (`firstName/lastName`, `phones[]`, `urgency`, `status`, `leadSource`, `canvasserName`, `createdByName`, `createdAt`, `stormSource`). Source = the existing storm-attribution leads, expanded. Page must never show the empty state on load.
|
- **Leads List page:** seed `leads` (currently `useState([])`) with ~28–35 records in the page's shape; never show the empty state.
|
||||||
- **Subcontractor access:** add login user records for `sub_002`–`sub_005` (type `subcontractor`) so each subcontractor's tasks/notifications are reachable. Keep existing task ownership.
|
- **Subcontractor access:** add login user records for `sub_002`–`sub_005` so each sub's tasks/notifications are reachable.
|
||||||
- **Task → project association:** every subcontractor task points to a project whose `address` matches the task `location` (residential roofing/electrical tasks → residential projects, not the commercial facade/hotel projects). Fix `STATUS_CONFIG`/`STATUS_STYLES` to include `On Hold`.
|
- **Task → project:** every subcontractor task points to a project whose `address` matches the task `location`; add `On Hold` to `STATUS_CONFIG`/`STATUS_STYLES`.
|
||||||
|
|
||||||
### 2b. Financial coherence
|
### 2b. Financial coherence
|
||||||
- **proj_008:** reduce paid invoices so paid ≤ `committedCost` (52,000) and ≤ `actualCost` (44,500).
|
- proj_008: paid invoices ≤ committedCost (52,000) and ≤ actualCost (44,500).
|
||||||
- **proj_005:** set `committedCost` to the breakdown's real sum **28,850** (work was committed; stored total was stale); verify variance/margin recompute.
|
- proj_005: set `committedCost` to the breakdown's real sum **28,850**.
|
||||||
- **Completed projects (proj_002, proj_010):** paid invoices ≤ `actualCost` (no overpay vs spend).
|
- Completed projects (proj_002, proj_010): paid invoices ≤ actualCost.
|
||||||
- **proj_004:** fix the contingency line where `committed < actual`; keep it as the deliberate over-budget case.
|
- proj_004: fix contingency line `committed < actual`; keep as the over-budget case.
|
||||||
- **Missing `budgetBreakdown`:** add coherent breakdowns to proj_006, 008, 009, 010, 011, 012 — `allocated` sums to budget, `committed` sums to `committedCost`, `actual` sums to `actualCost`, `committed ≥ actual` per line.
|
- Add coherent `budgetBreakdown` to projects missing it (allocated→budget, committed→committedCost, actual→actualCost, committed≥actual per line).
|
||||||
|
- **Variance correctness:** `variancePercent = (actualCost − budget)/budget × 100`, displayed with explicit `+` (over) / `−` (under) and correct color everywhere it renders.
|
||||||
|
|
||||||
### 2c. Pipeline / Kanban
|
### 2c. Pipeline / Kanban
|
||||||
- Add a structured numeric `value` (estimate) to **every** kanban lead (not just signed+), so a pipeline $ total and weighted funnel can be computed.
|
- Structured numeric `value` (estimate) on **every** kanban lead → enables pipeline $ total + weighted funnel.
|
||||||
- Fix Complete-stage leads (kl_034–038): real `actualCost` and `completionPct: 100`.
|
- Fix Complete-stage leads: real `actualCost`, `completionPct: 100`.
|
||||||
- Re-anchor the hardcoded "today" (`KanbanCard.jsx:14`) — derive from a single `DEMO_TODAY` constant (= a fixed recent date) used app-wide, or `Date.now()`; dates stay ordered and not-future relative to it.
|
- Single `DEMO_TODAY` constant (fixed recent date) used app-wide; dates ordered, not future relative to it.
|
||||||
- Populate `stormSource: true` on storm-canvassed leads so the wired badge shows.
|
- Populate `stormSource` on storm-canvassed leads; kl_044 (Signed) gets a project record or a pre-signed stage.
|
||||||
- kl_044 (Signed) gets a project record or moves to a pre-signed stage.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Five–six fully-threaded scenario projects (real-world spread)
|
## 3. Ten demo-case projects (threaded end-to-end)
|
||||||
|
|
||||||
Instead of one hero, **6 projects are threaded end-to-end across modules**, each representing a distinct real-world case. Every thread shares consistent identities, addresses, and dates from lead → project → subcontractor task → invoices/payments → commission.
|
**~10 projects**, each a distinct real-world case, each threaded across modules with consistent identities/addresses/dates: lead → project → subcontractor task(s) → invoices/payments → commission. Each requires a matching **lead** (pipeline stage consistent with project status), ≥1 **subcontractor task** (address-matched), **invoices + paymentSchedule** reconciling with `actualCost`, a **commission** config (+ payout for completed), and consistent team/owner/dates.
|
||||||
|
|
||||||
| # | Project | Scenario it demonstrates | Thread highlights |
|
Case matrix to cover:
|
||||||
|---|---------|--------------------------|-------------------|
|
1. **Storm-driven win, on track** (healthy active).
|
||||||
| proj_001 | Roof Replacement (active, healthy) | **Storm-driven win, on track** | Storm lead (Frank/e1 canvasses) → Jesus/p1 closes → active project, Carlos/sub_001 electrical task → progress invoices → commission accrues |
|
2. **Insurance-backed up-scope → approved** — small self-report → inspection finds major damage → revised estimate → insurance supplement → approved (the hero discrepancy story).
|
||||||
| proj_004 | Interior Reno (over budget) | **Problem job / margin loss** | Scope-creep change orders → costs exceed budget → at-risk health → owner sees negative net profit |
|
3. **Up-scope → customer declines (lost)** — revised estimate too high; lead lost, reason "declined after inspection".
|
||||||
| proj_007 | Commercial Roof (large, active) | **Big commercial, multi-phase** | Payment schedule by phase, multiple subs, larger commission |
|
4. **Smaller/different than claimed** — over-stated; inspection down-scopes to a quick small job.
|
||||||
| proj_008 | Foundation (88%, healthy) | **Near-complete, healthy** | Most milestones done, final invoices pending, payout imminent |
|
5. **Multiple revisions / negotiation** — several estimate versions before agreement (richest version history).
|
||||||
| proj_010 | Parking Lot (completed) | **Closed-won, full lifecycle** | Lead won months ago → completed → all invoices paid → commission paid out |
|
6. **Over-budget / margin loss** (proj_004 — scope creep).
|
||||||
| proj_011 | Facade/Disputed | **Disputed / on-hold** | Dispute timeline, paused payments, low health score |
|
7. **Large commercial, multi-phase** (proj_007).
|
||||||
|
8. **Near-complete, healthy** (proj_008).
|
||||||
|
9. **Completed, full lifecycle, all paid** (proj_010).
|
||||||
|
10. **Disputed / on-hold** (proj_011).
|
||||||
|
|
||||||
Each thread requires: a matching **lead** (pipeline stage consistent with project status; `stormSource` where relevant), at least one **subcontractor task** (address-matched), **invoices + paymentSchedule** that reconcile with `actualCost`, a **commission** config + (for completed) payout, and consistent **team/owner/dates**.
|
(Existing proj_001/003/005/006/009/012 are folded in / reused where they fit a case; net target ≈10 fully-threaded.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Deeper project detail tables
|
## 4. Deeper project detail tables
|
||||||
|
Every project carries the full coherent set so no tab is empty: `budgetBreakdown`, `changeOrders`, `rfis`, `riskLog`, `issueLog`, `activityTimeline`, `milestones`, `invoices`, `paymentSchedule`, `documents`, `teamMembers`, plus the new lifecycle/inspection/estimate data (§7–8).
|
||||||
Every project carries the full coherent set so no tab is empty: `budgetBreakdown`, `changeOrders`, `rfis`, `riskLog`, `issueLog`, `activityTimeline`, `milestones`, `invoices`, `paymentSchedule`, `documents`, `teamMembers`. Thin/empty arrays get realistic content sized to the project's status and scenario.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Broader pipeline + leaderboard
|
## 5. Broader pipeline + leaderboard
|
||||||
|
More leads across stages with $ values + storm attribution; canvasser leaderboard stats derive from the leads each agent sourced.
|
||||||
- More leads spread realistically across stages with $ values and storm attribution.
|
|
||||||
- Canvasser leaderboard stats derive from the leads each agent sourced (knocks → leads → won), so the leaderboard ties to the pipeline.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Computed analytics (investor wow)
|
## 6. Computed analytics (investor wow)
|
||||||
|
New `src/data/selectors.js` (pure functions over store arrays): pipeline value/funnel/win-rate; revenue/gross/net profit/margin (matching `OwnerProjectDetail` formulas); commission by rep/project; subcontractor performance; storm attribution. Wire into owner/admin dashboards so KPIs tie out with detail screens. **Curated exception:** long-range visual trend lines (labeled illustrative).
|
||||||
Add a `src/data/selectors.js` (pure functions over store arrays) deriving:
|
**Acceptance:** dashboard headline revenue/profit/pipeline == sums of underlying records.
|
||||||
- **Pipeline:** total value, weighted value by stage, conversion funnel counts, win rate.
|
|
||||||
- **Revenue/profit:** total contract value, gross/net profit, margin — summed from projects (matching `OwnerProjectDetail`'s own formulas).
|
|
||||||
- **Commission:** total accrued/paid by rep, by project.
|
|
||||||
- **Subcontractor performance:** tasks by sub, on-time %, total paid.
|
|
||||||
- **Storm attribution:** leads/revenue traced to storm events.
|
|
||||||
|
|
||||||
Wire these into the owner/admin dashboards (`OwnerSnapshot`, `Dashboard`, leaderboard) replacing hardcoded figures, so KPIs tie out with detail screens. **Curated exception:** purely-visual long-range trend charts (e.g., 12-month revenue line) where no daily source data exists — these stay hand-authored and are labeled as illustrative.
|
|
||||||
|
|
||||||
**Acceptance:** the owner dashboard's headline revenue/profit/pipeline numbers equal the sums of the underlying project/lead records (verified by a quick selector unit check).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Components / files touched
|
## 7. Project execution lifecycle (interactive state machine)
|
||||||
|
|
||||||
- `src/data/mockStore.jsx` — identity remap, leads seed, financial fixes, breakdowns, threaded scenarios, sub logins, deeper tables, pipeline values. **This file is large (~6,950 lines); changes are additive/surgical, not a rewrite.**
|
A `lifecycleStage` field per project drives a state machine; the coarse kanban stage (In Progress/Complete) **derives** from it. Each stage maps to a project **progress %**:
|
||||||
- `src/data/selectors.js` — **new**, computed analytics.
|
|
||||||
- Dashboards/components consuming analytics: `src/pages/owner/OwnerSnapshot.jsx`, `src/pages/Dashboard.jsx`, leaderboard page, `src/components/kanban/KanbanCard.jsx` (date anchor), `SubcontractorTasksPage.jsx`/`TaskViewModal.jsx` (On Hold styling).
|
| Stage | Progress |
|
||||||
|
|-------|---------|
|
||||||
|
| Lead *(customer's reported claim)* | 5% |
|
||||||
|
| Damage Inspection Scheduled | 10% |
|
||||||
|
| Damage Verified *(real scope captured)* | 20% |
|
||||||
|
| Scope Approved | 30% |
|
||||||
|
| Contract Signed | 40% |
|
||||||
|
| Work In Progress *(milestone-driven)* | 40–70% |
|
||||||
|
| Work Complete — Awaiting Inspection | 80% |
|
||||||
|
| Final Inspection (Scheduled → In Progress) | 85–90% |
|
||||||
|
| Rework In Progress *(issues found; regresses)* | re-inspects |
|
||||||
|
| Project Completed | 95% |
|
||||||
|
| Final Payment Received — Closed | 100% |
|
||||||
|
|
||||||
|
*(Stage names: proposed; user is tweaking a few — see Open Items.)*
|
||||||
|
|
||||||
|
**Interactivity:** owner/admin advances stages (each transition may carry an optional note + timestamp + actor). Fully interactive; photo/note **uploads persist for the session** (client-side object URLs), with seeded starting data; placeholder thumbnails acceptable where no real file.
|
||||||
|
|
||||||
|
**Inspections (two project-level touchpoints, each records who did it):**
|
||||||
|
- **Damage Verification** (pre-sale): performed by a field agent *or* sales rep — **inspector recorded per project** (name + role), with findings + photos.
|
||||||
|
- **Final Inspection** (post-work): an **external examiner brought by the customer** — no login/role; we store `inspectorName`, `inspectorContact` (phone + email, so they can be re-contacted), `inspectionDate`, `result` (pass/fail), `feedbackNote`, and `issues[]` (each `{ description, note, photos[], status: open|fixed }`). Re-inspection after rework creates a new round; all rounds visible.
|
||||||
|
|
||||||
|
**Claimed-vs-verified discrepancy model:**
|
||||||
|
- Lead stores `reportedScope` (customer's claim: description + rough size/cost/duration guess).
|
||||||
|
- Damage Verification produces `verifiedScope` (findings, photos, measurements, severity, recommended work, real cost/duration).
|
||||||
|
- When verified materially differs, project carries a **scope-variance flag**; **visible alert/badge** on project detail (e.g., *"Reported ~$200 / 1 day → Verified $15,400 / 5 days"*) + a flag in the pipeline.
|
||||||
|
- Drives estimate versioning (§8): v1 rough quote from claim → v2 verified → signed = verified.
|
||||||
|
- Decision branch at Damage Verified → Scope Approved: **approved** (often insurance-supplement) or **declined** (lead lost). All four outcomes from §3 (cases 2–5) represented across the 10 projects.
|
||||||
|
|
||||||
|
## 8. Estimate versions (tied to the project)
|
||||||
|
Each project carries `estimateVersions[]`: `{ version, date, total, note, status: sent|revised|signed, lineItems/templateRef }`. List shows date/note/total + a **Signed** badge; clicking a version opens the **full rendered estimate document** (reusing existing estimate/template rendering). Visible on owner project detail **and** pipeline lead-project page. v1 reflects the customer claim; later versions reflect verified scope.
|
||||||
|
|
||||||
|
## 9. Subcontractor task stage machine
|
||||||
|
Status set (replaces flat Assigned/In Progress/…), **each transition carries an optional note + timestamp + actor**:
|
||||||
|
**Not Assigned → Assigned → Pre-Work Inspection → Work In Progress → (On Hold ↔) → Post-Work Review →** ↳ **Pass → Completed** / ↳ **Fail → Rework Needed** (issue: what's wrong + note + photo) → back to *Work In Progress* → re-review.
|
||||||
|
- **Inspector attribution:** Pre-Work Inspection and Post-Work Review each record who performed them.
|
||||||
|
- Owner `/owner/subcontractor-tasks` table gains columns **Task Category** and **Assigned Date**.
|
||||||
|
- Task detail modal: richer progress — stage timeline, current stage + who/when, review results, issues list with photos.
|
||||||
|
- Same task + stage visible in the **subcontractor's own login**; sub can advance self-stages / log rework issues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coherency map (everything ties to a real project)
|
||||||
|
- Subcontractor task → real project → visible in sub login + owner project Team/Tasks tab + subcontractor-tasks page.
|
||||||
|
- Estimate versions → the project's signed estimate is the deal (matches lead's Estimate Sent/Signed pipeline stage).
|
||||||
|
- Lifecycle/inspection/issues/scope-variance → live on the project; surfaced on owner detail, pipeline lead-project view, and dashboard counts.
|
||||||
|
|
||||||
|
## Components / files touched
|
||||||
|
- `src/data/mockStore.jsx` — identity remap, leads seed, financial fixes, breakdowns, 10 threaded scenarios, sub logins, deeper tables, pipeline values, lifecycle/inspection/estimate/scope data, sub-task stage data. (Large file; surgical/additive edits.)
|
||||||
|
- `src/data/selectors.js` — **new**, computed analytics + a `LIFECYCLE_STAGES`/progress map.
|
||||||
|
- Dashboards/components: `OwnerSnapshot.jsx`, `Dashboard.jsx`, leaderboard, `OwnerProjectDetail.jsx` (lifecycle/inspection/estimate UI + scope badge), `LeadProjectPage.jsx` (same surfaced for pipeline projects), `KanbanCard.jsx` (date anchor), `SubcontractorTasksPage.jsx`/`TaskViewModal.jsx`/`SubcontractorTaskDetailPage.jsx` (stage machine, On Hold, columns, richer modal).
|
||||||
|
|
||||||
## Out of scope (YAGNI)
|
## Out of scope (YAGNI)
|
||||||
- No backend/persistence changes (mock store only).
|
- No backend/persistence (mock store only); uploads are session-only.
|
||||||
- No new routes/pages beyond what exists.
|
- No Inspector login/role (external inspector stored as contact data).
|
||||||
- No visual redesign of components — data + selectors + minimal wiring only.
|
- No visual redesign beyond what these features require.
|
||||||
- The dormant `project.margin` field stays but is made honest; not wired into new UI.
|
|
||||||
|
|
||||||
## Build sequencing
|
## Build sequencing
|
||||||
1. Canonical identity + `resolvePerson` (everything depends on it).
|
1. Canonical identity + `resolvePerson`.
|
||||||
2. Financial coherence + missing breakdowns (so projects reconcile).
|
2. Financial coherence + missing breakdowns + variance signs.
|
||||||
3. Feature-breaking fixes (leads seed, sub logins, task↔project).
|
3. Feature-breaking fixes (leads seed, sub logins, task↔project).
|
||||||
4. Threaded scenarios (lead↔project↔task↔invoice↔commission).
|
4. Ten threaded scenarios incl. discrepancy cases.
|
||||||
5. Pipeline breadth + leaderboard.
|
5. Pipeline breadth + leaderboard.
|
||||||
6. `selectors.js` + dashboard wiring.
|
6. `selectors.js` + dashboard wiring.
|
||||||
7. Verify build + spot-check reconciliation.
|
7. Project lifecycle state machine + progress %.
|
||||||
|
8. Inspections (damage + final) + scope-variance badge + estimate versions.
|
||||||
|
9. Subcontractor task stage machine + UI.
|
||||||
|
10. Verify build + reconciliation spot-checks at each major step.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
- `npm run build` clean after each major step.
|
- `npm run build` clean after each major step.
|
||||||
- Reconciliation spot-checks: dashboard revenue == Σ project contract values; pipeline total == Σ lead values; a threaded scenario navigable lead→project→task→invoice→commission with consistent names/dates.
|
- Reconciliation: dashboard revenue == Σ project contract values; pipeline total == Σ lead values; a threaded scenario navigable lead→project→task→invoice→commission with consistent names/dates; a discrepancy project shows reported vs verified + revised estimate versions.
|
||||||
- Dev run: Leads page non-empty; each subcontractor login sees their tasks; no "Unknown"/empty owner labels.
|
- Dev run: Leads page non-empty; each subcontractor login sees their tasks; no "Unknown" labels; project progress % matches lifecycle stage.
|
||||||
|
|
||||||
|
## Open items (pending user input)
|
||||||
|
- **Stage-name tweaks:** user chose "mostly good, I'll tweak a few" for both the project lifecycle and subcontractor stage names — specific renames TBD before implementation.
|
||||||
|
|||||||
Reference in New Issue
Block a user