From 5854267f45083479e787c42129dbde04b092c11a Mon Sep 17 00:00:00 2001 From: medina5 Date: Sat, 1 Nov 2025 14:29:19 +0100 Subject: [PATCH] banque --- postgresql-entrypoint-initdb.d/01_initdb.sql | 288 +++++++++++++++++-- 1 file changed, 270 insertions(+), 18 deletions(-) diff --git a/postgresql-entrypoint-initdb.d/01_initdb.sql b/postgresql-entrypoint-initdb.d/01_initdb.sql index 02eafe5..facfa65 100644 --- a/postgresql-entrypoint-initdb.d/01_initdb.sql +++ b/postgresql-entrypoint-initdb.d/01_initdb.sql @@ -361,46 +361,132 @@ CREATE TABLE emplois ( FOREIGN KEY (id_personne) REFERENCES personnes(id_personne), FOREIGN KEY (id_societe) REFERENCES societe(id) ); +*/ -insert into emplois values +-- ---------------------------------------------------------------------- +-- Banque +-- ---------------------------------------------------------------------- +create schema banque; -CREATE TABLE account ( - id bigint generated always as identity, +-- Générateur de numéro aléatoire +-- ---------------------------------------------------------------------- +CREATE OR REPLACE FUNCTION banque.rand_account(n integer) +RETURNS text AS $$ +DECLARE + chars text := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + out text := ''; + b bytea := gen_random_bytes(n); -- n octets aléatoires + i int; + idx int; +BEGIN + IF n <= 0 THEN + RAISE EXCEPTION 'La longueur doit être > 0'; + END IF; + + FOR i IN 0..(n - 1) LOOP + idx := (get_byte(b, i) % length(chars)) + 1; + out := out || substr(chars, idx, 1); + END LOOP; + + RETURN out; +END; +$$ LANGUAGE plpgsql; + +-- Devises (currencies) +-- ---------------------------------------------------------------------- +create table currency ( + code text not null, + num4217 integer default null, + symbole character varying(5) default null, + nom text default null, + format text default null, + division integer default 0, + minor text default null, + minors text default null +); + +alter table currency + add check (code ~ '^[A-Z]{3}$'); + +create table pays_devises ( + pays_code text not null, + devise_code text not null, + valide daterange default null +); + +alter table pays_devises + add check (pays_code ~ '^[A-Z]{2}$'); + +alter table pays_devises + add check (devise_code ~ '^[A-Z]{3}$'); + +create unique index currency_pk + on currency + using btree (code); + +alter table currency + add primary key using index currency_pk; + +\copy currency from '/tmp/geo/devises.csv' (FORMAT CSV, header, delimiter ',', ENCODING 'UTF8'); +\copy pays_devises from '/tmp/geo/devises_pays.csv' (FORMAT CSV, header, delimiter ',', ENCODING 'UTF8'); + +-- pays_devises -> pays +alter table only pays_devises + add foreign key (pays_code) + references geo.pays (code2); + +-- pays_devises -> devises +alter table only pays_devises + add foreign key (devise_code) + references currency (code); + +CREATE TABLE banque.exchange_rate ( + from_currency CHAR(3) references currency(code), + to_currency CHAR(3) references currency(code), + rate DECIMAL(12,6) NOT NULL, + fee_percent DECIMAL(5,2) DEFAULT 0, -- frais en % + last_updated TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (from_currency, to_currency) +); +-- ---------------------------------------------------------------------- + +CREATE TABLE banque.account ( + id bigint primary key generated always as identity, account_number TEXT UNIQUE NOT NULL, balance NUMERIC(18,2) NOT NULL DEFAULT 0, currency CHAR(3) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() ); -CREATE TABLE "transaction" ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), +CREATE TABLE banque."transaction" ( + id UUID PRIMARY KEY DEFAULT uuidv7(), reference TEXT, amount NUMERIC(18,2) NOT NULL, currency CHAR(3) NOT NULL, - from_account BIGINT NOT NULL REFERENCES account(id), - to_account BIGINT NOT NULL REFERENCES account(id), + from_account BIGINT NOT NULL REFERENCES banque.account(id), + to_account BIGINT NOT NULL REFERENCES banque.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 ledger_entry ( - id bigint generated always as identity, - transaction_id UUID NOT NULL REFERENCES "transaction"(id), - account_id BIGINT NOT NULL REFERENCES account(id), +CREATE TABLE banque.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), amount NUMERIC(18,2) 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 ); -- index pour performance et idempotence par transaction/account -CREATE UNIQUE INDEX ux_ledger_tx_account ON ledger_entry(transaction_id, account_id); - +CREATE UNIQUE INDEX ux_ledger_tx_account + ON banque.ledger_entry(transaction_id, account_id); -- outbox pour publisher reliable (pattern outbox) -CREATE TABLE outbox_event ( - id bigint generated always as identity, +CREATE TABLE banque.outbox_event ( + id bigint primary key generated always as identity, occurrenced_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), topic TEXT NOT NULL, payload JSONB NOT NULL, @@ -410,12 +496,178 @@ CREATE TABLE outbox_event ( ); -- table very simple de blockchain / chain d'audit -CREATE TABLE block_chain ( - id bigint generated always as identity, +CREATE TABLE banque.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) previous_hash TEXT NULL, block_hash TEXT NOT NULL, block_data JSONB NOT NULL -- stockage lisible des éléments du bloc (pour audit) ); -CREATE INDEX idx_block_chain_txid ON block_chain(tx_id); + +CREATE INDEX idx_block_chain_txid ON banque.block_chain(tx_id); + +CREATE OR REPLACE FUNCTION perform_transaction( + from_account_id INT, + to_account_id INT, + amount DECIMAL(18,2), + description TEXT +) RETURNS VOID AS $$ +DECLARE + from_currency CHAR(3); + to_currency CHAR(3); + rate DECIMAL(12,6); + fee DECIMAL(18,2); + base_amount DECIMAL(18,2); + converted_amount DECIMAL(18,2); + tx_id INT; + 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 hash INTO prev_hash FROM banque.transaction ORDER BY id DESC LIMIT 1; + + -- Création de la transaction principale + INSERT INTO banque.transaction (description, previous_hash) + VALUES (description, prev_hash) + RETURNING id INTO tx_id; + + IF from_currency = to_currency THEN + rate := 1; + fee := 0; + converted_amount := amount; + ELSE + SELECT rate, fee_percent INTO rate, fee + FROM banque.exchange_rate + WHERE from_currency = from_currency AND to_currency = to_currency; + + 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) + 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) + 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 bansue.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; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION notify_transaction() +RETURNS TRIGGER AS $$ +DECLARE + payload JSON; +BEGIN + payload := json_build_object( + 'transaction_id', NEW.id, + 'description', NEW.description, + 'timestamp', NEW.timestamp, + 'hash', NEW.hash + ); + PERFORM pg_notify('transactions', payload::text); -- canal PostgreSQL NOTIFY + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER tr_notify_transaction +AFTER INSERT ON banque.transaction +FOR EACH ROW +EXECUTE FUNCTION notify_transaction(); + +-- ---------------------------------------------------------------------- +-- Business Intelligence +-- ---------------------------------------------------------------------- + +create schema business; + +-- Chronologie +create table business.chronologie as + with recursive calendrier as ( + select + '2010-01-01 00:00:00'::timestamp as jour + union all + select + jour + interval '1 day' + from calendrier + where jour + interval '1 day' <= '2026-12-31' + ) + select + extract(epoch from jour) / 86400::int as jj, + jour, + extract (year from jour) as annee, + extract (month from jour) as mois, + extract (day from jour) as jmois, + extract (week from jour) as semaine, + extract (dow from jour) as jsemaine, + extract (doy from jour) as jannee, + floor((extract(month from jour) - 1) / 6) + 1 as semestre, + floor((extract(month from jour) - 1) / 4) + 1 as quadrimestre, + extract(quarter from jour)::int as trimestre, + floor((extract(month from jour) - 1) / 2) + 1 as bimestre, + extract (day from jour) / extract (day from (date_trunc('month', '2025-03-16'::date) + interval '1 month' - interval '1 day')) as frac_mois, + extract (doy from jour) / extract (doy from (extract (year from jour)||'-12-31')::date) as frac_annee + from calendrier; + +comment on column business.chronologie.jj + is 'jour julien'; + +-- ---------------------------------------------------------------------- +-- Musique +-- ---------------------------------------------------------------------- + +-- ---------------------------------------------------------------------- +-- Biblio +-- ---------------------------------------------------------------------- + +create schema biblio; + +CREATE TABLE biblio.genres ( + genre_id int primary key, + genre text +); + +INSERT INTO biblio.genres (genre_id, genre) VALUES + (1,'Science-Fiction'), + (2,'Fantasy'), + (3,'Young adult'), + (4,'Bit-lit'), + (5,'Policier'), + (6,'Romance'), + (7,'Espionnage'), + (8,'Aventure'), + (9,'Fantastique'), + (10,'Historique'), + (11,'Noir'), + (12,'Biographie'), + (13,'Cyberpunk'), + (14,'Steampunk'); + +create table biblio.auteurs ( + auteur_id integer not null, + nom text not null, + "references" text[] +); + +create table biblio.editeurs ( + editeur_id integer not null, + editeur_nom text not null, + ville text +); + +create table biblio.oeuvres ( + oeuvre_id integer not null, + titre text not null, + infos jsonb + --constraint fk_oeuvre_genre foreign key (genre_id) references genres (genre_id) +);