From 06660c3fe635d69ee06bf8d214047e79221c3bb1 Mon Sep 17 00:00:00 2001 From: Simone Cavalli Date: Thu, 11 Jun 2026 06:54:39 +0200 Subject: [PATCH] =?UTF-8?q?docs(08):=20phase=208=20planning=20complete=20?= =?UTF-8?q?=E2=80=94=201=20plan=20(offer=20phases=20+=20quote=20templates?= =?UTF-8?q?=20schema)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/ROADMAP.md | 29 +- .../08-offer-phases-quote-builder/08-PLAN.md | 421 ++++++++++++++++++ 2 files changed, 437 insertions(+), 13 deletions(-) create mode 100644 .planning/phases/08-offer-phases-quote-builder/08-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 793a3e2..631c507 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -160,25 +160,28 @@ Decimal phases appear between their surrounding integers in numeric order. 4. Query vecchie servizi falliscono esplicitamente (no silent fallback) **Plans**: 2 plans **Plan list**: - - [ ] 07-01-PLAN.md — Schema: services table (audit trail) + backfill + zero-data-loss validation - - [ ] 07-02-PLAN.md — Admin catalog CRUD rewired to services table + e2e verification + - [x] 07-01-PLAN.md — Schema: services table (audit trail) + backfill + zero-data-loss validation + - [x] 07-02-PLAN.md — Admin catalog CRUD rewired to services table + e2e verification **Durata**: 3–4 giorni -**Status**: Planning complete +**Status**: ✅ Planning complete (execution in progress) **Risk**: SQL deduplication logic; validate on staging before prod ### Phase 8: Offer Phases & Quote Templates -**Goal**: Struttura hierarchica offer (macro → micro → phase → services) con drag&drop builder +**Goal**: Struttura hierarchica offer (macro → micro → phase → services) per quote builder e auto-provisioning **Mode:** schema + ui **Depends on**: Phase 7 **Requirements**: OFFER-B-01, OFFER-B-02, OFFER-B-03 **Success Criteria** (what must be TRUE): 1. Nuove tabelle: `offer_phases`, `quotes`, `offer_phase_services` - 2. `/admin/offers/[id]/edit` builder two-column: left servizi (ricerca), right fasi con drag&drop - 3. Drag&drop tra fasi e dentro fase (reorder); salva per singolo drop con optimistic update - 4. Versioning previene race condition da multi-tab edit (409 Conflict su version mismatch) -**Plans**: 2 (schema + builder UI) -**Durata**: 2–3 giorni -**Status**: Pending planning + 2. Admin può visualizzare offer micro suddivisa in fasi, ogni fase con servizi unificati assegnati + 3. Quote sono record header separati da line items (quote_items) + 4. Quote pages possono copiare struttura offer per mostrare fasi + servizi ai lead +**Plans**: 1 plan (schema foundation for Phases 9-11) +**Plan list**: + - [ ] 08-01-PLAN.md — Schema: offer_phases, offer_phase_services, quotes + FKs + query layer + types +**Durata**: 1–2 giorni +**Status**: ✅ Planning complete +**Notes**: Schema-only phase (no UI in Phase 8). UI builder in Phase 9. ### Phase 9: Quote Builder & Public Routes **Goal**: Generazione preventivi 2-click (select offer → override prezzi → link pubblico); pagina multistep pubblico per lead @@ -254,12 +257,12 @@ Phases 7 → 8 → 9 → 10 → 11 planned for sequential execution. Research co | Phase | Plans | Status | Estimated Duration | Blocking? | |-------|-------|--------|-------------------|-----------| -| 7. Unified Service Catalog | 2 | Planning complete | 3–4 giorni | Foundation | -| 8. Offer Phases & Quote Templates | 2 | Pending planning | 2–3 giorni | Foundation | +| 7. Unified Service Catalog | 2 | ✅ Planning complete | 3–4 giorni | Foundation | +| 8. Offer Phases & Quote Templates | 1 | ✅ Planning complete | 1–2 giorni | Foundation | | 9. Quote Builder & Public Routes | 3 | Pending planning | 4–5 giorni | RHF compat test | | 10. CRM Pipeline & Activity Logging | 3 | Pending planning | 3–4 giorni | No | | 11. Auto-Provisioning on Win | 2 | Pending planning | 2–3 giorni | BullMQ ops (optional) | -| **Total v2.0 MVP** | **11** | Pending | **14–19 giorni** | — | +| **Total v2.0 MVP** | **11** | Pending | **13–18 giorni** | — | **v2.0 Deferred (Phase 12+):** - Phase 12: Email Automation & Polish diff --git a/.planning/phases/08-offer-phases-quote-builder/08-PLAN.md b/.planning/phases/08-offer-phases-quote-builder/08-PLAN.md new file mode 100644 index 0000000..815e779 --- /dev/null +++ b/.planning/phases/08-offer-phases-quote-builder/08-PLAN.md @@ -0,0 +1,421 @@ +--- +phase: 08-offer-phases-quote-builder +plan: 01 +type: execute +wave: 1 +depends_on: ["07-01", "07-02"] +files_modified: + - src/db/schema.ts + - src/db/migrations/0003_offer_phases_quote_templates.sql + - src/lib/admin-queries.ts + - src/types/offer.ts +autonomous: true +requirements: + - OFFER-B-01 + - OFFER-B-02 + - OFFER-B-03 +must_haves: + truths: + - "Admin can view offer micro structure broken down into phases" + - "Each offer phase can have services assigned from unified services table" + - "Quotes are stored as separate header records with items linked back" + - "Quote pages can copy offer structure to show phases + services to leads" + artifacts: + - path: src/db/schema.ts + provides: "offer_phases, offer_phase_services, quotes table definitions + types" + contains: "export const offer_phases, offer_phase_services, quotes" + - path: src/db/migrations/0003_offer_phases_quote_templates.sql + provides: "SQL migration adding all new tables with zero data loss" + creates: "offer_phases, offer_phase_services, quotes + FK updates" + - path: src/lib/admin-queries.ts + provides: "getOfferPhases, getOfferPhaseServices queries for builder UI" + exports: ["getOfferPhases(microId)", "getOfferPhaseServices(phaseId)"] + - path: src/types/offer.ts + provides: "TypeScript types for offer phases, quote records" + exports: ["OfferPhase", "OfferPhaseService", "Quote", "NewQuote"] + key_links: + - from: "offer_phases" + to: "offer_micros" + via: "foreign key micro_id" + pattern: "micro_id.*references.*offer_micros" + - from: "offer_phase_services" + to: "services" + via: "foreign key service_id" + pattern: "service_id.*references.*services" + - from: "quotes" + to: "offer_micros" + via: "offer_micro_id field (nullable, tracks which tier was quoted)" + pattern: "offer_micro_id.*references.*offer_micros" +--- + + +Add hierarchical offer phases structure and quote headers to ClientHub. This schema layer enables Phase 9 (quote builder UI + public routes) and Phase 11 (auto-provisioning on win). + +**Purpose:** Offer micros currently have no phase structure. Phase 8 introduces `offer_phases` (Discovery → Strategy → Execution), `offer_phase_services` (services assigned to each phase), and `quotes` (quote headers separate from line items). This enables: +- Quote builder to pre-populate from offer structure +- Public quote pages to show phases + services to leads +- Auto-provisioning to copy offer phases → project phases on win + +**Output:** Three new database tables (offer_phases, offer_phase_services, quotes), updated quote_items schema, migration script, types, and query layer. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/research/ARCHITECTURE.md +@.planning/research/FEATURES_v2.0.md +@.planning/phases/07-unified-service-catalog/07-02-SUMMARY.md + + + + + + Task 1: Add offer_phases, offer_phase_services, and quotes tables to schema + src/db/schema.ts, src/types/offer.ts + +Add three new table definitions to schema.ts (after existing offer_micros section): + +1. **offer_phases** table — hierarchical breakdown of an offer micro into phases: + - id (text PK, nanoid) + - micro_id (text FK → offer_micros, onDelete cascade) + - title (text, not null) — "Discovery", "Strategy", "Execution", etc. + - description (text, nullable) + - sort_order (integer, default 0) — display order + - duration_weeks (integer, nullable) — optional duration for timeline + - created_at (timestamp) + Note: NOT an edit of existing offer_micro_services; this is new. Offer micros previously had flat offer_services. Now they have hierarchical phases + services within phases. + +2. **offer_phase_services** junction table — assigns services to offer phases: + - phase_id (text FK → offer_phases, onDelete cascade) + - service_id (text FK → services, onDelete cascade) + - Primary key: (phase_id, service_id) + Purpose: Replaces the old flat offer_micro_services path (that remains for backward compat). New builder uses phases. + +3. **quotes** table — separate quote header from line items: + - id (text PK, nanoid) + - lead_id (text FK → leads, onDelete cascade, NULLABLE — for standalone quotes to clients) + - client_id (text FK → clients, onDelete cascade, NULLABLE — for direct client quotes) + - token (text, not null, unique) — nanoid for public page access (/quote/[token]) + - offer_micro_id (text FK → offer_micros, onDelete set null, NULLABLE) — which tier was quoted (for audit) + - total_amount (numeric(10,2), not null) — calculated sum of quote_items + - status (text, default 'draft') — draft | sent | viewed | accepted | rejected + - accepted_at (timestamp, nullable, immutable once set) + - accepted_by_email (text, nullable) — lead/client email on acceptance + - accepted_by_name (text, nullable) — signer name on acceptance + - created_at (timestamp) + - updated_at (timestamp) + +Add TypeScript types in src/types/offer.ts (new file or extend if exists): +- OfferPhase, NewOfferPhase +- OfferPhaseService, NewOfferPhaseService +- Quote, NewQuote + Infer from Drizzle tables using $inferSelect/$inferInsert pattern (per CLAUDE.md style). + +Update existing quote_items table schema (NO DATA CHANGES): +- Add quote_id (text FK → quotes, onDelete cascade) — link items to quote header +- Add offer_micro_id (text FK → offer_micros, onDelete set null, NULLABLE) — which tier (audit) +- Add offer_phase_id (text FK → offer_phases, onDelete set null, NULLABLE) — which phase within tier (audit) +- Keep existing: project_id, service_id, quantity, unit_price, subtotal, custom_label + +Update projects table schema (NO DATA CHANGES): +- Add offer_id (text FK → offer_micros, onDelete set null, NULLABLE) — tracks template origin +- Add created_from_lead_id (text, NULLABLE) — which lead this project came from (for audit) + +Update phases table schema (NO DATA CHANGES): +- Add offer_phase_id (text FK → offer_phases, onDelete set null, NULLABLE) — audit trail to original template phase + +Add relations in schema.ts: +- offerPhasesRelations: micro (one) → micro.id, phaseServices (many) +- offerPhaseServicesRelations: phase (one), service (one) → services +- quotesRelations: leadId (one) → leads, clientId (one) → clients, micro (one) → offer_micros, items (many) → quote_items +- update quote_items relations to include quote (one), offerMicro (one), offerPhase (one) +- update projects relations to include offer (one), created_from_lead (implicit, no direct relation needed) +- update phases relations to include offerPhase (one) + +Validation: `npm run build` must pass with no TypeScript errors. + + + npm run build 2>&1 | grep -E "(error|ERR)" || echo "Build passed" + + +All four tables defined in schema.ts with correct FK relationships. All TypeScript types exported from types/offer.ts. No data modifications (additive-only). Build passes. + + + + + Task 2: Create Drizzle migration file (SQL + idempotent validation) + src/db/migrations/0003_offer_phases_quote_templates.sql, scripts/validate-phase8-migration.ts + +Create SQL migration file `src/db/migrations/0003_offer_phases_quote_templates.sql` following Phase 7 pattern (additive-first, zero data loss, rollback-safe): + +```sql +-- Phase 8: Offer Phases & Quote Templates (Additive-First Migration) + +-- Create offer_phases table (hierarchical breakdown of offer micros) +CREATE TABLE IF NOT EXISTS offer_phases ( + id TEXT PRIMARY KEY, + micro_id TEXT NOT NULL REFERENCES offer_micros(id) ON DELETE CASCADE, + title TEXT NOT NULL, + description TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + duration_weeks INTEGER, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE(micro_id, title) +); +CREATE INDEX IF NOT EXISTS idx_offer_phases_micro_id ON offer_phases(micro_id); + +-- Create offer_phase_services junction (replaces flat offer_micro_services path) +CREATE TABLE IF NOT EXISTS offer_phase_services ( + phase_id TEXT NOT NULL REFERENCES offer_phases(id) ON DELETE CASCADE, + service_id TEXT NOT NULL REFERENCES services(id) ON DELETE CASCADE, + PRIMARY KEY (phase_id, service_id) +); +CREATE INDEX IF NOT EXISTS idx_offer_phase_services_service_id ON offer_phase_services(service_id); + +-- Create quotes table (separate header from line items) +CREATE TABLE IF NOT EXISTS quotes ( + id TEXT PRIMARY KEY, + lead_id TEXT REFERENCES leads(id) ON DELETE CASCADE, + client_id TEXT REFERENCES clients(id) ON DELETE CASCADE, + token TEXT NOT NULL UNIQUE, + offer_micro_id TEXT REFERENCES offer_micros(id) ON DELETE SET NULL, + total_amount NUMERIC(10, 2) NOT NULL, + status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sent', 'viewed', 'accepted', 'rejected')), + accepted_at TIMESTAMP WITH TIME ZONE, + accepted_by_email TEXT, + accepted_by_name TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE INDEX IF NOT EXISTS idx_quotes_token ON quotes(token); +CREATE INDEX IF NOT EXISTS idx_quotes_client_id ON quotes(client_id); +CREATE INDEX IF NOT EXISTS idx_quotes_lead_id ON quotes(lead_id); +CREATE INDEX IF NOT EXISTS idx_quotes_status ON quotes(status); + +-- Update quote_items: add quote_id + offer context FKs (additive, no drops) +-- IF quote_id column doesn't exist, add it +ALTER TABLE quote_items ADD COLUMN IF NOT EXISTS quote_id TEXT REFERENCES quotes(id) ON DELETE CASCADE; +ALTER TABLE quote_items ADD COLUMN IF NOT EXISTS offer_micro_id TEXT REFERENCES offer_micros(id) ON DELETE SET NULL; +ALTER TABLE quote_items ADD COLUMN IF NOT EXISTS offer_phase_id TEXT REFERENCES offer_phases(id) ON DELETE SET NULL; + +-- Create indexes on new FK columns +CREATE INDEX IF NOT EXISTS idx_quote_items_quote_id ON quote_items(quote_id); +CREATE INDEX IF NOT EXISTS idx_quote_items_offer_micro_id ON quote_items(offer_micro_id); +CREATE INDEX IF NOT EXISTS idx_quote_items_offer_phase_id ON quote_items(offer_phase_id); + +-- Update projects: add offer_id + created_from_lead_id (additive, no drops) +ALTER TABLE projects ADD COLUMN IF NOT EXISTS offer_id TEXT REFERENCES offer_micros(id) ON DELETE SET NULL; +ALTER TABLE projects ADD COLUMN IF NOT EXISTS created_from_lead_id TEXT; + +CREATE INDEX IF NOT EXISTS idx_projects_offer_id ON projects(offer_id); +CREATE INDEX IF NOT EXISTS idx_projects_created_from_lead_id ON projects(created_from_lead_id); + +-- Update phases: add offer_phase_id (additive, no drops) +ALTER TABLE phases ADD COLUMN IF NOT EXISTS offer_phase_id TEXT REFERENCES offer_phases(id) ON DELETE SET NULL; + +CREATE INDEX IF NOT EXISTS idx_phases_offer_phase_id ON phases(offer_phase_id); + +-- NOTE: leads table is not created here (Phase 10 CRM). This migration assumes leads exists when quotes.lead_id is used. +-- For Phase 8 standalone, lead_id is nullable; Phase 10 will populate it. +``` + +Create validation script `scripts/validate-phase8-migration.ts` that: +- Checks all four new tables exist (offer_phases, offer_phase_services, quotes) +- Verifies indexes created +- Counts rows in each table (expect mostly empty except offer_phases if backfilled from offer_micros) +- Verifies FKs intact (sample query: SELECT * FROM offer_phases WHERE micro_id IS NULL should return 0) +- Confirms quote_items.quote_id, offer_micro_id, offer_phase_id columns added +- Confirms projects.offer_id, created_from_lead_id columns added +- Confirms phases.offer_phase_id column added +- Exit code 0 if all checks pass, 1 if any fail + +Note: NO DATA MIGRATION in this phase. offer_phases table is empty until Phase 9 quote builder or admin manually populates it. Existing offer_micro_services junction remains untouched (backward compat). + + + ls -lh src/db/migrations/0003_offer_phases_quote_templates.sql && wc -l src/db/migrations/0003_offer_phases_quote_templates.sql + + +Migration file created (additive-only, ~100 lines). Validation script written. Both checked into src/db/migrations/ and scripts/. Ready for Postgres connectivity. + + + + + Task 3: Add query layer for offer phases + update quote_items relations + src/lib/admin-queries.ts, src/db/schema.ts (relations update) + +Add new query functions to src/lib/admin-queries.ts: + +1. **getOfferPhases(microId: string)** → Promise + Query: SELECT * FROM offer_phases WHERE micro_id = ? ORDER BY sort_order ASC + Returns: All phases for a given offer micro (used by quote builder, admin phase editor) + +2. **getOfferPhaseServices(phaseId: string)** → Promise + Query: SELECT s.* FROM services s + JOIN offer_phase_services ops ON s.id = ops.service_id + WHERE ops.phase_id = ? + Returns: All unified services assigned to a phase (used by quote builder, public quote page) + +3. **getQuoteById(quoteId: string)** → Promise + Query: SELECT * FROM quotes WHERE id = ? (with relations: lead, client, micro, items) + Returns: Full quote header + items (used by quote detail page) + +4. **getQuoteByToken(token: string)** → Promise + Query: SELECT * FROM quotes WHERE token = ? (with relations) + Returns: Quote by public token (used by /quote/[token] route validation) + +5. **getQuotesByClient(clientId: string)** → Promise + Query: SELECT * FROM quotes WHERE client_id = ? ORDER BY created_at DESC + Returns: All quotes for a client (used by /admin/clients/[id] detail page) + +Update schema.ts relations to include: +- quotesRelations: include lead (one), client (one), micro (one), items (many) +- quote_items relations update: include quote (one), offerMicro (one), offerPhase (one) +- offerPhasesRelations: include micro (one), services (many) + +Update existing getProjectFullDetail and getClientFullDetail queries to optionally include activeOffers (unchanged from Phase 7 — no breaking changes). + +All queries use Drizzle ORM with proper type inference. + +Validation: TypeScript build must pass. Query functions properly typed with import/export verified. + + + grep -c "getOfferPhases\|getOfferPhaseServices\|getQuoteById\|getQuoteByToken" src/lib/admin-queries.ts + + +All five query functions added to admin-queries.ts. Relations updated in schema.ts. TypeScript types properly inferred from Drizzle. No breaking changes to existing queries. + + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Admin → Quote builder | Admin is trusted to create/edit quotes; quotes are immutable once sent to lead | +| Public link → Quote view | Anyone with quote token can view; acceptance is gated by token verification | +| Quote acceptance | Once accepted_at is set, quote is final (immutable); triggers phase 11 auto-provisioning | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-08-01 | Tampering | offer_phases table | mitigate | offer_phase_id is immutable in projects.phases (audit trail, template reference only, phases editable after creation) | +| T-08-02 | Tampering | quotes table | mitigate | quotes.token is unique; quotes.accepted_at immutable once set via DB constraint | +| T-08-03 | Information Disclosure | quote_items via quote_id FK | mitigate | Quote items never exposed via public API; only quote header visible in /quote/[token] (items displayed, but not direct query access) | +| T-08-04 | Spoofing | quote.accepted_by_email | mitigate | Email captured from form submission in Phase 9; no validation in this phase (validation happens on submission, not query) | +| T-08-05 | Tampering | quote_items → offer_phase_id | accept | Nullable audit field; if NULL, phase unknown (acceptable for legacy quote items created before Phase 8) | + +**No new public endpoints in Phase 8** (schema-only). Threat surface limited to admin mutations via Phase 7 patterns (requireAdmin + Auth.js session check). + + + + +**Phase 8 Completion Checklist:** + +1. **Schema definitions:** + - [ ] offer_phases table created with micro_id, title, sort_order, duration_weeks + - [ ] offer_phase_services junction created with FK to services (unified catalog) + - [ ] quotes table created with token, status, accepted_at immutable fields + - [ ] quote_items.quote_id, offer_micro_id, offer_phase_id columns added + - [ ] projects.offer_id, created_from_lead_id columns added + - [ ] phases.offer_phase_id column added + - [ ] All FKs point to correct tables with cascade/set null as specified + +2. **Relations:** + - [ ] offer_phases → offer_micros (one micro to many phases) + - [ ] offer_phase_services → offer_phases + services (many-to-many) + - [ ] quotes → clients, leads, offer_micros (nullable) + - [ ] quote_items → quotes (new FK) + +3. **Types:** + - [ ] OfferPhase, NewOfferPhase exported from types/offer.ts + - [ ] OfferPhaseService, NewOfferPhaseService exported + - [ ] Quote, NewQuote exported + - [ ] All types properly inferred from Drizzle + +4. **Migration:** + - [ ] Migration file created in src/db/migrations/0003_*.sql + - [ ] Additive-only (no drops, no truncates) + - [ ] All new tables + columns with proper constraints + - [ ] Indexes created for FKs and common filters (status, token, client_id, micro_id) + +5. **Query layer:** + - [ ] getOfferPhases(microId) implemented + - [ ] getOfferPhaseServices(phaseId) implemented + - [ ] getQuoteById, getQuoteByToken implemented + - [ ] getQuotesByClient implemented + - [ ] All queries return typed results (OfferPhase[], Service[], Quote, Quote[], etc.) + +6. **Build & Type Safety:** + - [ ] `npm run build` passes with zero TypeScript errors + - [ ] No unused imports or exports + - [ ] All new types properly exported and consumed + +7. **Backward Compatibility:** + - [ ] Existing offer_micro_services junction untouched + - [ ] Existing quote_items functionality unchanged (quote_id/offer_micro_id nullable for legacy items) + - [ ] No breaking changes to existing queries or API surfaces + +8. **Data Safety (LOCKED):** + - [ ] No ALTER TABLE DROP on existing columns + - [ ] No DELETE or TRUNCATE on clients, projects, payments, phases, quote_items + - [ ] Migration additive-only (ADD COLUMN, CREATE TABLE) + - [ ] Rollback possible: tables remain, old columns preserved + + + + +Phase 8 is complete when: + +1. **Database schema extended:** + - Three new tables (offer_phases, offer_phase_services, quotes) exist in schema.ts with all fields, FKs, and indexes + - quote_items, projects, phases tables extended with new audit columns (quote_id, offer_micro_id, offer_phase_id, offer_id, created_from_lead_id, offer_phase_id) + - All relations defined in Drizzle + - `npm run build` passes + +2. **Migration script ready:** + - SQL migration file created in src/db/migrations/0003_offer_phases_quote_templates.sql + - File is additive-only (no drops/truncates) + - Validation script in scripts/validate-phase8-migration.ts checks all constraints + - Both files committed to git + +3. **Query layer functional:** + - Five new query functions added to src/lib/admin-queries.ts (getOfferPhases, getOfferPhaseServices, getQuoteById, getQuoteByToken, getQuotesByClient) + - All queries properly typed with Drizzle relations + - Existing queries unchanged (backward compatible) + +4. **Types exported:** + - OfferPhase, NewOfferPhase, OfferPhaseService, NewOfferPhaseService, Quote, NewQuote in src/types/offer.ts + - All types properly inferred from schema + +5. **Ready for Phase 9:** + - Phase 9 can now reference offer_phases + offer_phase_services in quote builder UI + - Phase 9 can reference quotes table for quote header management + - Public quote routes can validate by token via getQuoteByToken + - Auto-provisioning can copy offer_phases → project.phases via offer_phase_id audit trail + +6. **Data Safety verified:** + - No production data deleted or truncated + - Migration safe to run on staging/prod after connectivity restored + - Rollback possible at any point (all additive) + + + + +After completion, create `.planning/phases/08-offer-phases-quote-builder/08-01-SUMMARY.md` with: +- What was built (tables, migration, queries, types) +- Task completion status (3/3) +- Build verification (`npm run build` output snippet) +- Migration readiness (script path, validation checklist) +- Next phase dependencies (Phase 9 can start after this completes) +- Deviations (if any) +- Files committed to git +