CREATE TABLE ib_budgets ( id bigserial PRIMARY KEY, lot_id bigint NOT NULL REFERENCES ic_lots(id), version integer NOT NULL CHECK(version>0), previous_id bigint UNIQUE REFERENCES ib_budgets(id), dimensions jsonb NOT NULL, amounts jsonb NOT NULL, evidence text NOT NULL CHECK(length(evidence)>=3), actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL, UNIQUE(lot_id,version) ); CREATE TABLE ib_decisions ( id bigserial PRIMARY KEY, budget_id bigint UNIQUE NOT NULL REFERENCES ib_budgets(id), version integer NOT NULL, decision text NOT NULL CHECK(decision IN ('approved','refused')), reason text NOT NULL CHECK(length(reason)>=3), actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL ); CREATE TABLE ib_comparisons ( id bigserial PRIMARY KEY, lot_id bigint NOT NULL REFERENCES ic_lots(id), version integer NOT NULL CHECK(version>0), previous_id bigint UNIQUE REFERENCES ib_comparisons(id), budget_id bigint NOT NULL REFERENCES ib_budgets(id), result_id bigint NOT NULL REFERENCES ic_results(id), snapshot jsonb NOT NULL, actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL, UNIQUE(lot_id,version) ); CREATE TABLE ib_commands ( id bigserial PRIMARY KEY, lot_id bigint NOT NULL REFERENCES ic_lots(id),actor_id bigint NOT NULL REFERENCES users(id), action text NOT NULL CHECK(action IN ('propose','decide','compare')),command_key text NOT NULL,request_hash text NOT NULL, result jsonb NOT NULL,created_at timestamptz NOT NULL,UNIQUE(lot_id,actor_id,action,command_key) ); CREATE TABLE ib_events(id bigserial PRIMARY KEY,command_id bigint UNIQUE NOT NULL REFERENCES ib_commands(id),payload jsonb NOT NULL,created_at timestamptz NOT NULL); CREATE TABLE ib_outbox(event_id bigint PRIMARY KEY REFERENCES ib_events(id),state text NOT NULL CHECK(state='captured_no_stock_or_accounting')); CREATE OR REPLACE FUNCTION sgf_ib_integrity() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE lid bigint; prior bigint; n integer; b ib_budgets; r ic_results; ref jsonb; BEGIN PERFORM sgf_ic_environment(); IF TG_TABLE_NAME='ib_decisions' THEN SELECT * INTO STRICT b FROM ib_budgets WHERE id=NEW.budget_id; lid=b.lot_id; ELSE lid=NEW.lot_id; END IF; PERFORM 1 FROM companies WHERE id=(SELECT company_id FROM ic_lots WHERE id=lid) FOR UPDATE; IF TG_TABLE_NAME='ib_decisions' THEN IF b.actor_id=NEW.actor_id OR b.version<>NEW.version OR EXISTS(SELECT 1 FROM ib_budgets WHERE previous_id=b.id) THEN RAISE EXCEPTION 'SGF_IB_DECISION_VERSION_IDENTITY'; END IF; ELSIF TG_TABLE_NAME='ib_commands' THEN RETURN NEW; ELSE IF TG_TABLE_NAME='ib_budgets' THEN SELECT coalesce(max(version),0) INTO n FROM ib_budgets WHERE lot_id=lid; IF n>0 THEN SELECT id INTO STRICT prior FROM ib_budgets WHERE lot_id=lid AND version=n; END IF; ELSE SELECT coalesce(max(version),0) INTO n FROM ib_comparisons WHERE lot_id=lid; IF n>0 THEN SELECT id INTO STRICT prior FROM ib_comparisons WHERE lot_id=lid AND version=n; END IF; SELECT * INTO STRICT b FROM ib_budgets WHERE id=NEW.budget_id AND lot_id=lid; SELECT * INTO STRICT r FROM ic_results WHERE id=NEW.result_id AND lot_id=lid; IF NOT EXISTS(SELECT 1 FROM ib_decisions WHERE budget_id=b.id AND decision='approved') OR EXISTS(SELECT 1 FROM ib_budgets WHERE previous_id=b.id) OR NOT EXISTS(SELECT 1 FROM ic_decisions WHERE result_id=r.id AND decision='approved') OR EXISTS(SELECT 1 FROM ic_results WHERE previous_id=r.id) THEN RAISE EXCEPTION 'SGF_IB_APPROVED_CURRENT_REFERENCES'; END IF; FOR ref IN SELECT value FROM jsonb_each(r.snapshot->'refs') LOOP IF ref<>'null'::jsonb THEN PERFORM sgf_ic_input_current((ref->>'id')::bigint,lid,(ref->>'version')::integer); END IF; END LOOP; END IF; IF NEW.version<>n+1 OR (n=0 AND NEW.previous_id IS NOT NULL) OR (n>0 AND NEW.previous_id IS DISTINCT FROM prior) THEN RAISE EXCEPTION 'SGF_IB_VERSION'; END IF; END IF; RETURN NEW; END; $$; CREATE TRIGGER ib_budget_integrity BEFORE INSERT ON ib_budgets FOR EACH ROW EXECUTE FUNCTION sgf_ib_integrity(); CREATE TRIGGER ib_decision_integrity BEFORE INSERT ON ib_decisions FOR EACH ROW EXECUTE FUNCTION sgf_ib_integrity(); CREATE TRIGGER ib_comparison_integrity BEFORE INSERT ON ib_comparisons FOR EACH ROW EXECUTE FUNCTION sgf_ib_integrity(); CREATE TRIGGER ib_command_integrity BEFORE INSERT ON ib_commands FOR EACH ROW EXECUTE FUNCTION sgf_ib_integrity();