714 lines
19 KiB
PL/PgSQL
714 lines
19 KiB
PL/PgSQL
CREATE SCHEMA condon;
|
|
REVOKE ALL ON SCHEMA condon FROM PUBLIC;
|
|
COMMENT ON SCHEMA condon IS 'CONDO owner entitlement backend';
|
|
|
|
CREATE TABLE condon.schema_migrations (
|
|
version varchar(64) PRIMARY KEY,
|
|
checksum varchar(64) NOT NULL,
|
|
applied_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT schema_migrations_checksum_check
|
|
CHECK (checksum ~ '^[0-9a-f]{64}$')
|
|
);
|
|
|
|
CREATE TABLE condon.room_types (
|
|
code varchar(16) PRIMARY KEY,
|
|
entitlement_tier smallint,
|
|
requires_manual_multiplier boolean NOT NULL DEFAULT false,
|
|
created_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
updated_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT room_types_code_check
|
|
CHECK (code ~ '^[A-Z0-9]+$'),
|
|
CONSTRAINT room_types_rule_shape_check
|
|
CHECK (
|
|
(requires_manual_multiplier AND entitlement_tier IS NULL)
|
|
OR
|
|
(
|
|
NOT requires_manual_multiplier
|
|
AND entitlement_tier BETWEEN 1 AND 3
|
|
)
|
|
)
|
|
);
|
|
|
|
CREATE TABLE condon.owner_accounts (
|
|
id uuid PRIMARY KEY,
|
|
account_no integer,
|
|
transfer_date date,
|
|
owner_name text NOT NULL,
|
|
room_no varchar(32) NOT NULL,
|
|
purchased_room_type_code varchar(16) NOT NULL,
|
|
unit_no varchar(32) NOT NULL,
|
|
member_no varchar(32) NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
updated_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT owner_accounts_account_no_key UNIQUE (account_no),
|
|
CONSTRAINT owner_accounts_room_no_key UNIQUE (room_no),
|
|
CONSTRAINT owner_accounts_account_no_check
|
|
CHECK (account_no IS NULL OR account_no > 0),
|
|
CONSTRAINT owner_accounts_owner_name_check
|
|
CHECK (pg_catalog.btrim(owner_name) <> ''),
|
|
CONSTRAINT owner_accounts_room_no_check
|
|
CHECK (pg_catalog.btrim(room_no) <> ''),
|
|
CONSTRAINT owner_accounts_unit_no_check
|
|
CHECK (pg_catalog.btrim(unit_no) <> ''),
|
|
CONSTRAINT owner_accounts_member_no_check
|
|
CHECK (pg_catalog.btrim(member_no) <> ''),
|
|
CONSTRAINT owner_accounts_purchased_room_type_fk
|
|
FOREIGN KEY (purchased_room_type_code)
|
|
REFERENCES condon.room_types(code)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT
|
|
);
|
|
|
|
CREATE TABLE condon.entitlement_periods (
|
|
id uuid PRIMARY KEY,
|
|
owner_account_id uuid NOT NULL,
|
|
period_year smallint NOT NULL,
|
|
period_start date NOT NULL,
|
|
period_end date NOT NULL,
|
|
annual_grant integer NOT NULL DEFAULT 15,
|
|
carry_forward integer NOT NULL DEFAULT 0,
|
|
current_balance integer NOT NULL,
|
|
row_version integer NOT NULL DEFAULT 1,
|
|
created_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
updated_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT entitlement_periods_owner_year_key
|
|
UNIQUE (owner_account_id, period_year),
|
|
CONSTRAINT entitlement_periods_id_owner_key
|
|
UNIQUE (id, owner_account_id),
|
|
CONSTRAINT entitlement_periods_owner_fk
|
|
FOREIGN KEY (owner_account_id)
|
|
REFERENCES condon.owner_accounts(id)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT entitlement_periods_year_check
|
|
CHECK (period_year BETWEEN 2000 AND 9999),
|
|
CONSTRAINT entitlement_periods_dates_check
|
|
CHECK (
|
|
period_start = pg_catalog.make_date(period_year::integer, 1, 1)
|
|
AND period_end = pg_catalog.make_date(period_year::integer, 12, 31)
|
|
),
|
|
CONSTRAINT entitlement_periods_annual_grant_check
|
|
CHECK (annual_grant >= 0),
|
|
CONSTRAINT entitlement_periods_carry_forward_check
|
|
CHECK (carry_forward >= 0),
|
|
CONSTRAINT entitlement_periods_current_balance_check
|
|
CHECK (current_balance >= 0),
|
|
CONSTRAINT entitlement_periods_row_version_check
|
|
CHECK (row_version >= 1)
|
|
);
|
|
|
|
CREATE TABLE condon.usage_records (
|
|
id uuid PRIMARY KEY,
|
|
owner_account_id uuid NOT NULL,
|
|
entitlement_period_id uuid NOT NULL,
|
|
confirmation_no varchar(64) NOT NULL,
|
|
check_in date NOT NULL,
|
|
check_out date NOT NULL,
|
|
night_count integer NOT NULL,
|
|
used_room_type_code varchar(16) NOT NULL,
|
|
applied_multiplier smallint NOT NULL,
|
|
rule_version varchar(32) NOT NULL DEFAULT 'v1',
|
|
use_nights integer NOT NULL,
|
|
balance_before integer NOT NULL,
|
|
balance_after integer NOT NULL,
|
|
remark text NOT NULL DEFAULT '',
|
|
idempotency_key uuid NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT usage_records_confirmation_no_key UNIQUE (confirmation_no),
|
|
CONSTRAINT usage_records_idempotency_key_key UNIQUE (idempotency_key),
|
|
CONSTRAINT usage_records_id_period_key UNIQUE (id, entitlement_period_id),
|
|
CONSTRAINT usage_records_account_fk
|
|
FOREIGN KEY (owner_account_id)
|
|
REFERENCES condon.owner_accounts(id)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT usage_records_period_account_fk
|
|
FOREIGN KEY (entitlement_period_id, owner_account_id)
|
|
REFERENCES condon.entitlement_periods(id, owner_account_id)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT usage_records_used_room_type_fk
|
|
FOREIGN KEY (used_room_type_code)
|
|
REFERENCES condon.room_types(code)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT usage_records_confirmation_no_check
|
|
CHECK (confirmation_no ~ '^[0-9]+$'),
|
|
CONSTRAINT usage_records_dates_check
|
|
CHECK (check_out > check_in),
|
|
CONSTRAINT usage_records_same_year_check
|
|
CHECK (
|
|
EXTRACT(year FROM check_in)
|
|
= EXTRACT(year FROM check_out)
|
|
),
|
|
CONSTRAINT usage_records_night_count_check
|
|
CHECK (night_count = check_out - check_in AND night_count > 0),
|
|
CONSTRAINT usage_records_multiplier_check
|
|
CHECK (applied_multiplier BETWEEN 1 AND 3),
|
|
CONSTRAINT usage_records_use_nights_check
|
|
CHECK (
|
|
use_nights = night_count * applied_multiplier
|
|
AND use_nights > 0
|
|
),
|
|
CONSTRAINT usage_records_balance_check
|
|
CHECK (
|
|
balance_before >= use_nights
|
|
AND balance_after = balance_before - use_nights
|
|
AND balance_after >= 0
|
|
)
|
|
);
|
|
|
|
CREATE TABLE condon.entitlement_ledger (
|
|
id uuid PRIMARY KEY,
|
|
entitlement_period_id uuid NOT NULL,
|
|
usage_record_id uuid,
|
|
entry_type varchar(32) NOT NULL,
|
|
delta_nights integer NOT NULL,
|
|
balance_before integer NOT NULL,
|
|
balance_after integer NOT NULL,
|
|
occurred_at timestamptz NOT NULL DEFAULT pg_catalog.clock_timestamp(),
|
|
CONSTRAINT entitlement_ledger_usage_record_key
|
|
UNIQUE (usage_record_id),
|
|
CONSTRAINT entitlement_ledger_period_fk
|
|
FOREIGN KEY (entitlement_period_id)
|
|
REFERENCES condon.entitlement_periods(id)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT entitlement_ledger_usage_period_fk
|
|
FOREIGN KEY (usage_record_id, entitlement_period_id)
|
|
REFERENCES condon.usage_records(id, entitlement_period_id)
|
|
ON UPDATE RESTRICT
|
|
ON DELETE RESTRICT,
|
|
CONSTRAINT entitlement_ledger_entry_type_check
|
|
CHECK (entry_type IN ('annual_grant', 'carry_forward', 'usage')),
|
|
CONSTRAINT entitlement_ledger_balance_check
|
|
CHECK (
|
|
balance_after = balance_before + delta_nights
|
|
AND balance_after >= 0
|
|
),
|
|
CONSTRAINT entitlement_ledger_entry_shape_check
|
|
CHECK (
|
|
(
|
|
entry_type = 'usage'
|
|
AND usage_record_id IS NOT NULL
|
|
AND delta_nights < 0
|
|
)
|
|
OR
|
|
(
|
|
entry_type IN ('annual_grant', 'carry_forward')
|
|
AND usage_record_id IS NULL
|
|
AND delta_nights > 0
|
|
)
|
|
)
|
|
);
|
|
|
|
CREATE INDEX owner_accounts_member_no_idx
|
|
ON condon.owner_accounts (member_no);
|
|
CREATE INDEX owner_accounts_purchased_room_type_idx
|
|
ON condon.owner_accounts (purchased_room_type_code);
|
|
CREATE INDEX usage_records_owner_created_idx
|
|
ON condon.usage_records (owner_account_id, created_at DESC, id DESC);
|
|
CREATE INDEX usage_records_period_idx
|
|
ON condon.usage_records (entitlement_period_id);
|
|
CREATE INDEX usage_records_used_type_check_in_idx
|
|
ON condon.usage_records (used_room_type_code, check_in);
|
|
CREATE INDEX entitlement_ledger_period_occurred_idx
|
|
ON condon.entitlement_ledger (entitlement_period_id, occurred_at, id);
|
|
|
|
INSERT INTO condon.room_types (
|
|
code,
|
|
entitlement_tier,
|
|
requires_manual_multiplier
|
|
)
|
|
VALUES
|
|
('RM1', 1, false),
|
|
('RM2', 1, false),
|
|
('RM3', 1, false),
|
|
('RM4', 1, false),
|
|
('UG1', 1, false),
|
|
('UG2', 1, false),
|
|
('SU1', 2, false),
|
|
('SU2', 2, false),
|
|
('SU6', 2, false),
|
|
('SU3', 3, false),
|
|
('AC2', NULL, true);
|
|
|
|
CREATE FUNCTION condon.calculate_multiplier(
|
|
p_purchased_room_type_code varchar,
|
|
p_used_room_type_code varchar,
|
|
p_manual_multiplier smallint
|
|
)
|
|
RETURNS smallint
|
|
LANGUAGE plpgsql
|
|
STABLE
|
|
SECURITY INVOKER
|
|
SET search_path = pg_catalog
|
|
AS $function$
|
|
DECLARE
|
|
v_purchased_tier smallint;
|
|
v_used_tier smallint;
|
|
v_purchased_manual boolean;
|
|
v_used_manual boolean;
|
|
BEGIN
|
|
SELECT
|
|
entitlement_tier,
|
|
requires_manual_multiplier
|
|
INTO
|
|
v_purchased_tier,
|
|
v_purchased_manual
|
|
FROM condon.room_types
|
|
WHERE code = p_purchased_room_type_code;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_PURCHASED_ROOM_TYPE_NOT_FOUND';
|
|
END IF;
|
|
|
|
SELECT
|
|
entitlement_tier,
|
|
requires_manual_multiplier
|
|
INTO
|
|
v_used_tier,
|
|
v_used_manual
|
|
FROM condon.room_types
|
|
WHERE code = p_used_room_type_code;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_USED_ROOM_TYPE_NOT_FOUND';
|
|
END IF;
|
|
|
|
IF v_purchased_manual OR v_used_manual THEN
|
|
IF p_manual_multiplier IS NULL OR p_manual_multiplier NOT BETWEEN 1 AND 3 THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_MANUAL_MULTIPLIER_REQUIRED';
|
|
END IF;
|
|
RETURN p_manual_multiplier;
|
|
END IF;
|
|
|
|
IF p_manual_multiplier IS NOT NULL THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_MANUAL_MULTIPLIER_NOT_ALLOWED';
|
|
END IF;
|
|
|
|
RETURN GREATEST(
|
|
1,
|
|
v_used_tier - v_purchased_tier + 1
|
|
)::smallint;
|
|
END;
|
|
$function$;
|
|
|
|
CREATE FUNCTION condon.open_entitlement_period(
|
|
p_period_id uuid,
|
|
p_owner_account_id uuid,
|
|
p_period_year smallint,
|
|
p_annual_grant_ledger_id uuid,
|
|
p_carry_forward_ledger_id uuid
|
|
)
|
|
RETURNS condon.entitlement_periods
|
|
LANGUAGE plpgsql
|
|
VOLATILE
|
|
SECURITY INVOKER
|
|
SET search_path = pg_catalog
|
|
AS $function$
|
|
DECLARE
|
|
v_carry_forward integer := 0;
|
|
v_period condon.entitlement_periods%ROWTYPE;
|
|
v_now timestamptz := pg_catalog.clock_timestamp();
|
|
BEGIN
|
|
IF p_period_id IS NULL
|
|
OR p_owner_account_id IS NULL
|
|
OR p_annual_grant_ledger_id IS NULL
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_REQUIRED_ID_MISSING';
|
|
END IF;
|
|
|
|
IF p_period_year NOT BETWEEN 2000 AND 9999 THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_INVALID_PERIOD_YEAR';
|
|
END IF;
|
|
|
|
PERFORM 1
|
|
FROM condon.owner_accounts
|
|
WHERE id = p_owner_account_id
|
|
FOR UPDATE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = 'P0002',
|
|
MESSAGE = 'CONDON_OWNER_ACCOUNT_NOT_FOUND';
|
|
END IF;
|
|
|
|
IF EXISTS (
|
|
SELECT 1
|
|
FROM condon.entitlement_periods
|
|
WHERE owner_account_id = p_owner_account_id
|
|
AND period_year = p_period_year
|
|
) THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '23505',
|
|
MESSAGE = 'CONDON_ENTITLEMENT_PERIOD_EXISTS';
|
|
END IF;
|
|
|
|
SELECT current_balance
|
|
INTO v_carry_forward
|
|
FROM condon.entitlement_periods
|
|
WHERE owner_account_id = p_owner_account_id
|
|
AND period_year = p_period_year - 1
|
|
FOR UPDATE;
|
|
|
|
v_carry_forward := COALESCE(v_carry_forward, 0);
|
|
|
|
IF v_carry_forward > 0 AND p_carry_forward_ledger_id IS NULL THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_CARRY_FORWARD_LEDGER_ID_REQUIRED';
|
|
END IF;
|
|
|
|
IF v_carry_forward = 0 AND p_carry_forward_ledger_id IS NOT NULL THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_CARRY_FORWARD_LEDGER_ID_NOT_ALLOWED';
|
|
END IF;
|
|
|
|
INSERT INTO condon.entitlement_periods (
|
|
id,
|
|
owner_account_id,
|
|
period_year,
|
|
period_start,
|
|
period_end,
|
|
annual_grant,
|
|
carry_forward,
|
|
current_balance,
|
|
row_version,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
VALUES (
|
|
p_period_id,
|
|
p_owner_account_id,
|
|
p_period_year,
|
|
pg_catalog.make_date(p_period_year::integer, 1, 1),
|
|
pg_catalog.make_date(p_period_year::integer, 12, 31),
|
|
15,
|
|
v_carry_forward,
|
|
15 + v_carry_forward,
|
|
1,
|
|
v_now,
|
|
v_now
|
|
)
|
|
RETURNING * INTO v_period;
|
|
|
|
INSERT INTO condon.entitlement_ledger (
|
|
id,
|
|
entitlement_period_id,
|
|
usage_record_id,
|
|
entry_type,
|
|
delta_nights,
|
|
balance_before,
|
|
balance_after,
|
|
occurred_at
|
|
)
|
|
VALUES (
|
|
p_annual_grant_ledger_id,
|
|
p_period_id,
|
|
NULL,
|
|
'annual_grant',
|
|
15,
|
|
0,
|
|
15,
|
|
v_now
|
|
);
|
|
|
|
IF v_carry_forward > 0 THEN
|
|
INSERT INTO condon.entitlement_ledger (
|
|
id,
|
|
entitlement_period_id,
|
|
usage_record_id,
|
|
entry_type,
|
|
delta_nights,
|
|
balance_before,
|
|
balance_after,
|
|
occurred_at
|
|
)
|
|
VALUES (
|
|
p_carry_forward_ledger_id,
|
|
p_period_id,
|
|
NULL,
|
|
'carry_forward',
|
|
v_carry_forward,
|
|
15,
|
|
15 + v_carry_forward,
|
|
v_now
|
|
);
|
|
END IF;
|
|
|
|
RETURN v_period;
|
|
END;
|
|
$function$;
|
|
|
|
CREATE FUNCTION condon.create_usage_record(
|
|
p_usage_record_id uuid,
|
|
p_ledger_id uuid,
|
|
p_owner_account_id uuid,
|
|
p_confirmation_no varchar,
|
|
p_check_in date,
|
|
p_check_out date,
|
|
p_used_room_type_code varchar,
|
|
p_manual_multiplier smallint,
|
|
p_remark text,
|
|
p_idempotency_key uuid
|
|
)
|
|
RETURNS condon.usage_records
|
|
LANGUAGE plpgsql
|
|
VOLATILE
|
|
SECURITY INVOKER
|
|
SET search_path = pg_catalog
|
|
AS $function$
|
|
DECLARE
|
|
v_purchased_room_type_code varchar(16);
|
|
v_multiplier smallint;
|
|
v_night_count integer;
|
|
v_use_nights integer;
|
|
v_balance_before integer;
|
|
v_balance_after integer;
|
|
v_period condon.entitlement_periods%ROWTYPE;
|
|
v_existing condon.usage_records%ROWTYPE;
|
|
v_usage condon.usage_records%ROWTYPE;
|
|
v_now timestamptz := pg_catalog.clock_timestamp();
|
|
v_updated_count integer;
|
|
BEGIN
|
|
IF p_usage_record_id IS NULL
|
|
OR p_ledger_id IS NULL
|
|
OR p_owner_account_id IS NULL
|
|
OR p_idempotency_key IS NULL
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_REQUIRED_ID_MISSING';
|
|
END IF;
|
|
|
|
IF p_confirmation_no IS NULL
|
|
OR p_confirmation_no !~ '^[0-9]+$'
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_INVALID_CONFIRMATION_NO';
|
|
END IF;
|
|
|
|
IF p_check_in IS NULL
|
|
OR p_check_out IS NULL
|
|
OR p_check_out <= p_check_in
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_INVALID_STAY_DATES';
|
|
END IF;
|
|
|
|
IF EXTRACT(year FROM p_check_in)
|
|
<> EXTRACT(year FROM p_check_out)
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_CROSS_YEAR_STAY_NOT_SUPPORTED';
|
|
END IF;
|
|
|
|
SELECT purchased_room_type_code
|
|
INTO v_purchased_room_type_code
|
|
FROM condon.owner_accounts
|
|
WHERE id = p_owner_account_id
|
|
FOR SHARE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = 'P0002',
|
|
MESSAGE = 'CONDON_OWNER_ACCOUNT_NOT_FOUND';
|
|
END IF;
|
|
|
|
v_multiplier := condon.calculate_multiplier(
|
|
v_purchased_room_type_code,
|
|
p_used_room_type_code,
|
|
p_manual_multiplier
|
|
);
|
|
v_night_count := p_check_out - p_check_in;
|
|
v_use_nights := v_night_count * v_multiplier;
|
|
|
|
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
pg_catalog.hashtextextended(p_idempotency_key::text, 0)
|
|
);
|
|
|
|
SELECT *
|
|
INTO v_existing
|
|
FROM condon.usage_records
|
|
WHERE idempotency_key = p_idempotency_key;
|
|
|
|
IF FOUND THEN
|
|
IF v_existing.owner_account_id IS DISTINCT FROM p_owner_account_id
|
|
OR v_existing.confirmation_no IS DISTINCT FROM p_confirmation_no
|
|
OR v_existing.check_in IS DISTINCT FROM p_check_in
|
|
OR v_existing.check_out IS DISTINCT FROM p_check_out
|
|
OR v_existing.used_room_type_code IS DISTINCT FROM p_used_room_type_code
|
|
OR v_existing.applied_multiplier IS DISTINCT FROM v_multiplier
|
|
OR v_existing.remark IS DISTINCT FROM COALESCE(p_remark, '')
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '23505',
|
|
MESSAGE = 'CONDON_IDEMPOTENCY_KEY_REUSED';
|
|
END IF;
|
|
RETURN v_existing;
|
|
END IF;
|
|
|
|
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
pg_catalog.hashtextextended('confirmation:' || p_confirmation_no, 0)
|
|
);
|
|
|
|
IF EXISTS (
|
|
SELECT 1
|
|
FROM condon.usage_records
|
|
WHERE confirmation_no = p_confirmation_no
|
|
) THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '23505',
|
|
MESSAGE = 'CONDON_CONFIRMATION_NO_EXISTS';
|
|
END IF;
|
|
|
|
SELECT *
|
|
INTO v_period
|
|
FROM condon.entitlement_periods
|
|
WHERE owner_account_id = p_owner_account_id
|
|
AND period_year = EXTRACT(year FROM p_check_in)::smallint
|
|
FOR UPDATE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = 'P0002',
|
|
MESSAGE = 'CONDON_ENTITLEMENT_PERIOD_NOT_FOUND';
|
|
END IF;
|
|
|
|
IF p_check_in < v_period.period_start
|
|
OR p_check_out > v_period.period_end
|
|
THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '22023',
|
|
MESSAGE = 'CONDON_STAY_OUTSIDE_ENTITLEMENT_PERIOD';
|
|
END IF;
|
|
|
|
v_balance_before := v_period.current_balance;
|
|
v_balance_after := v_balance_before - v_use_nights;
|
|
|
|
IF v_balance_after < 0 THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = 'P0001',
|
|
MESSAGE = 'CONDON_INSUFFICIENT_BALANCE';
|
|
END IF;
|
|
|
|
INSERT INTO condon.usage_records (
|
|
id,
|
|
owner_account_id,
|
|
entitlement_period_id,
|
|
confirmation_no,
|
|
check_in,
|
|
check_out,
|
|
night_count,
|
|
used_room_type_code,
|
|
applied_multiplier,
|
|
rule_version,
|
|
use_nights,
|
|
balance_before,
|
|
balance_after,
|
|
remark,
|
|
idempotency_key,
|
|
created_at
|
|
)
|
|
VALUES (
|
|
p_usage_record_id,
|
|
p_owner_account_id,
|
|
v_period.id,
|
|
p_confirmation_no,
|
|
p_check_in,
|
|
p_check_out,
|
|
v_night_count,
|
|
p_used_room_type_code,
|
|
v_multiplier,
|
|
'v1',
|
|
v_use_nights,
|
|
v_balance_before,
|
|
v_balance_after,
|
|
COALESCE(p_remark, ''),
|
|
p_idempotency_key,
|
|
v_now
|
|
)
|
|
RETURNING * INTO v_usage;
|
|
|
|
INSERT INTO condon.entitlement_ledger (
|
|
id,
|
|
entitlement_period_id,
|
|
usage_record_id,
|
|
entry_type,
|
|
delta_nights,
|
|
balance_before,
|
|
balance_after,
|
|
occurred_at
|
|
)
|
|
VALUES (
|
|
p_ledger_id,
|
|
v_period.id,
|
|
p_usage_record_id,
|
|
'usage',
|
|
-v_use_nights,
|
|
v_balance_before,
|
|
v_balance_after,
|
|
v_now
|
|
);
|
|
|
|
UPDATE condon.entitlement_periods
|
|
SET
|
|
current_balance = v_balance_after,
|
|
row_version = row_version + 1,
|
|
updated_at = v_now
|
|
WHERE id = v_period.id
|
|
AND row_version = v_period.row_version;
|
|
|
|
GET DIAGNOSTICS v_updated_count = ROW_COUNT;
|
|
IF v_updated_count <> 1 THEN
|
|
RAISE EXCEPTION USING
|
|
ERRCODE = '40001',
|
|
MESSAGE = 'CONDON_BALANCE_CONCURRENTLY_CHANGED';
|
|
END IF;
|
|
|
|
RETURN v_usage;
|
|
END;
|
|
$function$;
|
|
|
|
REVOKE ALL ON FUNCTION condon.calculate_multiplier(
|
|
varchar,
|
|
varchar,
|
|
smallint
|
|
) FROM PUBLIC;
|
|
REVOKE ALL ON FUNCTION condon.open_entitlement_period(
|
|
uuid,
|
|
uuid,
|
|
smallint,
|
|
uuid,
|
|
uuid
|
|
) FROM PUBLIC;
|
|
REVOKE ALL ON FUNCTION condon.create_usage_record(
|
|
uuid,
|
|
uuid,
|
|
uuid,
|
|
varchar,
|
|
date,
|
|
date,
|
|
varchar,
|
|
smallint,
|
|
text,
|
|
uuid
|
|
) FROM PUBLIC;
|