first commit
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user