// PRODUCTION DEPLOY NOTE: This migration (0008_offer_tier_schema.sql) is additive-only // (ADD COLUMN IF NOT EXISTS, a guarded DO-block CHECK constraint, CREATE TABLE IF NOT // EXISTS, and CREATE INDEX IF NOT EXISTS — no drops/truncates). Per CLAUDE.md Data // Safety, this script MUST be run against PRODUCTION via SSH+docker exec BEFORE Phase // 12 Wave 3 code (query layer reading offer_tier_services/tier_letter/public_price/ // category/ticket) is exercised against production data. This is the BLOCKING step // for Plan 02. Idempotent — safe to re-run. // Run: npx tsx scripts/push-12-offer-tier-schema.ts (with prod DATABASE_URL) import postgres from "postgres"; async function push() { const databaseUrl = process.env.DATABASE_URL; if (!databaseUrl) { console.error("DATABASE_URL environment variable is required"); process.exit(1); } const client = postgres(databaseUrl); try { console.log("Pushing offer tier schema migration (0008)..."); // offer_macros: archive flag + short description + category/ticket dimensions + // structured transformation promise const offerMacrosColumns: Array<[string, string]> = [ ["description", "text"], ["category", "text"], ["ticket", "text"], ["is_archived", "boolean NOT NULL DEFAULT false"], ["cliente_ideale", "text"], ["risultato", "text"], ["tempo", "text"], ["pain", "text"], ["metodo", "text"], ]; for (const [column, type] of offerMacrosColumns) { try { await client.unsafe( `ALTER TABLE offer_macros ADD COLUMN IF NOT EXISTS ${column} ${type}` ); console.log(` ✓ offer_macros.${column} ready`); } catch (err: unknown) { if (err instanceof Error && err.message.includes("already exists")) { console.log(` ✓ offer_macros.${column} already exists (skipped)`); } else { throw err; } } } // offer_micros: tier designation (A/B/C) + manual public price const offerMicrosColumns: Array<[string, string]> = [ ["tier_letter", "text"], ["public_price", "numeric(10, 2)"], ]; for (const [column, type] of offerMicrosColumns) { try { await client.unsafe( `ALTER TABLE offer_micros ADD COLUMN IF NOT EXISTS ${column} ${type}` ); console.log(` ✓ offer_micros.${column} ready`); } catch (err: unknown) { if (err instanceof Error && err.message.includes("already exists")) { console.log(` ✓ offer_micros.${column} already exists (skipped)`); } else { throw err; } } } // CHECK constraint for tier_letter, guarded so it's safe to re-run. try { await client.unsafe(` DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'offer_micros_tier_letter_check' ) THEN ALTER TABLE offer_micros ADD CONSTRAINT offer_micros_tier_letter_check CHECK (tier_letter IS NULL OR tier_letter IN ('A', 'B', 'C')); END IF; END $$; `); console.log(" ✓ offer_micros_tier_letter_check constraint ready"); } catch (err: unknown) { if (err instanceof Error && err.message.includes("already exists")) { console.log(" ✓ offer_micros_tier_letter_check already exists (skipped)"); } else { throw err; } } // New junction table: tier (offer_micros) <-> unified services catalog. try { await client.unsafe(` CREATE TABLE IF NOT EXISTS offer_tier_services ( tier_id text NOT NULL REFERENCES offer_micros(id) ON DELETE CASCADE, service_id text NOT NULL REFERENCES services(id) ON DELETE CASCADE, created_at timestamp with time zone DEFAULT now() NOT NULL, PRIMARY KEY (tier_id, service_id) ) `); console.log(" ✓ offer_tier_services table ready"); } catch (err: unknown) { if (err instanceof Error && err.message.includes("already exists")) { console.log(" ✓ offer_tier_services table already exists (skipped)"); } else { throw err; } } try { await client.unsafe(` CREATE INDEX IF NOT EXISTS offer_tier_services_tier_idx ON offer_tier_services USING btree (tier_id) `); console.log(" ✓ offer_tier_services_tier_idx index ready"); } catch (err: unknown) { if (err instanceof Error && err.message.includes("already exists")) { console.log(" ✓ offer_tier_services_tier_idx already exists (skipped)"); } else { throw err; } } console.log("✓ Migration 0008 (offer tier schema) applied successfully"); process.exit(0); } catch (err: unknown) { if (err instanceof Error) { console.error("Error pushing migration:", err.message); } else { console.error("Unknown error:", err); } process.exit(1); } finally { await client.end(); } } push();