Initial commit: add .gitignore and README
This commit is contained in:
239
src/database/schema.sql
Normal file
239
src/database/schema.sql
Normal file
@@ -0,0 +1,239 @@
|
||||
-- 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();
|
||||
Reference in New Issue
Block a user