Files
route-commerce/supabase/migrations/042_product_import_rpc.sql
T

131 lines
4.1 KiB
PL/PgSQL

-- Migration 042: Product Import RPC + upsert_product
-- Idempotent: CREATE OR REPLACE FUNCTION
-- ── 1. upsert_product ─────────────────────────────────────────────────────────
-- Upserts a single product by name (within brand). Returns the product id.
-- Used by the CSV import tool to create or update products in bulk.
CREATE OR REPLACE FUNCTION public.upsert_product(
p_brand_id UUID,
p_name TEXT,
p_price NUMERIC,
p_type TEXT,
p_description TEXT DEFAULT '',
p_active BOOLEAN DEFAULT true,
p_image_url TEXT DEFAULT NULL
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
DECLARE
v_product_id UUID;
v_is_update BOOLEAN := false;
BEGIN
-- Check if product with same name exists in this brand
SELECT id INTO v_product_id
FROM products
WHERE brand_id = p_brand_id AND name = p_name
LIMIT 1;
IF v_product_id IS NOT NULL THEN
-- Update existing
UPDATE products SET
name = p_name,
description = p_description,
price = p_price,
type = p_type,
active = p_active,
image_url = COALESCE(p_image_url, image_url),
updated_at = now()
WHERE id = v_product_id
RETURNING id INTO v_product_id;
v_is_update := true;
ELSE
-- Insert new
INSERT INTO products (brand_id, name, description, price, type, active, image_url)
VALUES (p_brand_id, p_name, p_description, p_price, p_type, p_active, p_image_url)
RETURNING id INTO v_product_id;
END IF;
RETURN jsonb_build_object(
'id', v_product_id,
'is_update', v_is_update,
'name', p_name
);
END;
$$;
-- ── 2. bulk_upsert_products ──────────────────────────────────────────────────
-- Takes an array of product records and upserts them all within a brand.
-- Returns a summary: { created, updated, errors }.
CREATE OR REPLACE FUNCTION public.bulk_upsert_products(
p_brand_id UUID,
p_products JSONB -- array of { name, description, price, type, active, image_url }
)
RETURNS JSONB
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
AS $$
DECLARE
v_entry JSONB;
v_result JSONB := '{"created": 0, "updated": 0, "errors": []}'::JSONB;
v_name TEXT;
v_desc TEXT;
v_price NUMERIC;
v_type TEXT;
v_active BOOLEAN;
v_img TEXT;
v_was_new BOOLEAN;
BEGIN
FOR v_entry IN SELECT * FROM jsonb_array_elements(p_products)
LOOP
BEGIN
v_name := nullif(v_entry->>'name', '');
v_desc := coalesce(nullif(v_entry->>'description', ''), '');
v_price := nullif((v_entry->>'price')::TEXT, '')::NUMERIC;
v_type := nullif(v_entry->>'type', '');
v_active := coalesce((v_entry->>'active')::BOOLEAN, true);
v_img := nullif(v_entry->>'image_url', '');
IF v_name IS NULL OR v_name = '' THEN
RAISE EXCEPTION 'Product name is required';
END IF;
IF v_price IS NULL OR v_price < 0 THEN
RAISE EXCEPTION 'Invalid price for product: %', v_name;
END IF;
IF v_type NOT IN ('Pickup', 'Shipping', 'Pickup & Shipping') THEN
RAISE EXCEPTION 'Invalid type for product: %. Must be Pickup, Shipping, or Pickup & Shipping', v_name;
END IF;
v_was_new := NOT EXISTS (
SELECT 1 FROM products WHERE brand_id = p_brand_id AND name = v_name
);
PERFORM upsert_product(p_brand_id, v_name, v_desc, v_price, v_type, v_active, v_img);
IF v_was_new THEN
v_result := jsonb_set(v_result, '{created}',
to_jsonb((v_result->>'created')::INTEGER + 1));
ELSE
v_result := jsonb_set(v_result, '{updated}',
to_jsonb((v_result->>'updated')::INTEGER + 1));
END IF;
EXCEPTION WHEN OTHERS THEN
v_result := jsonb_set(
v_result, '{errors}',
v_result->'errors' || jsonb_build_array(
jsonb_build_object('product', v_name, 'error', SQLERRM)
)
);
END;
END LOOP;
RETURN v_result;
END;
$$;
NOTIFY pgrst, 'reload schema';