79 lines
2.5 KiB
PL/PgSQL
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$;
|