Files
sembara/migrations/20260825100000_create_complete_order_rpc.sql

79 lines
2.5 KiB
PL/PgSQL

-- complete_order: transisi delivering -> completed secara atomik & idempotent.
-- Idempotensi: partial unique index stock_movements_order_out_unique_idx
-- ON (reference_type, reference_id) WHERE type='order_out' AND reference_type='order'
-- membuat insert mutasi ganda otomatis dilewati (ON CONFLICT DO NOTHING).
CREATE OR REPLACE FUNCTION public.complete_order(p_order_id uuid, p_actor uuid DEFAULT NULL)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $fn$
DECLARE
v_order public.orders%ROWTYPE;
v_item record;
v_new_balance integer;
BEGIN
SELECT * INTO v_order FROM public.orders WHERE id = p_order_id FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION 'ORDER_NOT_FOUND';
END IF;
-- Sudah selesai: anggap sukses (idempotent), tanpa efek stok.
IF v_order.order_status = 'completed' THEN
RETURN;
END IF;
IF v_order.deleted_at IS NOT NULL THEN
RAISE EXCEPTION 'ORDER_DELETED';
END IF;
IF v_order.order_status <> 'delivering' THEN
RAISE EXCEPTION 'INVALID_TRANSITION:%_TO_COMPLETED', v_order.order_status;
END IF;
FOR v_item IN
SELECT oi.product_id, oi.quantity
FROM public.order_items oi
WHERE oi.order_id = p_order_id
ORDER BY oi.product_id
FOR UPDATE OF oi
LOOP
SELECT s.quantity
INTO v_new_balance
FROM public.stocks s
WHERE s.product_id = v_item.product_id
FOR UPDATE;
v_new_balance := COALESCE(v_new_balance, 0) - v_item.quantity;
IF v_new_balance < 0 THEN
RAISE EXCEPTION 'INSUFFICIENT_STOCK:%', v_item.product_id;
END IF;
INSERT INTO public.stock_movements
(product_id, type, quantity, reference_type, reference_id, notes, created_by)
VALUES
(v_item.product_id, 'order_out', -v_item.quantity, 'order', p_order_id, NULL, p_actor)
ON CONFLICT DO NOTHING;
UPDATE public.stocks s
SET quantity = v_new_balance,
updated_at = now()
WHERE s.product_id = v_item.product_id;
END LOOP;
UPDATE public.orders
SET order_status = 'completed',
completed_at = now(),
updated_at = now()
WHERE id = p_order_id;
INSERT INTO public.order_status_histories
(order_id, old_status, new_status, changed_by)
VALUES
(p_order_id, v_order.order_status, 'completed', COALESCE(p_actor, v_order.created_by));
END;
$fn$;