-- 카페 음성 선물쿠폰 MVP — cafe_merchants (2026-05-28, P1a Phase A) -- -- 카페 사장이 본인 카페를 등록하는 발행자 테이블. -- 요청서: doc/runbook/req_cafe_gift_mvp.md §1 -- 설계: doc/runbook/cafe_gift_p1a.md §2 sql/045 -- -- 핵심: -- - 카페 = 쿠폰 발행자·판매 당사자 (단일가맹점 → 선불업 면제) -- - status='draft' 셀프등록 / status='active' 운영자(icesgg) 수동 전환 (P1a 는 SQL UPDATE) -- - 통신판매중개 고지 의무 필드(상호/대표/주소/전화/사업자번호/통신판매신고번호) -- - brand_voice = '__default__' or '__spk__' (P1a 는 내장화자만, voice_clone P2) -- -- ⛔ 회귀 가드: -- - 2026-05-27 sql/043 사고 교훈 — 신규 테이블, 기존 CHECK 수정 X -- - CHECK 모두 신규 컬럼 only + nullable/default 명시 -- - RLS 활성 + owner-scoped + 공개 read 는 status='active' 만 CREATE TABLE IF NOT EXISTS public.cafe_merchants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), owner_user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, -- 단축 URL용 (P1a 는 미사용 NULL, P2 에 도입) slug TEXT UNIQUE, -- 공개 노출 + 통신판매중개 고지 (요청서 §0-1 #3) name TEXT NOT NULL, -- 상호명 representative TEXT, -- 대표자명 address TEXT, -- 사업장 주소 phone TEXT, -- 연락처 business_no TEXT, -- 사업자등록번호 telesales_no TEXT, -- 통신판매신고번호 (P1b 필수) -- 결제 (P1b 에서 채움) pg_submerchant_id TEXT, -- 음성편지 기본 보이스 — 손님이 변경 가능 (Plan 결정 #10) brand_voice TEXT NOT NULL DEFAULT '__default__', -- 운영자 게이트 (사용자 결정 #2 — draft 셀프등록 + admin 수동 active) status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','active','suspended')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_cafe_merchants_owner ON public.cafe_merchants (owner_user_id); CREATE INDEX IF NOT EXISTS idx_cafe_merchants_status ON public.cafe_merchants (status); ALTER TABLE public.cafe_merchants ENABLE ROW LEVEL SECURITY; -- 사장: 본인 카페 ALL DROP POLICY IF EXISTS cafe_merchants_owner ON public.cafe_merchants; CREATE POLICY cafe_merchants_owner ON public.cafe_merchants FOR ALL USING (owner_user_id = auth.uid()) WITH CHECK (owner_user_id = auth.uid()); -- 공개: status='active' 만 SELECT (중개 고지·구매 페이지용, anon 가능) DROP POLICY IF EXISTS cafe_merchants_public_read ON public.cafe_merchants; CREATE POLICY cafe_merchants_public_read ON public.cafe_merchants FOR SELECT USING (status = 'active'); CREATE OR REPLACE FUNCTION public._cafe_merchants_touch_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS cafe_merchants_touch_updated_at ON public.cafe_merchants; CREATE TRIGGER cafe_merchants_touch_updated_at BEFORE UPDATE ON public.cafe_merchants FOR EACH ROW EXECUTE FUNCTION public._cafe_merchants_touch_updated_at(); COMMENT ON TABLE public.cafe_merchants IS '카페 음성 선물쿠폰 발행자. 단일가맹점 선불업 면제. status=draft 셀프등록 → admin 수동 active.'; COMMENT ON COLUMN public.cafe_merchants.brand_voice IS '음성편지 기본 보이스: __default__ (Sohee) 또는 __spk__ (내장화자 8종). 손님이 구매 시 변경 가능.'; COMMENT ON COLUMN public.cafe_merchants.status IS 'draft: 셀프등록 직후 / active: 운영자 검증·게시 / suspended: 운영자 정지.';