-- DBIS Core Lite Database Schema -- PostgreSQL 14+ -- Operators (Terminal Users) CREATE TABLE IF NOT EXISTS operators ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), operator_id VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE, password_hash VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL CHECK (role IN ('MAKER', 'CHECKER', 'ADMIN')), active BOOLEAN DEFAULT TRUE, last_login_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_operators_operator_id ON operators(operator_id); CREATE INDEX idx_operators_role ON operators(role); -- Payments (Payment Transactions) CREATE TABLE IF NOT EXISTS payments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), payment_id VARCHAR(100) UNIQUE NOT NULL, type VARCHAR(50) NOT NULL CHECK (type IN ('CUSTOMER_CREDIT_TRANSFER', 'FI_TO_FI')), amount DECIMAL(18, 2) NOT NULL, currency VARCHAR(3) NOT NULL, sender_account VARCHAR(100) NOT NULL, sender_bic VARCHAR(11) NOT NULL, receiver_account VARCHAR(100) NOT NULL, receiver_bic VARCHAR(11) NOT NULL, beneficiary_name VARCHAR(255) NOT NULL, purpose TEXT, remittance_info TEXT, maker_operator_id UUID NOT NULL REFERENCES operators(id), checker_operator_id UUID REFERENCES operators(id), status VARCHAR(50) NOT NULL, internal_transaction_id VARCHAR(100), compliance_screening_id VARCHAR(100), compliance_status VARCHAR(20) CHECK (compliance_status IN ('PASS', 'FAIL', 'PENDING')), uetr UUID, iso_message_id VARCHAR(100), iso_message_hash VARCHAR(64), transport_session_id VARCHAR(100), ack_received BOOLEAN DEFAULT FALSE, nack_reason TEXT, settlement_confirmed BOOLEAN DEFAULT FALSE, settlement_date TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_payments_payment_id ON payments(payment_id); CREATE INDEX idx_payments_status ON payments(status); CREATE INDEX idx_payments_uetr ON payments(uetr); CREATE INDEX idx_payments_maker ON payments(maker_operator_id); CREATE INDEX idx_payments_created_at ON payments(created_at); -- Ledger Postings (Core Banking Transactions) CREATE TABLE IF NOT EXISTS ledger_postings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), internal_transaction_id VARCHAR(100) UNIQUE NOT NULL, payment_id UUID NOT NULL REFERENCES payments(id), account_number VARCHAR(100) NOT NULL, transaction_type VARCHAR(20) NOT NULL CHECK (transaction_type IN ('DEBIT', 'CREDIT', 'RESERVE', 'RELEASE')), amount DECIMAL(18, 2) NOT NULL, currency VARCHAR(3) NOT NULL, status VARCHAR(20) NOT NULL CHECK (status IN ('PENDING', 'POSTED', 'FAILED', 'REVERSED')), posting_timestamp TIMESTAMP WITH TIME ZONE, reference TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_ledger_postings_transaction_id ON ledger_postings(internal_transaction_id); CREATE INDEX idx_ledger_postings_payment_id ON ledger_postings(payment_id); CREATE INDEX idx_ledger_postings_account ON ledger_postings(account_number); CREATE INDEX idx_ledger_postings_status ON ledger_postings(status); -- ISO Messages CREATE TABLE IF NOT EXISTS iso_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id VARCHAR(100) UNIQUE NOT NULL, payment_id UUID NOT NULL REFERENCES payments(id), message_type VARCHAR(20) NOT NULL CHECK (message_type IN ('pacs.008', 'pacs.009')), uetr UUID NOT NULL, msg_id VARCHAR(100) NOT NULL, xml_content TEXT NOT NULL, xml_hash VARCHAR(64) NOT NULL, status VARCHAR(20) NOT NULL CHECK (status IN ('GENERATED', 'VALIDATED', 'TRANSMITTED', 'ACK_RECEIVED', 'NACK_RECEIVED')), transmitted_at TIMESTAMP WITH TIME ZONE, ack_received_at TIMESTAMP WITH TIME ZONE, nack_reason TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_iso_messages_message_id ON iso_messages(message_id); CREATE INDEX idx_iso_messages_payment_id ON iso_messages(payment_id); CREATE INDEX idx_iso_messages_uetr ON iso_messages(uetr); CREATE INDEX idx_iso_messages_status ON iso_messages(status); -- Transport Sessions (TLS Connections) CREATE TABLE IF NOT EXISTS transport_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id VARCHAR(100) UNIQUE NOT NULL, receiver_ip VARCHAR(45) NOT NULL, receiver_port INTEGER NOT NULL, tls_version VARCHAR(10), session_fingerprint VARCHAR(64), connected_at TIMESTAMP WITH TIME ZONE, disconnected_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_transport_sessions_session_id ON transport_sessions(session_id); CREATE INDEX idx_transport_sessions_connected_at ON transport_sessions(connected_at); -- ACK/NACK Logs CREATE TABLE IF NOT EXISTS ack_nack_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id UUID NOT NULL REFERENCES iso_messages(id), payment_id UUID NOT NULL REFERENCES payments(id), uetr UUID NOT NULL, msg_id VARCHAR(100) NOT NULL, type VARCHAR(4) NOT NULL CHECK (type IN ('ACK', 'NACK')), payload TEXT NOT NULL, reason TEXT, received_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_ack_nack_logs_message_id ON ack_nack_logs(message_id); CREATE INDEX idx_ack_nack_logs_payment_id ON ack_nack_logs(payment_id); CREATE INDEX idx_ack_nack_logs_uetr ON ack_nack_logs(uetr); CREATE INDEX idx_ack_nack_logs_received_at ON ack_nack_logs(received_at); -- Settlement Records CREATE TABLE IF NOT EXISTS settlement_records ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), payment_id UUID NOT NULL REFERENCES payments(id), uetr UUID NOT NULL, status VARCHAR(30) NOT NULL CHECK (status IN ('PENDING', 'ACK_RECEIVED', 'CREDIT_CONFIRMED', 'SETTLED', 'FAILED')), ack_received BOOLEAN DEFAULT FALSE, ack_received_at TIMESTAMP WITH TIME ZONE, credit_confirmed BOOLEAN DEFAULT FALSE, credit_confirmed_at TIMESTAMP WITH TIME ZONE, credit_confirmation_reference VARCHAR(100), settled_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_settlement_records_payment_id ON settlement_records(payment_id); CREATE INDEX idx_settlement_records_uetr ON settlement_records(uetr); CREATE INDEX idx_settlement_records_status ON settlement_records(status); -- Reconciliation Runs CREATE TABLE IF NOT EXISTS reconciliation_runs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), run_date DATE NOT NULL, run_timestamp TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, total_payments INTEGER DEFAULT 0, matched_payments INTEGER DEFAULT 0, unmatched_payments INTEGER DEFAULT 0, exceptions INTEGER DEFAULT 0, status VARCHAR(20) NOT NULL CHECK (status IN ('RUNNING', 'COMPLETED', 'FAILED')), completed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_reconciliation_runs_run_date ON reconciliation_runs(run_date); CREATE INDEX idx_reconciliation_runs_status ON reconciliation_runs(status); -- Audit Logs (Tamper-evident) CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_type VARCHAR(100) NOT NULL, entity_type VARCHAR(50), entity_id VARCHAR(100), operator_id VARCHAR(50), terminal_id VARCHAR(100), action VARCHAR(100) NOT NULL, details JSONB, checksum VARCHAR(64) NOT NULL, -- SHA-256 of previous row + current row timestamp TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_audit_logs_event_type ON audit_logs(event_type); CREATE INDEX idx_audit_logs_entity_type ON audit_logs(entity_type, entity_id); CREATE INDEX idx_audit_logs_operator_id ON audit_logs(operator_id); CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp); -- Export History CREATE TABLE IF NOT EXISTS export_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), format VARCHAR(20) NOT NULL CHECK (format IN ('rje', 'xmlv2', 'raw-iso', 'json')), scope VARCHAR(20) NOT NULL CHECK (scope IN ('messages', 'ledger', 'full')), record_count INTEGER NOT NULL DEFAULT 0, file_size BIGINT NOT NULL DEFAULT 0, filename VARCHAR(255) NOT NULL, start_date TIMESTAMP WITH TIME ZONE, end_date TIMESTAMP WITH TIME ZONE, account_number VARCHAR(100), uetr UUID, payment_id UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_export_history_format ON export_history(format); CREATE INDEX idx_export_history_scope ON export_history(scope); CREATE INDEX idx_export_history_created_at ON export_history(created_at); CREATE INDEX idx_export_history_uetr ON export_history(uetr); CREATE INDEX idx_export_history_payment_id ON export_history(payment_id); -- Function to update updated_at timestamp CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; -- Triggers for updated_at CREATE TRIGGER update_payments_updated_at BEFORE UPDATE ON payments FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_operators_updated_at BEFORE UPDATE ON operators FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_ledger_postings_updated_at BEFORE UPDATE ON ledger_postings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_iso_messages_updated_at BEFORE UPDATE ON iso_messages FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_settlement_records_updated_at BEFORE UPDATE ON settlement_records FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();