64 lines
3.3 KiB
PL/PgSQL
64 lines
3.3 KiB
PL/PgSQL
ALTER TABLE pricing_policies ADD CONSTRAINT pricing_rule_bounds CHECK (
|
|
merchandise_tax_bps BETWEEN 0 AND 10000 AND shipping_tax_bps BETWEEN 0 AND 10000
|
|
AND shipping_fee >= 0 AND (free_shipping_minimum IS NULL OR free_shipping_minimum >= 0)
|
|
AND currency IN ('INR','USD','EUR','GBP') AND country_code ~ '^[A-Z]{2}$' AND region = upper(region)
|
|
);
|
|
CREATE UNIQUE INDEX pricing_one_active_scope ON pricing_policies (organization_id, currency, country_code, region)
|
|
WHERE active = true;
|
|
CREATE FUNCTION protect_pricing_policy() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
IF (to_jsonb(NEW) - 'active') IS DISTINCT FROM (to_jsonb(OLD) - 'active') THEN
|
|
RAISE EXCEPTION 'Pricing policies are immutable; create a new version';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
CREATE TRIGGER pricing_policy_immutable BEFORE UPDATE ON pricing_policies
|
|
FOR EACH ROW EXECUTE FUNCTION protect_pricing_policy();
|
|
|
|
CREATE FUNCTION reject_commerce_snapshot_mutation() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION 'Commerce snapshots and events are immutable';
|
|
END;
|
|
$$;
|
|
CREATE TRIGGER order_pricing_immutable BEFORE UPDATE OR DELETE ON order_pricing
|
|
FOR EACH ROW EXECUTE FUNCTION reject_commerce_snapshot_mutation();
|
|
CREATE TRIGGER commerce_events_immutable BEFORE UPDATE OR DELETE ON commerce_events
|
|
FOR EACH ROW EXECUTE FUNCTION reject_commerce_snapshot_mutation();
|
|
|
|
CREATE FUNCTION reconcile_order_pricing() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE
|
|
p pricing_policies%ROWTYPE;
|
|
merchandise NUMERIC;
|
|
expected_tax NUMERIC;
|
|
shipping NUMERIC;
|
|
shipping_tax NUMERIC;
|
|
payable NUMERIC;
|
|
BEGIN
|
|
SELECT * INTO STRICT p FROM pricing_policies WHERE id = NEW.policy_id AND organization_id = NEW.organization_id;
|
|
SELECT merchandise_total INTO STRICT merchandise FROM orders WHERE id = NEW.order_id AND organization_id = NEW.organization_id;
|
|
expected_tax := ROUND(merchandise * p.merchandise_tax_bps /
|
|
(10000 + CASE WHEN p.tax_mode = 'INCLUSIVE' THEN p.merchandise_tax_bps ELSE 0 END), 2);
|
|
shipping := CASE WHEN p.free_shipping_minimum IS NOT NULL AND merchandise >= p.free_shipping_minimum
|
|
THEN 0 ELSE p.shipping_fee END;
|
|
shipping_tax := ROUND(shipping * p.shipping_tax_bps / 10000, 2);
|
|
payable := merchandise + shipping + shipping_tax + CASE WHEN p.tax_mode = 'EXCLUSIVE' THEN expected_tax ELSE 0 END;
|
|
IF NEW.merchandise_tax <> expected_tax OR NEW.shipping_net <> shipping OR NEW.shipping_tax <> shipping_tax
|
|
OR NEW.tax_total <> expected_tax + shipping_tax OR NEW.payable_total <> payable THEN
|
|
RAISE EXCEPTION 'Final pricing does not match the selected policy';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
CREATE TRIGGER order_pricing_reconciled BEFORE INSERT ON order_pricing
|
|
FOR EACH ROW EXECUTE FUNCTION reconcile_order_pricing();
|
|
|
|
ALTER TABLE commerce_events ADD CONSTRAINT commerce_event_kind CHECK (kind IN ('order.created','order.cancelled'));
|
|
ALTER TABLE event_deliveries ADD CONSTRAINT delivery_attempt_bounds CHECK (attempts BETWEEN 0 AND 5);
|
|
ALTER TABLE event_deliveries ADD CONSTRAINT delivery_lease_pair CHECK ((lease_token IS NULL) = (lease_expires_at IS NULL));
|
|
UPDATE roles SET permissions = ARRAY(
|
|
SELECT DISTINCT permission FROM unnest(permissions || ARRAY[
|
|
'pricing.manage', 'operations.read', 'notifications.retry'
|
|
]::text[]) AS permission ORDER BY permission
|
|
) WHERE is_system = true;
|