CREATE TABLE cash_events(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,session_key uuid NOT NULL,version bigint NOT NULL CHECK(version>0),action text NOT NULL CHECK(action IN('prepare','review','close')),kind text NOT NULL CHECK(kind IN('open','receipt','expense','count')),state text NOT NULL CHECK(state IN('proposed','approved','refused','closed')),proposal_id bigint REFERENCES cash_events(id),source_receipt_id bigint REFERENCES sr_records(id),actor_id bigint NOT NULL REFERENCES users(id),data jsonb NOT NULL,dependencies jsonb NOT NULL,reason text NOT NULL CHECK(length(reason)>=10),created_at timestamptz NOT NULL DEFAULT now(),UNIQUE(tenant_id,company_id,unit_id,session_key,version),FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)); CREATE UNIQUE INDEX cash_one_review ON cash_events(proposal_id) WHERE action='review'; CREATE UNIQUE INDEX cash_one_receipt ON cash_events(source_receipt_id) WHERE action='review' AND kind='receipt' AND state='approved'; CREATE UNIQUE INDEX cash_one_close ON cash_events(tenant_id,company_id,unit_id,session_key) WHERE action='close'; CREATE UNIQUE INDEX cash_one_open ON cash_events(tenant_id,company_id,unit_id,session_key) WHERE action='review' AND kind='open' AND state='approved'; CREATE TABLE cash_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),command_key uuid NOT NULL,request_hash char(64) NOT NULL,result jsonb NOT NULL,created_at timestamptz NOT NULL DEFAULT now(),UNIQUE(tenant_id,company_id,unit_id,actor_id,command_key)); CREATE FUNCTION sgf_cash_guard() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE latest bigint;p cash_events;permission_name text; BEGIN PERFORM sgf_wo_environment();PERFORM 1 FROM companies WHERE id=NEW.company_id AND tenant_id=NEW.tenant_id FOR UPDATE; permission_name:='cash.'||CASE NEW.action WHEN 'prepare' THEN 'prepare' WHEN 'review' THEN 'review' ELSE 'close' END; IF NOT EXISTS(SELECT 1 FROM permission_grants WHERE user_id=NEW.actor_id AND tenant_id=NEW.tenant_id AND company_id=NEW.company_id AND unit_id=NEW.unit_id AND permission=permission_name) THEN RAISE EXCEPTION 'SGF_CASH_ACCESS'; END IF; SELECT coalesce(max(version),0) INTO latest FROM cash_events WHERE (tenant_id,company_id,unit_id,session_key)=(NEW.tenant_id,NEW.company_id,NEW.unit_id,NEW.session_key); IF NEW.version<>latest+1 OR EXISTS(SELECT 1 FROM cash_events WHERE (tenant_id,company_id,unit_id,session_key)=(NEW.tenant_id,NEW.company_id,NEW.unit_id,NEW.session_key) AND state='closed') THEN RAISE EXCEPTION 'SGF_CASH_STALE_OR_CLOSED'; END IF; IF NEW.action='review' THEN SELECT * INTO STRICT p FROM cash_events WHERE id=NEW.proposal_id AND action='prepare';IF ROW(p.tenant_id,p.company_id,p.unit_id,p.session_key,p.kind) IS DISTINCT FROM ROW(NEW.tenant_id,NEW.company_id,NEW.unit_id,NEW.session_key,NEW.kind) OR p.actor_id=NEW.actor_id OR NEW.data IS DISTINCT FROM p.data OR NEW.source_receipt_id IS DISTINCT FROM p.source_receipt_id THEN RAISE EXCEPTION 'SGF_CASH_SELF_OR_SOURCE'; END IF; END IF; RETURN NEW; END; $$; CREATE TRIGGER cash_guard BEFORE INSERT ON cash_events FOR EACH ROW EXECUTE FUNCTION sgf_cash_guard(); CREATE TRIGGER cash_events_immutable BEFORE UPDATE OR DELETE ON cash_events FOR EACH ROW EXECUTE FUNCTION sgf_append_only_audit(); CREATE TRIGGER cash_commands_immutable BEFORE UPDATE OR DELETE ON cash_commands FOR EACH ROW EXECUTE FUNCTION sgf_append_only_audit(); CREATE TRIGGER cash_commands_test BEFORE INSERT ON cash_commands FOR EACH ROW EXECUTE FUNCTION sgf_wo_test_only(); REVOKE ALL ON cash_events,cash_commands FROM PUBLIC; REVOKE ALL ON SEQUENCE cash_events_id_seq,cash_commands_id_seq FROM PUBLIC; REVOKE ALL ON FUNCTION sgf_cash_guard() FROM PUBLIC;