This commit is contained in:
2026-10-29 18:43:29 +02:00
parent f77900e198
commit 78e89a51d2
75 changed files with 2881 additions and 14 deletions
+49
View File
@@ -0,0 +1,49 @@
CREATE TABLE wc_requests(id bigserial PRIMARY KEY,generation_id bigint NOT NULL REFERENCES wb_generations(id),version integer NOT NULL CHECK(version>0),previous_id bigint UNIQUE REFERENCES wc_requests(id),kind text NOT NULL CHECK(kind IN ('replace','cancel','withdraw')),snapshot jsonb NOT NULL,actor_id bigint NOT NULL REFERENCES users(id),created_at timestamptz NOT NULL,UNIQUE(generation_id,version));
CREATE TABLE wc_reviews(id bigserial PRIMARY KEY,request_id bigint UNIQUE NOT NULL REFERENCES wc_requests(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 wc_applications(id bigserial PRIMARY KEY,generation_id bigint NOT NULL REFERENCES wb_generations(id),request_id bigint UNIQUE NOT NULL REFERENCES wc_requests(id),sequence integer NOT NULL CHECK(sequence>0),kind text NOT NULL CHECK(kind IN ('replace','cancel')),old_service_id bigint UNIQUE NOT NULL REFERENCES a_service_drafts(id),old_billing_id bigint UNIQUE NOT NULL REFERENCES a_billing_drafts(id),new_service_id bigint UNIQUE REFERENCES a_service_drafts(id),new_billing_id bigint UNIQUE REFERENCES a_billing_drafts(id),actor_id bigint NOT NULL REFERENCES users(id),created_at timestamptz NOT NULL,UNIQUE(generation_id,sequence),CHECK((kind='replace' AND new_service_id IS NOT NULL AND new_billing_id IS NOT NULL) OR (kind='cancel' AND new_service_id IS NULL AND new_billing_id IS NULL)));
CREATE TABLE wc_commands(id bigserial PRIMARY KEY,generation_id bigint NOT NULL REFERENCES wb_generations(id),actor_id bigint NOT NULL REFERENCES users(id),action text NOT NULL CHECK(action IN ('request','review','apply')),command_key text NOT NULL,request_hash text NOT NULL,result jsonb NOT NULL,created_at timestamptz NOT NULL,UNIQUE(generation_id,actor_id,action,command_key));
CREATE TABLE wc_events(id bigserial PRIMARY KEY,command_id bigint UNIQUE NOT NULL REFERENCES wc_commands(id),payload jsonb NOT NULL,created_at timestamptz NOT NULL);
CREATE TABLE wc_outbox(event_id bigint PRIMARY KEY REFERENCES wc_events(id),state text NOT NULL CHECK(state='captured_no_stock_or_accounting'));
CREATE OR REPLACE FUNCTION sgf_wc_linked_service(sid bigint) RETURNS boolean LANGUAGE sql AS $$ SELECT EXISTS(SELECT 1 FROM wb_generations WHERE service_draft_id=sid) OR EXISTS(SELECT 1 FROM wc_applications WHERE new_service_id=sid) $$;
CREATE OR REPLACE FUNCTION sgf_wc_linked_billing(bid bigint) RETURNS boolean LANGUAGE sql AS $$ SELECT EXISTS(SELECT 1 FROM wb_generations WHERE billing_draft_id=bid) OR EXISTS(SELECT 1 FROM wc_applications WHERE new_billing_id=bid) $$;
CREATE OR REPLACE FUNCTION sgf_wc_assert_current(bid bigint) RETURNS void LANGUAGE plpgsql AS $$
BEGIN IF EXISTS(SELECT 1 FROM wc_applications WHERE old_billing_id=bid) THEN RAISE EXCEPTION 'SGF_WC_DESTINATION_SUPERSEDED_OR_CANCELLED'; END IF; END; $$;
CREATE OR REPLACE FUNCTION sgf_wc_integrity() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE r wc_requests; g wb_generations; a wc_applications; n integer; prior bigint; sid bigint; bid bigint;
BEGIN
PERFORM sgf_ic_environment();
IF TG_TABLE_NAME='wc_reviews' THEN SELECT * INTO STRICT r FROM wc_requests WHERE id=NEW.request_id; SELECT * INTO STRICT g FROM wb_generations WHERE id=r.generation_id;
ELSE SELECT * INTO STRICT g FROM wb_generations WHERE id=NEW.generation_id; END IF;
PERFORM 1 FROM companies WHERE id=(SELECT company_id FROM wo_orders WHERE id=g.order_id) FOR UPDATE;
IF TG_TABLE_NAME='wc_requests' THEN
SELECT coalesce(max(version),0) INTO n FROM wc_requests WHERE generation_id=g.id; SELECT id INTO prior FROM wc_requests WHERE generation_id=g.id AND version=n;
IF NEW.version<>n+1 OR NEW.previous_id IS DISTINCT FROM prior THEN RAISE EXCEPTION 'SGF_WC_REQUEST_VERSION'; END IF;
ELSIF TG_TABLE_NAME='wc_reviews' THEN
IF r.actor_id=NEW.actor_id OR r.version<>NEW.version OR r.kind='withdraw' OR EXISTS(SELECT 1 FROM wc_requests WHERE previous_id=r.id) THEN RAISE EXCEPTION 'SGF_WC_REVIEW_IDENTITY_VERSION'; END IF;
ELSIF TG_TABLE_NAME='wc_applications' THEN
SELECT * INTO STRICT r FROM wc_requests WHERE id=NEW.request_id AND generation_id=g.id;
IF r.kind<>NEW.kind OR EXISTS(SELECT 1 FROM wc_requests WHERE previous_id=r.id) OR NOT EXISTS(SELECT 1 FROM wc_reviews WHERE request_id=r.id AND version=r.version AND decision='approved') THEN RAISE EXCEPTION 'SGF_WC_APPROVED_REQUEST'; END IF;
SELECT * INTO a FROM wc_applications WHERE generation_id=g.id ORDER BY sequence DESC LIMIT 1;
sid=coalesce(a.new_service_id,g.service_draft_id);bid=coalesce(a.new_billing_id,g.billing_draft_id);
IF a.kind='cancel' OR NEW.sequence<>coalesce(a.sequence,0)+1 OR NEW.old_service_id<>sid OR NEW.old_billing_id<>bid THEN RAISE EXCEPTION 'SGF_WC_DESTINATION_CHAIN'; END IF;
IF EXISTS(SELECT 1 FROM b_internal_confirmations WHERE billing_draft_id=bid) OR EXISTS(SELECT 1 FROM a_billing_drafts WHERE source_id=sid AND id<>bid) THEN RAISE EXCEPTION 'SGF_WC_DEPENDENCY'; END IF;
IF NEW.kind='replace' AND NOT EXISTS(SELECT 1 FROM a_billing_drafts b JOIN a_service_drafts s ON s.id=b.source_id JOIN wo_orders o ON o.id=g.order_id WHERE b.id=NEW.new_billing_id AND s.id=NEW.new_service_id AND b.state='draft' AND s.state='draft' AND ROW(b.tenant_id,b.company_id,b.unit_id,b.client_id)=ROW(o.tenant_id,o.company_id,o.unit_id,o.client_id) AND ROW(s.tenant_id,s.company_id,s.unit_id,s.client_id)=ROW(o.tenant_id,o.company_id,o.unit_id,o.client_id)) THEN RAISE EXCEPTION 'SGF_WC_NEW_DESTINATION_SCOPE'; END IF;
END IF;
RETURN NEW;
END; $$;
CREATE TRIGGER wc_request_integrity BEFORE INSERT ON wc_requests FOR EACH ROW EXECUTE FUNCTION sgf_wc_integrity();
CREATE TRIGGER wc_review_integrity BEFORE INSERT ON wc_reviews FOR EACH ROW EXECUTE FUNCTION sgf_wc_integrity();
CREATE TRIGGER wc_application_integrity BEFORE INSERT ON wc_applications FOR EACH ROW EXECUTE FUNCTION sgf_wc_integrity();
CREATE OR REPLACE FUNCTION sgf_wb_draft_locked() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF (TG_TABLE_NAME='a_service_drafts' AND sgf_wc_linked_service(OLD.id)) OR (TG_TABLE_NAME='a_billing_drafts' AND sgf_wc_linked_billing(OLD.id)) THEN RAISE EXCEPTION 'SGF_WB_LINKED_DRAFT_REQUIRES_REVIEWED_CORRECTION'; END IF;
IF TG_OP='UPDATE' THEN RETURN NEW; END IF; RETURN OLD;
END; $$;
CREATE OR REPLACE FUNCTION sgf_wb_no_second_billing() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN IF sgf_wc_linked_service(NEW.source_id) THEN RAISE EXCEPTION 'SGF_WB_SOURCE_ALREADY_CONVERTED'; END IF; RETURN NEW; END; $$;
CREATE OR REPLACE FUNCTION sgf_wc_confirmation_guard() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
PERFORM 1 FROM companies WHERE id=NEW.company_id FOR UPDATE;
PERFORM sgf_wc_assert_current(NEW.billing_draft_id); RETURN NEW;
END; $$;
CREATE TRIGGER wc_confirmation_current BEFORE INSERT ON b_internal_confirmations FOR EACH ROW EXECUTE FUNCTION sgf_wc_confirmation_guard();