Accept isolated WORKSHOP-RESOURCE-RECORD-v1 delivery
Record explicit workshop acceptance and preserve subsequent synthetic work without accepting receivables. Keep 080000/070000 unapplied, real workshop grants pending and operational issuance blocks intact. Exclude local credentials and real-data preservation reports.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
CREATE TABLE sr_records (
|
||||
id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, company_id bigint NOT NULL, unit_id bigint NOT NULL,
|
||||
invoice_id bigint NOT NULL REFERENCES si_invoices(id), client_id bigint NOT NULL REFERENCES a_clients(id),
|
||||
obligation_id bigint REFERENCES sr_records(id), receipt_id bigint REFERENCES sr_records(id),
|
||||
kind text NOT NULL CHECK(kind IN ('obligation','receipt','observation','correction')),
|
||||
currency text NOT NULL CHECK(currency='MZN'), amount numeric NOT NULL CHECK(amount>=0 AND amount<10000000000000000 AND amount=trunc(amount,2)),
|
||||
source text NOT NULL, external_id text NOT NULL, data jsonb NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1 CHECK(version=1), actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL,
|
||||
UNIQUE(company_id,unit_id,kind,source,external_id),
|
||||
FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)
|
||||
);
|
||||
CREATE UNIQUE INDEX sr_one_obligation ON sr_records(invoice_id) WHERE kind='obligation';
|
||||
CREATE UNIQUE INDEX sr_reduction_document ON sr_records(company_id,unit_id,(data->>'document_ref')) WHERE kind='correction' AND data->>'action'='reduction';
|
||||
CREATE TABLE sr_effects (
|
||||
id bigserial PRIMARY KEY, record_id bigint NOT NULL REFERENCES sr_records(id),
|
||||
obligation_id bigint NOT NULL REFERENCES sr_records(id), receipt_id bigint REFERENCES sr_records(id),
|
||||
kind text NOT NULL CHECK(kind IN ('open','receipt_confirm','refused','allocate','reconcile','divergence','reverse_allocation','reverse_reconcile','reverse_receipt','reduction','due')),
|
||||
amount numeric NOT NULL CHECK(amount>=0 AND amount<10000000000000000 AND amount=trunc(amount,2)), target_effect_id bigint REFERENCES sr_effects(id),
|
||||
revision integer NOT NULL CHECK(revision>=0), data jsonb NOT NULL,
|
||||
actor_id bigint NOT NULL REFERENCES users(id), created_at timestamptz NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX sr_terminal_decision ON sr_effects(record_id) WHERE kind NOT IN ('allocate','divergence');
|
||||
CREATE UNIQUE INDEX sr_reverse_once ON sr_effects(target_effect_id) WHERE target_effect_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX sr_divergence_once ON sr_effects(record_id) WHERE kind='divergence';
|
||||
CREATE INDEX sr_obligation_effects ON sr_effects(obligation_id,id);
|
||||
CREATE TABLE sr_commands (
|
||||
id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, company_id bigint NOT NULL, unit_id bigint NOT NULL,
|
||||
actor_id bigint NOT NULL REFERENCES users(id), action text NOT NULL, command_key varchar(100) NOT NULL,
|
||||
request_hash varchar(64) 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 sr_events (
|
||||
id bigserial PRIMARY KEY, command_id bigint NOT NULL UNIQUE REFERENCES sr_commands(id),
|
||||
type text NOT NULL, schema_version integer NOT NULL CHECK(schema_version=1), payload jsonb NOT NULL, created_at timestamptz NOT NULL
|
||||
);
|
||||
CREATE TABLE sr_outbox (event_id bigint PRIMARY KEY REFERENCES sr_events(id), state text NOT NULL CHECK(state='captured_simulation_only'));
|
||||
CREATE TABLE sr_deliveries (event_id bigint PRIMARY KEY REFERENCES sr_events(id), consumer text NOT NULL CHECK(consumer='simulation_integrity_v1'), created_at timestamptz NOT NULL);
|
||||
|
||||
CREATE OR REPLACE FUNCTION sgf_sr_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_SR_TEST_DATABASE_ONLY'; END IF;
|
||||
END; $$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION sgf_sr_record_guard() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE inv si_invoices; ob sr_records; rc sr_records; cid bigint; expected numeric;
|
||||
BEGIN
|
||||
PERFORM sgf_sr_environment();
|
||||
PERFORM 1 FROM companies WHERE id=NEW.company_id FOR UPDATE;
|
||||
SELECT * INTO STRICT inv FROM si_invoices WHERE id=NEW.invoice_id;
|
||||
SELECT (snapshot->>'client_id')::bigint INTO STRICT cid FROM a_history
|
||||
WHERE area='billing_drafts' AND record_id=inv.draft_id AND version=(inv.snapshot->'document'->>'draft_version')::integer
|
||||
AND tenant_id=inv.tenant_id AND company_id=inv.company_id AND unit_id=inv.unit_id;
|
||||
IF ROW(inv.tenant_id,inv.company_id,inv.unit_id,cid) IS DISTINCT FROM ROW(NEW.tenant_id,NEW.company_id,NEW.unit_id,NEW.client_id)
|
||||
OR inv.snapshot->>'notice' IS DISTINCT FROM 'TESTE — SEM VALIDADE FISCAL'
|
||||
OR inv.snapshot->>'simulation_only' IS DISTINCT FROM 'true'
|
||||
OR inv.snapshot->'document'->'data'->>'currency' IS DISTINCT FROM NEW.currency
|
||||
THEN RAISE EXCEPTION 'SGF_SR_DOCUMENT_SCOPE'; END IF;
|
||||
IF NEW.data->>'policy' IS DISTINCT FROM 'SGF-RECEIVABLES-SIM-v1' THEN RAISE EXCEPTION 'SGF_SR_POLICY'; END IF;
|
||||
IF NEW.kind='obligation' THEN
|
||||
expected=(inv.snapshot->'calculation'->>'total_document_proposed')::numeric;
|
||||
IF NEW.obligation_id IS NOT NULL OR NEW.receipt_id IS NOT NULL OR NEW.amount<>expected OR expected<=0 OR trunc(expected,2)<>expected THEN RAISE EXCEPTION 'SGF_SR_ORIGIN_AMOUNT'; END IF;
|
||||
ELSE
|
||||
SELECT * INTO STRICT ob FROM sr_records WHERE id=NEW.obligation_id AND kind='obligation';
|
||||
IF ROW(ob.tenant_id,ob.company_id,ob.unit_id,ob.invoice_id,ob.client_id,ob.currency) IS DISTINCT FROM ROW(NEW.tenant_id,NEW.company_id,NEW.unit_id,NEW.invoice_id,NEW.client_id,NEW.currency)
|
||||
OR NOT EXISTS(SELECT 1 FROM sr_effects WHERE record_id=ob.id AND kind='open') THEN RAISE EXCEPTION 'SGF_SR_OBLIGATION_SCOPE'; END IF;
|
||||
IF NEW.kind='receipt' AND (NEW.amount<=0 OR NEW.receipt_id IS NOT NULL OR coalesce(NEW.data->>'evidence','')='' OR coalesce(NEW.data->>'date','')='') THEN RAISE EXCEPTION 'SGF_SR_RECEIPT'; END IF;
|
||||
IF NEW.receipt_id IS NOT NULL THEN
|
||||
SELECT * INTO STRICT rc FROM sr_records WHERE id=NEW.receipt_id AND kind='receipt';
|
||||
IF rc.obligation_id<>ob.id THEN RAISE EXCEPTION 'SGF_SR_RECEIPT_SCOPE'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind='observation' AND (NEW.receipt_id IS NULL OR coalesce(NEW.data->>'date','')='') THEN RAISE EXCEPTION 'SGF_SR_OBSERVATION'; END IF;
|
||||
IF NEW.kind='correction' AND (NEW.data->>'action' NOT IN ('reverse_allocation','reverse_reconcile','reverse_receipt','reduction','due') OR coalesce(NEW.data->>'reason','')='') THEN RAISE EXCEPTION 'SGF_SR_CORRECTION'; END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'SGF_SR_INVALID_REFERENCE';
|
||||
END; $$;
|
||||
CREATE TRIGGER sr_record_guard BEFORE INSERT ON sr_records FOR EACH ROW EXECUTE FUNCTION sgf_sr_record_guard();
|
||||
|
||||
CREATE OR REPLACE FUNCTION sgf_sr_effect_guard() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE r sr_records; ob sr_records; rc sr_records; target sr_effects; balance numeric; available numeric; n integer; a text;
|
||||
BEGIN
|
||||
PERFORM sgf_sr_environment();
|
||||
SELECT * INTO STRICT r FROM sr_records WHERE id=NEW.record_id;
|
||||
PERFORM 1 FROM companies WHERE id=r.company_id FOR UPDATE;
|
||||
SELECT * INTO STRICT ob FROM sr_records WHERE id=NEW.obligation_id AND kind='obligation';
|
||||
IF (CASE WHEN r.kind='obligation' THEN r.id ELSE r.obligation_id END)<>ob.id THEN RAISE EXCEPTION 'SGF_SR_EFFECT_SCOPE'; END IF;
|
||||
SELECT count(*) INTO n FROM sr_effects WHERE obligation_id=ob.id;
|
||||
IF NEW.revision<>n THEN RAISE EXCEPTION 'SGF_SR_STALE_REVISION'; END IF;
|
||||
IF NEW.kind IN ('open','receipt_confirm','refused','reconcile','divergence','reverse_allocation','reverse_reconcile','reverse_receipt','reduction','due') AND NEW.actor_id=r.actor_id THEN RAISE EXCEPTION 'SGF_SR_SELF_APPROVAL'; END IF;
|
||||
IF NEW.kind='open' AND (r.kind<>'obligation' OR NEW.amount<>r.amount) THEN RAISE EXCEPTION 'SGF_SR_OPEN'; END IF;
|
||||
IF NEW.kind='refused' AND (r.kind NOT IN ('obligation','receipt','correction') OR NEW.amount<>0) THEN RAISE EXCEPTION 'SGF_SR_REFUSE'; END IF;
|
||||
IF NEW.kind='receipt_confirm' AND (r.kind<>'receipt' OR NEW.amount<>r.amount) THEN RAISE EXCEPTION 'SGF_SR_CONFIRM'; END IF;
|
||||
IF NEW.receipt_id IS NOT NULL THEN
|
||||
SELECT * INTO STRICT rc FROM sr_records WHERE id=NEW.receipt_id AND kind='receipt';
|
||||
IF rc.obligation_id<>ob.id OR NOT EXISTS(SELECT 1 FROM sr_effects WHERE record_id=rc.id AND kind='receipt_confirm')
|
||||
OR EXISTS(SELECT 1 FROM sr_effects WHERE receipt_id=rc.id AND kind='reverse_receipt') THEN RAISE EXCEPTION 'SGF_SR_RECEIPT_UNAVAILABLE'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind='receipt_confirm' AND NEW.receipt_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_SR_CONFIRM_LINK'; END IF;
|
||||
IF NEW.kind='receipt_confirm' AND NEW.amount+(SELECT coalesce(sum(CASE WHEN kind='receipt_confirm' THEN amount WHEN kind='reverse_receipt' THEN -amount ELSE 0 END),0) FROM sr_effects WHERE obligation_id=ob.id)>=10000000000000000 THEN RAISE EXCEPTION 'SGF_SR_TOTAL_OVERFLOW'; END IF;
|
||||
IF NEW.kind NOT IN ('reverse_allocation','reverse_reconcile') AND NEW.target_effect_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_SR_UNEXPECTED_TARGET'; END IF;
|
||||
IF NEW.kind NOT IN ('open','refused') AND NOT EXISTS(SELECT 1 FROM sr_effects WHERE record_id=ob.id AND kind='open') THEN RAISE EXCEPTION 'SGF_SR_NOT_OPEN'; END IF;
|
||||
SELECT ob.amount-coalesce(sum(CASE WHEN kind IN ('allocate','reduction') THEN amount WHEN kind='reverse_allocation' THEN -amount ELSE 0 END),0) INTO balance FROM sr_effects WHERE obligation_id=ob.id;
|
||||
IF NEW.kind='allocate' THEN
|
||||
IF r.kind<>'receipt' OR NEW.receipt_id IS DISTINCT FROM r.id OR NEW.amount<=0 THEN RAISE EXCEPTION 'SGF_SR_ALLOCATION'; END IF;
|
||||
SELECT rc.amount-coalesce(sum(CASE WHEN kind='allocate' THEN amount WHEN kind='reverse_allocation' THEN -amount ELSE 0 END),0) INTO available FROM sr_effects WHERE receipt_id=rc.id;
|
||||
IF NEW.amount>balance OR NEW.amount>available THEN RAISE EXCEPTION 'SGF_SR_OVERALLOCATION'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind IN ('reconcile','divergence') THEN
|
||||
IF r.kind<>'observation' OR r.receipt_id IS DISTINCT FROM NEW.receipt_id THEN RAISE EXCEPTION 'SGF_SR_RECONCILE_SCOPE'; END IF;
|
||||
IF NEW.kind='reconcile' AND (r.amount<>rc.amount OR NEW.amount<>rc.amount) THEN RAISE EXCEPTION 'SGF_SR_RECONCILE_AMOUNT'; END IF;
|
||||
IF NEW.kind='divergence' AND (r.amount=rc.amount OR NEW.amount<>abs(r.amount-rc.amount)) THEN RAISE EXCEPTION 'SGF_SR_DIVERGENCE'; END IF;
|
||||
IF EXISTS(SELECT 1 FROM sr_effects e WHERE e.kind='reconcile' AND (e.receipt_id=rc.id OR e.record_id=r.id) AND NOT EXISTS(SELECT 1 FROM sr_effects v WHERE v.target_effect_id=e.id)) THEN RAISE EXCEPTION 'SGF_SR_OBSERVATION_REUSED'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind IN ('reverse_allocation','reverse_reconcile','reverse_receipt','reduction','due') THEN
|
||||
IF r.kind<>'correction' OR r.data->>'action' IS DISTINCT FROM NEW.kind OR (r.data->>'revision')::integer IS DISTINCT FROM n THEN RAISE EXCEPTION 'SGF_SR_CORRECTION_VERSION'; END IF;
|
||||
IF NEW.kind IN ('reverse_allocation','reverse_reconcile') THEN
|
||||
SELECT * INTO STRICT target FROM sr_effects WHERE id=NEW.target_effect_id AND obligation_id=ob.id;
|
||||
IF target.kind<>(CASE WHEN NEW.kind='reverse_allocation' THEN 'allocate' ELSE 'reconcile' END) OR NEW.amount<>target.amount OR NEW.receipt_id IS DISTINCT FROM target.receipt_id OR (r.data->>'target_effect_id')::bigint IS DISTINCT FROM target.id THEN RAISE EXCEPTION 'SGF_SR_REVERSAL_TARGET'; END IF;
|
||||
ELSIF NEW.target_effect_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_SR_UNEXPECTED_TARGET'; END IF;
|
||||
IF NEW.kind='reverse_receipt' THEN
|
||||
IF r.receipt_id IS DISTINCT FROM NEW.receipt_id OR rc.id IS NULL OR NEW.amount<>rc.amount THEN RAISE EXCEPTION 'SGF_SR_REVERSAL_RECEIPT'; END IF;
|
||||
SELECT coalesce(sum(CASE WHEN kind='allocate' THEN amount WHEN kind='reverse_allocation' THEN -amount ELSE 0 END),0) INTO available FROM sr_effects WHERE receipt_id=rc.id;
|
||||
IF available<>0 OR EXISTS(SELECT 1 FROM sr_effects e WHERE e.receipt_id=rc.id AND e.kind='reconcile' AND NOT EXISTS(SELECT 1 FROM sr_effects v WHERE v.target_effect_id=e.id)) THEN RAISE EXCEPTION 'SGF_SR_REVERSAL_DEPENDENCIES'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind='reduction' AND (NEW.amount<=0 OR NEW.amount<>r.amount OR NEW.amount>balance OR coalesce(r.data->>'document_ref','') NOT LIKE 'TEST-REDUCTION-%' OR r.data->>'line' IS DISTINCT FROM '1') THEN RAISE EXCEPTION 'SGF_SR_REDUCTION'; END IF;
|
||||
IF NEW.kind='due' AND (NEW.amount<>0 OR NEW.data->>'due_date' IS DISTINCT FROM r.data->>'due_date') THEN RAISE EXCEPTION 'SGF_SR_DUE'; END IF;
|
||||
END IF;
|
||||
IF NEW.kind IN ('open','refused','receipt_confirm','reduction','due') AND NEW.receipt_id IS NOT NULL THEN RAISE EXCEPTION 'SGF_SR_UNEXPECTED_RECEIPT'; END IF;
|
||||
RETURN NEW;
|
||||
EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'SGF_SR_INVALID_REFERENCE';
|
||||
END; $$;
|
||||
CREATE TRIGGER sr_effect_guard BEFORE INSERT ON sr_effects FOR EACH ROW EXECUTE FUNCTION sgf_sr_effect_guard();
|
||||
|
||||
CREATE OR REPLACE FUNCTION sgf_sr_test_insert() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
BEGIN PERFORM sgf_sr_environment(); RETURN NEW; END; $$;
|
||||
Reference in New Issue
Block a user