Files
dbis_core-lite/src/database/schema.sql
2026-02-09 21:51:45 -08:00

240 lines
10 KiB
PL/PgSQL

-- 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();