149 lines
4.9 KiB
PL/PgSQL
149 lines
4.9 KiB
PL/PgSQL
-- Migration 00003: Transactional Stock Adjustment RPC (adjust_product_stock with FEFO)
|
|
|
|
CREATE OR REPLACE FUNCTION public.adjust_product_stock(
|
|
p_household_id UUID,
|
|
p_product_id UUID,
|
|
p_quantity_change NUMERIC,
|
|
p_reason public.stock_movement_reason,
|
|
p_batch_id UUID DEFAULT NULL,
|
|
p_note TEXT DEFAULT NULL
|
|
)
|
|
RETURNS NUMERIC
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_user_id UUID;
|
|
v_total_before NUMERIC := 0.0;
|
|
v_total_after NUMERIC := 0.0;
|
|
v_target_batch RECORD;
|
|
v_remaining_to_deduct NUMERIC;
|
|
v_deduct_amount NUMERIC;
|
|
BEGIN
|
|
v_user_id := auth.uid();
|
|
|
|
-- 1. Check permissions
|
|
IF NOT public.has_household_role(p_household_id, ARRAY['MEMBER', 'ADMIN', 'OWNER']::public.household_role[]) THEN
|
|
RAISE EXCEPTION 'PERMISSION_DENIED: User is not authorized to modify stock in household %', p_household_id;
|
|
END IF;
|
|
|
|
-- 2. Lock product and calculate current total stock
|
|
PERFORM 1 FROM public.products WHERE id = p_product_id AND household_id = p_household_id FOR UPDATE;
|
|
|
|
SELECT COALESCE(SUM(quantity), 0.0) INTO v_total_before
|
|
FROM public.inventory_batches
|
|
WHERE household_id = p_household_id AND product_id = p_product_id;
|
|
|
|
-- 3. Check for negative stock
|
|
IF (v_total_before + p_quantity_change) < 0 THEN
|
|
RAISE EXCEPTION 'INSUFFICIENT_STOCK: Required quantity reduction exceeds current total stock (%)', v_total_before;
|
|
END IF;
|
|
|
|
-- 4. Apply changes
|
|
IF p_batch_id IS NOT NULL THEN
|
|
-- Target specific batch
|
|
SELECT * INTO v_target_batch
|
|
FROM public.inventory_batches
|
|
WHERE id = p_batch_id AND product_id = p_product_id AND household_id = p_household_id
|
|
FOR UPDATE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION 'BATCH_NOT_FOUND: Specified batch % does not exist', p_batch_id;
|
|
END IF;
|
|
|
|
IF (v_target_batch.quantity + p_quantity_change) < 0 THEN
|
|
RAISE EXCEPTION 'INSUFFICIENT_BATCH_STOCK: Batch quantity cannot be reduced below 0';
|
|
END IF;
|
|
|
|
UPDATE public.inventory_batches
|
|
SET quantity = quantity + p_quantity_change,
|
|
updated_at = NOW()
|
|
WHERE id = p_batch_id;
|
|
|
|
ELSE
|
|
-- No explicit batch provided
|
|
IF p_quantity_change > 0 THEN
|
|
-- Increase stock: Add to default batch (batch without expiration_date or newest) or create new batch
|
|
SELECT id INTO p_batch_id
|
|
FROM public.inventory_batches
|
|
WHERE household_id = p_household_id
|
|
AND product_id = p_product_id
|
|
AND expiration_date IS NULL
|
|
ORDER BY created_at DESC
|
|
LIMIT 1;
|
|
|
|
IF p_batch_id IS NOT NULL THEN
|
|
UPDATE public.inventory_batches
|
|
SET quantity = quantity + p_quantity_change,
|
|
updated_at = NOW()
|
|
WHERE id = p_batch_id;
|
|
ELSE
|
|
INSERT INTO public.inventory_batches (household_id, product_id, quantity)
|
|
VALUES (p_household_id, p_product_id, p_quantity_change)
|
|
RETURNING id INTO p_batch_id;
|
|
END IF;
|
|
|
|
ELSIF p_quantity_change < 0 THEN
|
|
-- FEFO Deduction (First Expire, First Out)
|
|
v_remaining_to_deduct := ABS(p_quantity_change);
|
|
|
|
FOR v_target_batch IN
|
|
SELECT id, quantity
|
|
FROM public.inventory_batches
|
|
WHERE household_id = p_household_id
|
|
AND product_id = p_product_id
|
|
AND quantity > 0
|
|
ORDER BY
|
|
expiration_date ASC NULLS LAST,
|
|
created_at ASC
|
|
FOR UPDATE
|
|
LOOP
|
|
IF v_remaining_to_deduct <= 0 THEN
|
|
EXIT;
|
|
END IF;
|
|
|
|
v_deduct_amount := LEAST(v_target_batch.quantity, v_remaining_to_deduct);
|
|
|
|
UPDATE public.inventory_batches
|
|
SET quantity = quantity - v_deduct_amount,
|
|
updated_at = NOW()
|
|
WHERE id = v_target_batch.id;
|
|
|
|
v_remaining_to_deduct := v_remaining_to_deduct - v_deduct_amount;
|
|
END LOOP;
|
|
END IF;
|
|
END IF;
|
|
|
|
-- 5. Calculate new total stock
|
|
SELECT COALESCE(SUM(quantity), 0.0) INTO v_total_after
|
|
FROM public.inventory_batches
|
|
WHERE household_id = p_household_id AND product_id = p_product_id;
|
|
|
|
-- 6. Insert stock_movements record
|
|
INSERT INTO public.stock_movements (
|
|
household_id,
|
|
product_id,
|
|
batch_id,
|
|
quantity_before,
|
|
quantity_change,
|
|
quantity_after,
|
|
reason,
|
|
note,
|
|
created_by
|
|
) VALUES (
|
|
p_household_id,
|
|
p_product_id,
|
|
p_batch_id,
|
|
v_total_before,
|
|
p_quantity_change,
|
|
v_total_after,
|
|
p_reason,
|
|
p_note,
|
|
v_user_id
|
|
);
|
|
|
|
RETURN v_total_after;
|
|
END;
|
|
$$;
|