ALTER TABLE carts ADD CONSTRAINT cart_version_nonnegative CHECK (version >= 0); ALTER TABLE cart_lines ADD CONSTRAINT cart_quantity_bounds CHECK (quantity BETWEEN 1 AND 100); ALTER TABLE coupons ADD CONSTRAINT coupon_rule_valid CHECK ( (kind = 'FIXED' AND amount IS NOT NULL AND amount > 0 AND percent_bps IS NULL) OR (kind = 'PERCENT' AND amount IS NULL AND percent_bps BETWEEN 1 AND 10000 AND percent_bps IS NOT NULL) ); ALTER TABLE coupons ADD CONSTRAINT coupon_limits_valid CHECK ( minimum_subtotal >= 0 AND max_uses > 0 AND per_user_limit > 0 AND per_user_limit <= max_uses AND ends_at > starts_at AND code ~ '^[A-Z0-9][A-Z0-9_-]{0,39}$' AND currency IN ('INR', 'USD', 'EUR', 'GBP') ); ALTER TABLE orders ADD CONSTRAINT order_totals_valid CHECK ( subtotal > 0 AND discount >= 0 AND discount <= subtotal AND merchandise_total = subtotal - discount AND expires_at > created_at ); ALTER TABLE order_lines ADD CONSTRAINT order_line_totals_valid CHECK ( quantity BETWEEN 1 AND 100 AND unit_price > 0 AND line_total = unit_price * quantity ); CREATE FUNCTION protect_order_snapshot() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF TG_OP = 'DELETE' THEN RAISE EXCEPTION 'Orders cannot be deleted'; END IF; IF (to_jsonb(NEW) - 'status') IS DISTINCT FROM (to_jsonb(OLD) - 'status') THEN RAISE EXCEPTION 'Order snapshots are immutable'; END IF; IF OLD.status = 'CANCELLED' AND NEW.status <> 'CANCELLED' THEN RAISE EXCEPTION 'Cancelled orders cannot be reopened'; END IF; RETURN NEW; END; $$; CREATE TRIGGER orders_snapshot_immutable BEFORE UPDATE OR DELETE ON orders FOR EACH ROW EXECUTE FUNCTION protect_order_snapshot(); CREATE FUNCTION protect_order_line() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'Order lines are immutable'; END; $$; CREATE TRIGGER order_lines_immutable BEFORE UPDATE OR DELETE ON order_lines FOR EACH ROW EXECUTE FUNCTION protect_order_line(); CREATE FUNCTION protect_coupon_rules() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF (to_jsonb(NEW) - 'active') IS DISTINCT FROM (to_jsonb(OLD) - 'active') THEN RAISE EXCEPTION 'Create a new coupon to change discount rules'; END IF; RETURN NEW; END; $$; CREATE TRIGGER coupon_rules_immutable BEFORE UPDATE ON coupons FOR EACH ROW EXECUTE FUNCTION protect_coupon_rules(); CREATE FUNCTION protect_order_reservation() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF OLD.order_id IS NOT NULL THEN IF TG_OP = 'DELETE' THEN RAISE EXCEPTION 'Order reservations cannot be deleted'; END IF; IF (to_jsonb(NEW) - 'status') IS DISTINCT FROM (to_jsonb(OLD) - 'status') THEN RAISE EXCEPTION 'Order reservation allocation is immutable'; END IF; END IF; IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $$; CREATE TRIGGER order_reservation_immutable BEFORE UPDATE OR DELETE ON stock_reservations FOR EACH ROW EXECUTE FUNCTION protect_order_reservation(); UPDATE roles SET permissions = ARRAY( SELECT DISTINCT permission FROM unnest(permissions || ARRAY[ 'coupons.manage', 'orders.read', 'orders.manage' ]::text[]) AS permission ORDER BY permission ) WHERE is_system = true;