TP bank
This commit is contained in:
@@ -341,19 +341,19 @@ CREATE TABLE emplois (
|
||||
*/
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- Banque
|
||||
-- Banque (Bank)
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
create schema banque;
|
||||
create schema bank;
|
||||
|
||||
-- Générateur de numéro aléatoire
|
||||
-- ----------------------------------------------------------------------
|
||||
CREATE OR REPLACE FUNCTION banque.rand_account(n integer)
|
||||
CREATE OR REPLACE FUNCTION bank.rand_account(n integer)
|
||||
RETURNS text AS $$
|
||||
DECLARE
|
||||
chars text := '0123456789ABCDEFGHJKLMNPRSTUWXYZ';
|
||||
chars text := '1234ABCD';
|
||||
out text := '';
|
||||
b bytea := gen_random_bytes(n); -- n octets aléatoires
|
||||
b bytea := ext.gen_random_bytes(n); -- n octets aléatoires
|
||||
i int;
|
||||
idx int;
|
||||
BEGIN
|
||||
@@ -372,7 +372,7 @@ $$ LANGUAGE plpgsql;
|
||||
|
||||
-- Devises (Currencies)
|
||||
-- ----------------------------------------------------------------------
|
||||
create table banque.currency (
|
||||
create table bank.currency (
|
||||
code text not null,
|
||||
num4217 integer default null,
|
||||
symbole character varying(5) default null,
|
||||
@@ -382,7 +382,7 @@ create table banque.currency (
|
||||
minors text default null
|
||||
);
|
||||
|
||||
alter table banque.currency
|
||||
alter table bank.currency
|
||||
add check (code ~ '^[A-Z]{3}$');
|
||||
|
||||
create table pays_devises (
|
||||
@@ -398,13 +398,13 @@ alter table pays_devises
|
||||
add check (devise_code ~ '^[A-Z]{3}$');
|
||||
|
||||
create unique index currency_pk
|
||||
on banque.currency
|
||||
on bank.currency
|
||||
using btree (code);
|
||||
|
||||
alter table banque.currency
|
||||
alter table bank.currency
|
||||
add primary key using index currency_pk;
|
||||
|
||||
\copy banque.currency from '/tmp/banque/devises.csv' (FORMAT CSV, header, delimiter ',', ENCODING 'UTF8');
|
||||
\copy bank.currency from '/tmp/banque/devises.csv' (FORMAT CSV, header, delimiter ',', ENCODING 'UTF8');
|
||||
\copy pays_devises from '/tmp/banque/devises_pays.csv' (FORMAT CSV, header, delimiter ',', ENCODING 'UTF8');
|
||||
|
||||
-- pays_devises -> pays
|
||||
@@ -415,13 +415,13 @@ alter table only pays_devises
|
||||
-- pays_devises -> devises
|
||||
alter table only pays_devises
|
||||
add foreign key (devise_code)
|
||||
references banque.currency (code);
|
||||
references bank.currency (code);
|
||||
|
||||
-- Taux de change ()
|
||||
-- ----------------------------------------------------------------------
|
||||
CREATE TABLE banque.exchange_rate (
|
||||
from_currency CHAR(3) references banque.currency(code),
|
||||
to_currency CHAR(3) references banque.currency(code),
|
||||
CREATE TABLE bank.exchange_rate (
|
||||
from_currency CHAR(3) references bank.currency(code),
|
||||
to_currency CHAR(3) references bank.currency(code),
|
||||
rate DECIMAL(12,6) NOT NULL,
|
||||
fee_percent DECIMAL(5,2) DEFAULT 0,
|
||||
last_updated TIMESTAMP DEFAULT NOW(),
|
||||
@@ -440,7 +440,7 @@ DECLARE
|
||||
BEGIN
|
||||
-- Liste des devises à importer
|
||||
FOR rec IN
|
||||
SELECT code FROM banque.currency WHERE code <> 'EUR'
|
||||
SELECT code FROM bank.currency WHERE code <> 'EUR'
|
||||
LOOP
|
||||
path := format('/tmp/webstat/Webstat_Export_fr_EXR.M.%s.EUR.SP00.E.csv', rec.code);
|
||||
|
||||
@@ -455,7 +455,7 @@ BEGIN
|
||||
-- Insertion dans la table principale
|
||||
EXECUTE format(
|
||||
$sql$
|
||||
INSERT INTO banque.exchange_rate (from_currency, to_currency, rate, fee_percent, last_updated)
|
||||
INSERT INTO bank.exchange_rate (from_currency, to_currency, rate, fee_percent, last_updated)
|
||||
SELECT 'EUR', %L, rate, 0, jour FROM exchange
|
||||
$sql$,
|
||||
rec.code
|
||||
@@ -472,7 +472,7 @@ DROP table exchange;
|
||||
-- Titulaires
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE banque.titulaire (
|
||||
CREATE TABLE bank.titulaire (
|
||||
id bigint primary key generated always as identity,
|
||||
type_titulaire TEXT CHECK (type_titulaire IN ('individu', 'société')) NOT NULL,
|
||||
created_at timestamp with time zone not null default now()
|
||||
@@ -484,7 +484,7 @@ DECLARE
|
||||
new_titulaire_id INTEGER;
|
||||
BEGIN
|
||||
IF NEW.id IS NULL THEN
|
||||
INSERT INTO banque.titulaire (type_titulaire) VALUES ('individu')
|
||||
INSERT INTO bank.titulaire (type_titulaire) VALUES ('individu')
|
||||
RETURNING id INTO new_titulaire_id;
|
||||
NEW.id := new_titulaire_id;
|
||||
END IF;
|
||||
@@ -503,7 +503,7 @@ DECLARE
|
||||
new_titulaire_id INTEGER;
|
||||
BEGIN
|
||||
IF NEW.id IS NULL THEN
|
||||
INSERT INTO banque.titulaire (type_titulaire) VALUES ('société')
|
||||
INSERT INTO bank.titulaire (type_titulaire) VALUES ('société')
|
||||
RETURNING id INTO new_titulaire_id;
|
||||
NEW.id := new_titulaire_id;
|
||||
END IF;
|
||||
@@ -520,7 +520,7 @@ EXECUTE FUNCTION auto_titulaire_morale();
|
||||
-- Comptes (Accounts)
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
create table banque.account (
|
||||
create table bank.account (
|
||||
id bigint primary key generated always as identity,
|
||||
account_number text unique not null,
|
||||
balance numeric(18,6) not null default 0,
|
||||
@@ -528,32 +528,74 @@ create table banque.account (
|
||||
created_at timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
create table banque.account_holders (
|
||||
account_id bigint NOT NULL REFERENCES banque.account(id) ON DELETE CASCADE,
|
||||
titulaire_id int NOT NULL REFERENCES banque.titulaire(id) ON DELETE CASCADE,
|
||||
create table bank.account_holders (
|
||||
account_id bigint NOT NULL REFERENCES bank.account(id) ON DELETE CASCADE,
|
||||
titulaire_id int NOT NULL REFERENCES bank.titulaire(id) ON DELETE CASCADE,
|
||||
share numeric(5,2) CHECK (share >= 0 AND share <= 100),
|
||||
role text DEFAULT 'Titulaire',
|
||||
PRIMARY KEY (account_id, titulaire_id)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION bank.insert_account_random(
|
||||
person_ids int[], -- liste d'identifiants de personnes
|
||||
currency text default 'EUR', -- la devise du compte
|
||||
n int DEFAULT 2 -- longueur du numéro de compte
|
||||
)
|
||||
RETURNS text AS $$
|
||||
DECLARE
|
||||
candidate text;
|
||||
retry_count int := 0;
|
||||
new_account_id bigint;
|
||||
person_id int;
|
||||
BEGIN
|
||||
IF array_length(person_ids, 1) IS NULL THEN
|
||||
RAISE EXCEPTION 'La liste des personnes ne peut pas être vide';
|
||||
END IF;
|
||||
|
||||
LOOP
|
||||
candidate := bank.rand_account(n);
|
||||
BEGIN
|
||||
INSERT INTO bank.account(account_number, currency) VALUES (candidate, currency)
|
||||
RETURNING id INTO new_account_id;
|
||||
|
||||
CREATE TABLE banque."transaction" (
|
||||
-- Lier chaque personne au compte
|
||||
FOREACH person_id IN ARRAY person_ids LOOP
|
||||
INSERT INTO bank.account_holders(account_id, titulaire_id, share)
|
||||
VALUES (new_account_id, person_id, (100.0 / array_length(person_ids, 1)));
|
||||
END LOOP;
|
||||
|
||||
RETURN candidate;
|
||||
|
||||
EXCEPTION WHEN unique_violation THEN
|
||||
retry_count := retry_count + 1;
|
||||
IF retry_count > 20 THEN
|
||||
RAISE EXCEPTION 'Trop de collisions après % tentatives', retry_count;
|
||||
END IF;
|
||||
CONTINUE;
|
||||
END;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Transactions
|
||||
-- ----------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE bank."transaction" (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||
reference TEXT,
|
||||
amount NUMERIC(18,6) NOT NULL,
|
||||
currency CHAR(3) NOT NULL,
|
||||
from_account BIGINT NOT NULL REFERENCES banque.account(id),
|
||||
to_account BIGINT NOT NULL REFERENCES banque.account(id),
|
||||
from_account BIGINT NOT NULL REFERENCES bank.account(id),
|
||||
to_account BIGINT NOT NULL REFERENCES bank.account(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
processed BOOLEAN NOT NULL DEFAULT FALSE -- indique si ledger + soldes ont été appliqués
|
||||
);
|
||||
|
||||
-- ledger (écritures comptables immuables) : append-only
|
||||
CREATE TABLE banque.ledger_entry (
|
||||
CREATE TABLE bank.ledger_entry (
|
||||
id bigint primary key generated always as identity,
|
||||
transaction_id UUID NOT NULL REFERENCES banque."transaction"(id),
|
||||
account_id BIGINT NOT NULL REFERENCES banque.account(id),
|
||||
transaction_id UUID NOT NULL REFERENCES bank."transaction"(id),
|
||||
account_id BIGINT NOT NULL REFERENCES bank.account(id),
|
||||
amount NUMERIC(18,6) NOT NULL, -- convention: positif = crédit, négatif = débit (ici from = -amount, to = +amount)
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
description TEXT
|
||||
@@ -561,10 +603,10 @@ CREATE TABLE banque.ledger_entry (
|
||||
|
||||
-- index pour performance et idempotence par transaction/account
|
||||
CREATE UNIQUE INDEX ux_ledger_tx_account
|
||||
ON banque.ledger_entry(transaction_id, account_id);
|
||||
ON bank.ledger_entry(transaction_id, account_id);
|
||||
|
||||
-- outbox pour publisher reliable (pattern outbox)
|
||||
CREATE TABLE banque.outbox_event (
|
||||
CREATE TABLE bank.outbox_event (
|
||||
id bigint primary key generated always as identity,
|
||||
occurrenced_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
topic TEXT NOT NULL,
|
||||
@@ -575,7 +617,7 @@ CREATE TABLE banque.outbox_event (
|
||||
);
|
||||
|
||||
-- table very simple de blockchain / chain d'audit
|
||||
CREATE TABLE banque.block_chain (
|
||||
CREATE TABLE bank.block_chain (
|
||||
id bigint primary key generated always as identity,
|
||||
block_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
tx_id UUID NOT NULL, -- transaction incluse dans ce bloc (ou multiple selon choix)
|
||||
@@ -584,7 +626,7 @@ CREATE TABLE banque.block_chain (
|
||||
block_data JSONB NOT NULL -- stockage lisible des éléments du bloc (pour audit)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_block_chain_txid ON banque.block_chain(tx_id);
|
||||
CREATE INDEX idx_block_chain_txid ON bank.block_chain(tx_id);
|
||||
|
||||
CREATE OR REPLACE FUNCTION perform_transaction(
|
||||
from_account_id INT,
|
||||
@@ -603,13 +645,13 @@ DECLARE
|
||||
prev_hash TEXT;
|
||||
new_hash TEXT;
|
||||
BEGIN
|
||||
SELECT currency_code INTO from_currency FROM banque.account WHERE id = from_account_id;
|
||||
SELECT currency_code INTO to_currency FROM banque.account WHERE id = to_account_id;
|
||||
SELECT currency_code INTO from_currency FROM bank.account WHERE id = from_account_id;
|
||||
SELECT currency_code INTO to_currency FROM bank.account WHERE id = to_account_id;
|
||||
|
||||
SELECT hash INTO prev_hash FROM banque.transaction ORDER BY id DESC LIMIT 1;
|
||||
SELECT hash INTO prev_hash FROM bank.transaction ORDER BY id DESC LIMIT 1;
|
||||
|
||||
-- Création de la transaction principale
|
||||
INSERT INTO banque.transaction (description, previous_hash)
|
||||
INSERT INTO bank.transaction (description, previous_hash)
|
||||
VALUES (description, prev_hash)
|
||||
RETURNING id INTO tx_id;
|
||||
|
||||
@@ -619,27 +661,27 @@ BEGIN
|
||||
converted_amount := amount;
|
||||
ELSE
|
||||
SELECT rate, fee_percent INTO rate, fee
|
||||
FROM banque.exchange_rate
|
||||
FROM bank.exchange_rate
|
||||
WHERE from_currency = from_currency AND to_currency = to_currency ORDER BY last_updated desc LIMIT 1;
|
||||
|
||||
converted_amount := amount * rate * (1 - fee / 100);
|
||||
END IF;
|
||||
|
||||
-- Débit
|
||||
INSERT INTO banque.ledger_entry (transaction_id, account_id, amount, currency_code, entry_type, rate_to_base, converted_amount)
|
||||
INSERT INTO bank.ledger_entry (transaction_id, account_id, amount, currency_code, entry_type, rate_to_base, converted_amount)
|
||||
VALUES (tx_id, from_account_id, -amount, from_currency, 'debit', rate, amount * rate);
|
||||
|
||||
-- Crédit
|
||||
INSERT INTO banque.ledger_entry (transaction_id, account_id, amount, currency_code, entry_type, rate_to_base, converted_amount)
|
||||
INSERT INTO bank.ledger_entry (transaction_id, account_id, amount, currency_code, entry_type, rate_to_base, converted_amount)
|
||||
VALUES (tx_id, to_account_id, converted_amount, to_currency, 'credit', rate, converted_amount);
|
||||
|
||||
-- Mise à jour des soldes
|
||||
UPDATE banque.account SET balance = balance - amount WHERE id = from_account_id;
|
||||
UPDATE banque.account SET balance = balance + converted_amount WHERE id = to_account_id;
|
||||
UPDATE bank.account SET balance = balance - amount WHERE id = from_account_id;
|
||||
UPDATE bank.account SET balance = balance + converted_amount WHERE id = to_account_id;
|
||||
|
||||
-- Génération du hash blockchain
|
||||
SELECT encode(digest(concat(tx_id, description, prev_hash, NOW()::text), 'sha256'), 'hex') INTO new_hash;
|
||||
UPDATE banque.transaction SET hash = new_hash WHERE id = tx_id;
|
||||
UPDATE bank.transaction SET hash = new_hash WHERE id = tx_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
@@ -660,7 +702,7 @@ END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER tr_notify_transaction
|
||||
AFTER INSERT ON banque.transaction
|
||||
AFTER INSERT ON bank.transaction
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_transaction();
|
||||
|
||||
|
||||
@@ -18,3 +18,10 @@ truncate table fournisseur;
|
||||
|
||||
\COPY personne(prenom, nom, telephone, ville) FROM '/tmp/personne1.csv' (FORMAT CSV, header, ENCODING 'UTF8');
|
||||
\COPY societe(societe) FROM '/tmp/societe1.csv' (FORMAT CSV, header, ENCODING 'UTF8');
|
||||
|
||||
SELECT bank.insert_account_random(ARRAY[1]);
|
||||
SELECT bank.insert_account_random(ARRAY[2]);
|
||||
SELECT bank.insert_account_random(ARRAY[3]);
|
||||
SELECT bank.insert_account_random(ARRAY[4]);
|
||||
SELECT bank.insert_account_random(ARRAY[5],'USD');
|
||||
SELECT bank.insert_account_random(ARRAY[6,11]);
|
||||
|
||||
Reference in New Issue
Block a user