-- Merchant onboarding: a prospective seller (personal 个人 or enterprise 企业) -- applies once, a platform admin reviews it, and approval provisions the shop -- and its dedicated shop_owner account in one transaction. CREATE TYPE merchant_entity_type AS ENUM ('personal', 'enterprise'); CREATE TYPE merchant_application_status AS ENUM ('pending', 'approved', 'rejected'); CREATE TABLE merchant_applications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE, entity_type merchant_entity_type NOT NULL, -- Personal-kind entity information. real_name TEXT, -- Enterprise-kind entity information. company_name TEXT, business_license_no TEXT, -- Operating categories, one or more rows of the reference category tree. category_ids UUID[] NOT NULL DEFAULT '{}', -- Contact details, shared by both kinds. contact_name TEXT NOT NULL, contact_phone TEXT NOT NULL, contact_email TEXT NOT NULL, contact_address TEXT, -- Qualification materials are URLs only; no file storage in this MVP. identity_document_url TEXT, business_license_url TEXT, extra_materials JSONB NOT NULL DEFAULT '[]'::jsonb, status merchant_application_status NOT NULL DEFAULT 'pending', rejection_reason TEXT, reviewed_by UUID REFERENCES users (id), reviewed_at TIMESTAMPTZ, -- Shop created by approval; null until then. created_shop_id UUID REFERENCES shops (id), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- Each kind carries its own required entity + qualification fields. CONSTRAINT merchant_applications_kind_fields CHECK ( ( entity_type = 'personal' AND real_name IS NOT NULL AND btrim(real_name) <> '' AND identity_document_url IS NOT NULL AND btrim(identity_document_url) <> '' ) OR ( entity_type = 'enterprise' AND company_name IS NOT NULL AND btrim(company_name) <> '' AND business_license_url IS NOT NULL AND btrim(business_license_url) <> '' ) ), -- A pending row is unreviewed; terminal rows record who and when, and a -- rejection always carries its reason. CONSTRAINT merchant_applications_review_consistent CHECK ( (status = 'pending' AND reviewed_by IS NULL AND reviewed_at IS NULL AND rejection_reason IS NULL AND created_shop_id IS NULL) OR (status = 'approved' AND reviewed_by IS NOT NULL AND reviewed_at IS NOT NULL AND rejection_reason IS NULL AND created_shop_id IS NOT NULL) OR (status = 'rejected' AND reviewed_by IS NOT NULL AND reviewed_at IS NOT NULL AND rejection_reason IS NOT NULL AND btrim(rejection_reason) <> '' AND created_shop_id IS NULL) ), CONSTRAINT merchant_applications_categories CHECK (cardinality(category_ids) >= 1) ); -- At most one live application per user; rejected rows are free to retry. CREATE UNIQUE INDEX merchant_applications_active_idx ON merchant_applications (user_id) WHERE status IN ('pending', 'approved'); CREATE INDEX merchant_applications_user_idx ON merchant_applications (user_id, created_at DESC); CREATE INDEX merchant_applications_status_idx ON merchant_applications (status, created_at DESC);