-- Additive workflow storage. No historical time or financial data is modified. ALTER TABLE customer_fixed_price_agreements ADD COLUMN IF NOT EXISTS workflow_coverage JSONB NOT NULL DEFAULT '{}'; ALTER TABLE customer_fixed_price_agreements ADD COLUMN IF NOT EXISTS monthly_price NUMERIC(12,2); ALTER TABLE tticket_prepaid_cards ADD COLUMN IF NOT EXISTS workflow_coverage JSONB NOT NULL DEFAULT '{}'; CREATE TABLE IF NOT EXISTS bmc_workflow_resource_history ( id BIGSERIAL PRIMARY KEY,resource_type TEXT NOT NULL,resource_id BIGINT NOT NULL, created_by TEXT,before_value JSONB NOT NULL,after_value JSONB NOT NULL,created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS bmc_workflows ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, customer_id TEXT, priority INTEGER NOT NULL DEFAULT 100, enabled BOOLEAN NOT NULL DEFAULT FALSE, active_version_id BIGINT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS bmc_workflow_versions ( id BIGSERIAL PRIMARY KEY, workflow_id BIGINT NOT NULL REFERENCES bmc_workflows(id), version INTEGER NOT NULL, definition JSONB NOT NULL, created_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(workflow_id,version) ); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='bmc_workflows_active_version_fk') THEN ALTER TABLE bmc_workflows ADD CONSTRAINT bmc_workflows_active_version_fk FOREIGN KEY(active_version_id) REFERENCES bmc_workflow_versions(id); END IF; END $$; CREATE TABLE IF NOT EXISTS bmc_workflow_events ( id TEXT PRIMARY KEY, event_key TEXT NOT NULL UNIQUE, trigger TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, revision BIGINT NOT NULL DEFAULT 0, context JSONB NOT NULL DEFAULT '{}', chain JSONB NOT NULL DEFAULT '[]', status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','processing','done','failed','superseded')), attempts INTEGER NOT NULL DEFAULT 0, available_at TIMESTAMPTZ NOT NULL DEFAULT now(), error TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS bmc_workflow_pending ON bmc_workflow_events(available_at) WHERE status='pending'; CREATE TABLE IF NOT EXISTS bmc_workflow_executions ( id BIGSERIAL PRIMARY KEY,event_id TEXT NOT NULL REFERENCES bmc_workflow_events(id), mode TEXT NOT NULL CHECK(mode IN ('shadow','active')),result JSONB NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(event_id,mode) ); CREATE TABLE IF NOT EXISTS bmc_billing_decisions ( id BIGSERIAL PRIMARY KEY,time_id BIGINT NOT NULL REFERENCES tmodule_times(id),revision BIGINT NOT NULL, event_id TEXT REFERENCES bmc_workflow_events(id),status TEXT NOT NULL CHECK(status IN ('decided','manual_review','overridden','settled')), method TEXT,details JSONB NOT NULL DEFAULT '{}',is_current BOOLEAN NOT NULL DEFAULT TRUE, manual_override BOOLEAN NOT NULL DEFAULT FALSE,created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE UNIQUE INDEX IF NOT EXISTS bmc_billing_current ON bmc_billing_decisions(time_id) WHERE is_current; CREATE TABLE IF NOT EXISTS bmc_workflow_effects ( id BIGSERIAL PRIMARY KEY,event_id TEXT NOT NULL REFERENCES bmc_workflow_events(id),action_key TEXT NOT NULL UNIQUE, status TEXT NOT NULL DEFAULT 'planned',payload JSONB NOT NULL,result JSONB,created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS bmc_workflow_activation_history ( id BIGSERIAL PRIMARY KEY,workflow_id BIGINT NOT NULL REFERENCES bmc_workflows(id), version_id BIGINT REFERENCES bmc_workflow_versions(id),previous_version_id BIGINT REFERENCES bmc_workflow_versions(id), created_by TEXT,created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE OR REPLACE FUNCTION bmc_workflow_version_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'Workflow versions are immutable; create a new version'; END $$; DROP TRIGGER IF EXISTS bmc_workflow_version_immutable_trigger ON bmc_workflow_versions; CREATE TRIGGER bmc_workflow_version_immutable_trigger BEFORE UPDATE OR DELETE ON bmc_workflow_versions FOR EACH ROW EXECUTE FUNCTION bmc_workflow_version_immutable();