first commit

This commit is contained in:
2026-10-27 12:26:52 +02:00
parent 7628645fb6
commit ea9dcb3314
19 changed files with 701 additions and 1 deletions
+103
View File
@@ -0,0 +1,103 @@
CREATE TABLE dc_rates (
id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, company_id bigint NOT NULL, unit_id bigint NOT NULL,
order_id bigint NOT NULL REFERENCES wo_orders(id), entry_id bigint NOT NULL REFERENCES wo_entries(id), entry_version integer NOT NULL,
identifier text NOT NULL, version integer NOT NULL CHECK(version>0), previous_id bigint UNIQUE REFERENCES dc_rates(id),
currency text NOT NULL CHECK(currency='MZN'), unit text NOT NULL CHECK(unit IN ('UN','KG','M','L','H')),
amount numeric NOT NULL CHECK(amount>=0 AND amount<10000000000000000 AND amount=trunc(amount,8)), evidence text NOT NULL CHECK(length(evidence)>=3),
actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL,
UNIQUE(order_id,identifier,version), FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)
);
CREATE TABLE dc_calculations (
id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, company_id bigint NOT NULL, unit_id bigint NOT NULL,
order_id bigint NOT NULL REFERENCES wo_orders(id), version integer NOT NULL CHECK(version>0), previous_id bigint UNIQUE REFERENCES dc_calculations(id),
fingerprint text NOT NULL, snapshot jsonb NOT NULL, actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL,
UNIQUE(order_id,version), FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)
);
CREATE TABLE dc_decisions (
id bigserial PRIMARY KEY, rate_id bigint UNIQUE REFERENCES dc_rates(id), calculation_id bigint UNIQUE REFERENCES dc_calculations(id),
version integer NOT NULL CHECK(version>0), 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,
CHECK(num_nonnulls(rate_id,calculation_id)=1)
);
CREATE TABLE dc_commands (
id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, company_id bigint NOT NULL, unit_id bigint NOT NULL,
order_id bigint NOT NULL REFERENCES wo_orders(id), actor_id bigint NOT NULL REFERENCES users(id), action text NOT NULL CHECK(action IN ('rate','calculate','decide')),
command_key text NOT NULL, request_hash text NOT NULL, result jsonb NOT NULL, created_at timestamptz NOT NULL,
UNIQUE(company_id,unit_id,actor_id,action,command_key), FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)
);
CREATE TABLE dc_events (id bigserial PRIMARY KEY, command_id bigint NOT NULL UNIQUE REFERENCES dc_commands(id), schema_version integer NOT NULL CHECK(schema_version=1), payload jsonb NOT NULL, created_at timestamptz NOT NULL);
CREATE TABLE dc_outbox (event_id bigint PRIMARY KEY REFERENCES dc_events(id), state text NOT NULL CHECK(state='captured_no_accounting'));
CREATE OR REPLACE FUNCTION sgf_dc_environment() RETURNS void LANGUAGE plpgsql AS $$
BEGIN IF current_database() IS DISTINCT FROM 'sgf_tempo_test' OR inet_server_port() IS DISTINCT FROM 55432 THEN RAISE EXCEPTION 'SGF_DC_TEST_DATABASE_ONLY'; END IF; END; $$;
CREATE OR REPLACE FUNCTION sgf_dc_test_only() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN PERFORM sgf_dc_environment(); RETURN NEW; END; $$;
CREATE OR REPLACE FUNCTION sgf_dc_source(eid bigint, oid bigint, ev integer) RETURNS void LANGUAGE plpgsql AS $$
DECLARE e wo_entries; d wo_decisions;
BEGIN
SELECT * INTO STRICT e FROM wo_entries WHERE id=eid AND order_id=oid AND version=ev;
SELECT * INTO STRICT d FROM wo_decisions WHERE entry_id=e.id AND entry_version=e.version AND decision='verified';
IF EXISTS(SELECT 1 FROM wo_entries WHERE previous_id=e.id) OR e.outside_authority
OR NOT ((e.kind='time' AND e.data->>'time_type'='work') OR (e.kind='material' AND e.data->>'event'='consumed' AND e.data->>'owner'='company'))
OR EXISTS(SELECT 1 FROM wo_entries WHERE previous_id=e.task_id) THEN RAISE EXCEPTION 'SGF_DC_INELIGIBLE_SOURCE'; END IF;
EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'SGF_DC_UNVERIFIED_SOURCE'; END; $$;
CREATE OR REPLACE FUNCTION sgf_dc_guard() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE o wo_orders; e wo_entries; p record; target record; line jsonb; r dc_rates; latest integer;
BEGIN
PERFORM sgf_dc_environment();
IF TG_TABLE_NAME='dc_decisions' THEN
IF NEW.rate_id IS NOT NULL THEN SELECT order_id INTO STRICT latest FROM dc_rates WHERE id=NEW.rate_id;
ELSE SELECT order_id INTO STRICT latest FROM dc_calculations WHERE id=NEW.calculation_id; END IF;
SELECT * INTO STRICT o FROM wo_orders WHERE id=latest; PERFORM 1 FROM companies WHERE id=o.company_id FOR UPDATE;
IF NEW.rate_id IS NOT NULL THEN
SELECT id,actor_id,version,order_id INTO STRICT target FROM dc_rates WHERE id=NEW.rate_id;
IF EXISTS(SELECT 1 FROM dc_rates WHERE previous_id=target.id) THEN RAISE EXCEPTION 'SGF_DC_OLD_RATE'; END IF;
SELECT * INTO r FROM dc_rates WHERE id=target.id; PERFORM sgf_dc_source(r.entry_id,r.order_id,r.entry_version);
ELSE
SELECT id,actor_id,version,order_id INTO STRICT target FROM dc_calculations WHERE id=NEW.calculation_id;
IF EXISTS(SELECT 1 FROM dc_calculations WHERE previous_id=target.id) THEN RAISE EXCEPTION 'SGF_DC_OLD_CALCULATION'; END IF;
FOR line IN SELECT * FROM jsonb_array_elements((SELECT snapshot->'lines' FROM dc_calculations WHERE id=target.id)) LOOP
PERFORM sgf_dc_source((line->>'entry_id')::bigint,target.order_id,(line->>'entry_version')::integer);
IF line->>'rate_id' IS NOT NULL THEN
SELECT * INTO STRICT r FROM dc_rates WHERE id=(line->>'rate_id')::bigint;
IF EXISTS(SELECT 1 FROM dc_rates WHERE previous_id=r.id) OR NOT EXISTS(SELECT 1 FROM dc_decisions WHERE rate_id=r.id AND decision='approved') THEN RAISE EXCEPTION 'SGF_DC_OLD_RATE'; END IF;
END IF;
END LOOP;
END IF;
SELECT * INTO STRICT o FROM wo_orders WHERE id=target.order_id; PERFORM 1 FROM companies WHERE id=o.company_id FOR UPDATE;
IF target.actor_id=NEW.actor_id OR target.version<>NEW.version THEN RAISE EXCEPTION 'SGF_DC_SEPARATION_OR_VERSION'; END IF;
RETURN NEW;
END IF;
SELECT * INTO STRICT o FROM wo_orders WHERE id=NEW.order_id; PERFORM 1 FROM companies WHERE id=o.company_id FOR UPDATE;
IF ROW(NEW.tenant_id,NEW.company_id,NEW.unit_id) IS DISTINCT FROM ROW(o.tenant_id,o.company_id,o.unit_id) THEN RAISE EXCEPTION 'SGF_DC_SCOPE'; END IF;
IF TG_TABLE_NAME='dc_commands' THEN RETURN NEW; END IF;
IF TG_TABLE_NAME='dc_rates' THEN
PERFORM sgf_dc_source(NEW.entry_id,NEW.order_id,NEW.entry_version);
SELECT * INTO STRICT e FROM wo_entries WHERE id=NEW.entry_id;
IF (e.kind='time' AND NEW.unit<>'H') OR (e.kind='material' AND NEW.unit IS DISTINCT FROM e.data->>'unit') THEN RAISE EXCEPTION 'SGF_DC_UNIT'; END IF;
SELECT coalesce(max(version),0) INTO latest FROM dc_rates WHERE order_id=o.id AND identifier=NEW.identifier;
IF NEW.version<>latest+1 THEN RAISE EXCEPTION 'SGF_DC_VERSION'; END IF;
IF latest=0 THEN IF NEW.previous_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_DC_PREVIOUS'; END IF;
ELSE
SELECT * INTO STRICT p FROM dc_rates WHERE id=NEW.previous_id AND order_id=o.id AND identifier=NEW.identifier AND version=latest;
IF NOT EXISTS(SELECT 1 FROM wo_entries pe WHERE pe.id=p.entry_id AND pe.origin=e.origin AND pe.kind=e.kind) THEN RAISE EXCEPTION 'SGF_DC_SOURCE_TRANSFER'; END IF;
END IF;
ELSE
SELECT coalesce(max(version),0) INTO latest FROM dc_calculations WHERE order_id=o.id;
IF NEW.version<>latest+1 OR NEW.snapshot->>'policy' IS DISTINCT FROM 'WORKSHOP-DIRECT-COST-SIM-v1' OR NEW.snapshot->>'currency' IS DISTINCT FROM 'MZN' THEN RAISE EXCEPTION 'SGF_DC_VERSION_POLICY'; END IF;
IF latest=0 THEN IF NEW.previous_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_DC_PREVIOUS'; END IF;
ELSE PERFORM 1 FROM dc_calculations WHERE id=NEW.previous_id AND order_id=o.id AND version=latest; IF NOT FOUND THEN RAISE EXCEPTION 'SGF_DC_PREVIOUS'; END IF; END IF;
IF jsonb_array_length(NEW.snapshot->'lines')<>2 THEN RAISE EXCEPTION 'SGF_DC_TWO_SOURCES'; END IF;
FOR line IN SELECT * FROM jsonb_array_elements(NEW.snapshot->'lines') LOOP
PERFORM sgf_dc_source((line->>'entry_id')::bigint,o.id,(line->>'entry_version')::integer);
IF line->>'rate_id' IS NOT NULL THEN
SELECT * INTO STRICT r FROM dc_rates WHERE id=(line->>'rate_id')::bigint AND order_id=o.id AND entry_id=(line->>'entry_id')::bigint AND entry_version=(line->>'entry_version')::integer AND version=(line->>'rate_version')::integer;
IF EXISTS(SELECT 1 FROM dc_rates WHERE previous_id=r.id) OR NOT EXISTS(SELECT 1 FROM dc_decisions WHERE rate_id=r.id AND decision='approved') THEN RAISE EXCEPTION 'SGF_DC_RATE_NOT_CURRENT_APPROVED'; END IF;
END IF;
END LOOP;
END IF;
RETURN NEW;
EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'SGF_DC_REFERENCE'; END; $$;
CREATE TRIGGER dc_rate_integrity BEFORE INSERT ON dc_rates FOR EACH ROW EXECUTE FUNCTION sgf_dc_guard();
CREATE TRIGGER dc_calculation_integrity BEFORE INSERT ON dc_calculations FOR EACH ROW EXECUTE FUNCTION sgf_dc_guard();
CREATE TRIGGER dc_decision_integrity BEFORE INSERT ON dc_decisions FOR EACH ROW EXECUTE FUNCTION sgf_dc_guard();
CREATE TRIGGER dc_command_integrity BEFORE INSERT ON dc_commands FOR EACH ROW EXECUTE FUNCTION sgf_dc_guard();