Files
clienthub/.planning/phases/03-service-catalog-quote-builder/03-03-SUMMARY.md
T
Simone Cavalli 8641253e85 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
2026-05-17 11:46:11 +02:00

6.0 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
03 03 quote-builder
quote
admin
server-actions
drizzle
security
requires provides affects
03-01
quote-tab-ui
quote-actions
admin-quote-queries
src/lib/admin-queries.ts
src/app/admin/clients/[id]/page.tsx
added patterns
Server Actions with requireAdmin guard
useTransition for optimistic UI
COALESCE SQL for label resolution
leftJoin for optional catalog ref
created modified
src/app/admin/clients/[id]/quote-actions.ts
src/components/admin/tabs/QuoteTab.tsx
src/lib/admin-queries.ts
src/app/admin/clients/[id]/page.tsx
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)
duration completed_date tasks_completed tasks_total files_created files_modified
~15 min 2026-05-17T09:45:11Z 2 2 2 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