131 lines
4.1 KiB
PL/PgSQL
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';
|