docs(03-03): complete quote builder plan summary
- SUMMARY.md for plan 03-03 (2/2 tasks complete) - Covers quote-actions.ts, QuoteTab.tsx, admin-queries extension, page.tsx wiring - Security verification: requireAdmin on all actions, 0 quote_items in client-view
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
---
|
||||
phase: "03"
|
||||
plan: "03"
|
||||
subsystem: "quote-builder"
|
||||
tags: [quote, admin, server-actions, drizzle, security]
|
||||
dependency_graph:
|
||||
requires: ["03-01"]
|
||||
provides: ["quote-tab-ui", "quote-actions", "admin-quote-queries"]
|
||||
affects: ["src/lib/admin-queries.ts", "src/app/admin/clients/[id]/page.tsx"]
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns: ["Server Actions with requireAdmin guard", "useTransition for optimistic UI", "COALESCE SQL for label resolution", "leftJoin for optional catalog ref"]
|
||||
key_files:
|
||||
created:
|
||||
- src/app/admin/clients/[id]/quote-actions.ts
|
||||
- src/components/admin/tabs/QuoteTab.tsx
|
||||
modified:
|
||||
- src/lib/admin-queries.ts
|
||||
- src/app/admin/clients/[id]/page.tsx
|
||||
decisions:
|
||||
- "QuoteTab is a Client Component (useTransition + useRouter) — actions called via startTransition, router.refresh() for revalidation"
|
||||
- "updateAcceptedTotal in quote-actions.ts is separate from the one in actions.ts — scoped to quote tab, adds requireAdmin guard"
|
||||
- "Service price pre-filled in catalog mode but editable — allows overriding price at quote time (snapshot semantics)"
|
||||
metrics:
|
||||
duration: "~15 min"
|
||||
completed_date: "2026-05-17T09:45:11Z"
|
||||
tasks_completed: 2
|
||||
tasks_total: 2
|
||||
files_created: 2
|
||||
files_modified: 2
|
||||
---
|
||||
|
||||
# Phase 03 Plan 03: Quote Builder Tab Summary
|
||||
|
||||
**One-liner:** Admin quote builder tab with catalog dropdown, freeform toggle, items table with calculated total, and accepted_total editor — all backed by Zod-validated Server Actions with requireAdmin guard.
|
||||
|
||||
## Tasks Completed
|
||||
|
||||
| Task | Name | Commit | Files |
|
||||
|------|------|--------|-------|
|
||||
| 1 | Server Actions + extend getClientFullDetail | db81829 | quote-actions.ts, admin-queries.ts |
|
||||
| 2 | QuoteTab component + wire into client detail page | 48f81e7 | QuoteTab.tsx, page.tsx |
|
||||
|
||||
## What Was Built
|
||||
|
||||
**Task 1 — Server Actions + Query Layer**
|
||||
|
||||
- `src/app/admin/clients/[id]/quote-actions.ts`: Three Server Actions exported:
|
||||
- `addQuoteItem(clientId, formData)` — Zod validates service_id/custom_label, quantity, unit_price; computes subtotal; inserts into `quote_items`
|
||||
- `removeQuoteItem(quoteItemId, clientId)` — deletes by item ID
|
||||
- `updateAcceptedTotal(clientId, formData)` — writes to `clients.accepted_total` only (no payment row splitting — that stays in `actions.ts`)
|
||||
- All three call `requireAdmin()` (getServerSession check) before any DB operation
|
||||
- `src/lib/admin-queries.ts`:
|
||||
- Added `QuoteItemWithLabel` type (COALESCE resolved label, snapshotted unit_price)
|
||||
- Extended `ClientFullDetail` with `quoteItems: QuoteItemWithLabel[]` and `activeServices: ServiceCatalog[]`
|
||||
- Added two queries in `getClientFullDetail()`: leftJoin for quote items with COALESCE label; active services ordered by name
|
||||
- Security comment enforces that `client-view.ts` must never query `quote_items` (verified: 0 functional references)
|
||||
|
||||
**Task 2 — UI Component + Page Wiring**
|
||||
|
||||
- `src/components/admin/tabs/QuoteTab.tsx` (`"use client"`) — three sections:
|
||||
- **Add items**: catalog dropdown (pre-fills unit_price on selection, editable) + freeform toggle (custom_label + price + qty)
|
||||
- **Items table**: label, qty, unit_price, subtotal columns; "Rimuovi" button per row; "Totale calcolato" in bold footer
|
||||
- **Accepted total**: editable numeric input with "Salva" button + helper text clarifying client sees only this value
|
||||
- `src/app/admin/clients/[id]/page.tsx`:
|
||||
- Import `QuoteTab`
|
||||
- Destructure `quoteItems`, `activeServices` from `getClientFullDetail` result
|
||||
- Added 5th `TabsTrigger value="quote"` with label "Preventivo"
|
||||
- Added 5th `TabsContent value="quote"` rendering `<QuoteTab>`
|
||||
|
||||
## Security Verification
|
||||
|
||||
| Constraint | Status |
|
||||
|------------|--------|
|
||||
| T-03-03-01: requireAdmin on all Server Actions | Done — all three actions call `await requireAdmin()` first |
|
||||
| T-03-03-02: Zod validation on formData numbers | Done — `quoteItemSchema` validates quantity + unit_price as `z.coerce.number().min(0.01)` |
|
||||
| T-03-03-03: quote_items not in client-facing routes | Done — client-view.ts has 0 functional references to quote_items (only comments) |
|
||||
| T-03-03-04: IDOR on removeQuoteItem | Mitigated by requireAdmin; future multi-admin scenario noted for future hardening |
|
||||
| T-03-03-05: XSS in custom_label | Accepted — React JSX auto-escapes, no dangerouslySetInnerHTML used |
|
||||
| T-03-03-06: calculated_total vs accepted_total confusion | Accepted — visual design enforces separation |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
**1. [Rule 3 - Blocking] Build required DATABASE_URL env var not present in worktree**
|
||||
- **Found during:** Task 2 build verification
|
||||
- **Issue:** Worktree has no `.env.local`; build fails with "DATABASE_URL env var is required" at runtime collection phase
|
||||
- **Fix:** Ran build with `DATABASE_URL=$(grep DATABASE_URL /path/.env.local ...)` from main repo — build passed clean
|
||||
- **Impact:** None on code quality; worktree environment limitation only
|
||||
|
||||
**2. [Rule 1 - Architecture] updateAcceptedTotal in quote-actions.ts does NOT update payment rows**
|
||||
- **Found during:** Task 1 implementation
|
||||
- **Rationale:** The existing `updateAcceptedTotal` in `actions.ts` splits the total 50/50 between payment rows. The quote tab version intentionally only writes to `clients.accepted_total` — this is the quote builder's domain. Payment row updates remain in the payments tab action. This preserves clean separation of concerns.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None — all three sections are fully wired to real Server Actions and real DB queries.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None — all new surface is admin-only, guarded by `requireAdmin()`, and consistent with the plan's threat model.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- `src/app/admin/clients/[id]/quote-actions.ts` exists and exports 3 Server Actions
|
||||
- `src/components/admin/tabs/QuoteTab.tsx` exists and exports QuoteTab
|
||||
- `src/lib/admin-queries.ts` modified with QuoteItemWithLabel type + quoteItems/activeServices in return
|
||||
- `src/app/admin/clients/[id]/page.tsx` modified with Preventivo tab
|
||||
- Commits db81829 and 48f81e7 verified in git log
|
||||
- TypeScript: no errors
|
||||
- Build: passes (with DATABASE_URL)
|
||||
- client-view.ts: 0 functional references to quote_items
|
||||
Reference in New Issue
Block a user