Files
wyndham-Condon/backend/migrations/003_delete_usage_record.up.sql
2026-08-03 16:43:57 +08:00

168 lines
4.4 KiB
PL/PgSQL

-- Add an atomic usage-record deletion path. The operation restores the
-- entitlement period and reflows stored balance snapshots for later rows.
CREATE FUNCTION condon.delete_usage_record(
p_usage_record_id uuid
)
RETURNS void
LANGUAGE plpgsql
VOLATILE
SECURITY INVOKER
SET search_path = pg_catalog
AS $function$
DECLARE
v_usage condon.usage_records%ROWTYPE;
v_period condon.entitlement_periods%ROWTYPE;
v_initial_balance integer;
v_now timestamptz := pg_catalog.clock_timestamp();
BEGIN
IF p_usage_record_id IS NULL THEN
RAISE EXCEPTION USING
ERRCODE = '22023',
MESSAGE = 'CONDON_USAGE_RECORD_ID_REQUIRED';
END IF;
SELECT *
INTO v_usage
FROM condon.usage_records
WHERE id = p_usage_record_id
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION USING
ERRCODE = 'P0002',
MESSAGE = 'CONDON_USAGE_RECORD_NOT_FOUND';
END IF;
SELECT *
INTO v_period
FROM condon.entitlement_periods
WHERE id = v_usage.entitlement_period_id
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION USING
ERRCODE = 'P0002',
MESSAGE = 'CONDON_ENTITLEMENT_PERIOD_NOT_FOUND';
END IF;
DELETE FROM condon.entitlement_ledger
WHERE usage_record_id = v_usage.id;
DELETE FROM condon.usage_records
WHERE id = v_usage.id;
v_initial_balance := v_period.annual_grant + v_period.carry_forward;
WITH ordered AS (
SELECT
ur.id,
(
v_initial_balance
- COALESCE(
SUM(ur.use_nights) OVER (
ORDER BY
CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END,
ur.source_sheet NULLS LAST,
ur.source_sequence NULLS LAST,
ur.source_row NULLS LAST,
ur.created_at ASC,
ur.id ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
)
)::integer AS balance_before,
(
v_initial_balance
- SUM(ur.use_nights) OVER (
ORDER BY
CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END,
ur.source_sheet NULLS LAST,
ur.source_sequence NULLS LAST,
ur.source_row NULLS LAST,
ur.created_at ASC,
ur.id ASC
)
)::integer AS balance_after
FROM condon.usage_records AS ur
WHERE ur.entitlement_period_id = v_period.id
)
UPDATE condon.usage_records AS ur
SET
balance_before = ordered.balance_before,
balance_after = ordered.balance_after
FROM ordered
WHERE ur.id = ordered.id;
WITH ordered AS (
SELECT
ur.id,
(
v_initial_balance
- COALESCE(
SUM(ur.use_nights) OVER (
ORDER BY
CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END,
ur.source_sheet NULLS LAST,
ur.source_sequence NULLS LAST,
ur.source_row NULLS LAST,
ur.created_at ASC,
ur.id ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
),
0
)
)::integer AS balance_before,
(
v_initial_balance
- SUM(ur.use_nights) OVER (
ORDER BY
CASE WHEN ur.source_sheet IS NULL THEN 1 ELSE 0 END,
ur.source_sheet NULLS LAST,
ur.source_sequence NULLS LAST,
ur.source_row NULLS LAST,
ur.created_at ASC,
ur.id ASC
)
)::integer AS balance_after
FROM condon.usage_records AS ur
WHERE ur.entitlement_period_id = v_period.id
)
UPDATE condon.entitlement_ledger AS ledger
SET
balance_before = ordered.balance_before,
balance_after = ordered.balance_after
FROM ordered
WHERE ledger.usage_record_id = ordered.id
AND ledger.entry_type = 'usage';
UPDATE condon.entitlement_periods
SET
current_balance = (
v_initial_balance
- COALESCE(
(
SELECT SUM(ur.use_nights)
FROM condon.usage_records AS ur
WHERE ur.entitlement_period_id = v_period.id
),
0
)
)::integer,
row_version = row_version + 1,
updated_at = v_now
WHERE id = v_period.id;
DELETE FROM condon.bookings AS booking
WHERE booking.confirmation_no = v_usage.confirmation_no
AND NOT EXISTS (
SELECT 1
FROM condon.usage_records AS ur
WHERE ur.confirmation_no = booking.confirmation_no
);
END;
$function$;
REVOKE ALL ON FUNCTION condon.delete_usage_record(uuid) FROM PUBLIC;