# Modélisation d'un système bancaire

Une banque locale souhaite informatiser la gestion de ses titulaires et de leurs comptes.

Vous êtes chargé(e) de concevoir et d’implémenter le schéma relationnel de base permettant de gérer :

1. Les clients de la banque,
2. Les comptes bancaires,
3. Le lien entre les titulaires et les comptes.
4. Les dépots et les retraits d'argent.
5. Les virements entre compte.
6. Les devises et les taux de change.


L'objectif de ces travaux pratiques est de :

- Concevoir un **modèle relationnel** à partir d’un scénario réaliste.
- Utiliser les **contraintes d’intégrité** pour garantir la cohérence des données.
- Manipuler des **jointures** et des **relations n–n**.
- Comprendre la notion d’**héritage logique** en base de données.

La diffusion de cette application est internationale, vous vous efforcerez d'utiliser des termes anglais pour nommer les entités et les propriétés. Réferrez pour cela vous au [glossaire](banque.glossaire.md)

Pour les entités vous utiliserez le singuler et écrirez le tout en minuscule.

## 1. Les titulaires

Un clients de la banque est appelé un titulaire. Il peut être une personne physique ou une entreprise.

### 1.1 Exemple de données

- une personne nommée _Françoise Zanetti_, née le 12 avril 1995.
- une entreprise nommée _Boulangerie de Valorgue_, créée le 19 août 2014, numéro d’immatriculation FR19803269968.

### 1.2 Analyse

- Quelles informations faut-il conserver pour tous les titulaires ?
- Quelles informations sont spécifiques à chaque type de titulaire ?
- Comment représenter cette distinction en base relationnelle ?

> [!TIP]
> Indice : on peut utiliser une table abstraite `holder`, puis des tables `person` et `company` qui héritent logiquement de celle-ci.

### 1.3 Contraintes à respecter

- Chaque `person` ou `company` doit correspondre à exactement un seul `holder`.
- La suppression d’un `holder` doit supprimer automatiquement la ligne correspondante dans `person` ou `company`.
- Le type doit être contraint à `'PERSON'` ou `'COMPANY'`.

### 1.4 Vérifications

- Lister tous les titulaires. Pour réutiliser rapidement la requête enregistrer la dans une vue.
- Supprimer un titulaire, vérifier que cela supprime l'individu ou la société correspondante.

### 1.5 Pour aller plus loin

La banque souhaite désormais que toute personne titulaire d’un compte ait au moins 15 ans à la date de création de sa fiche. Il n'y a pas de restriction sur l'âge de la société.

### 1.6 Conclusion

#### Pourquoi séparer `person` et `company` ?

Parce que leurs attributs diffèrent (nom/prénom vs raison sociale).
Cela évite les **colonnes inutiles** et permet des **contraintes spécifiques** à chaque type.

#### Quelle contrainte empêche d’insérer une person sans holder ?

La clé étrangère `references holder(id)` dans `person`.

```mermaid
erDiagram
    person {
      bigint id PK
      text firstname
      text lastname
      date birthdate
    }

    company {
      bigint id PK
      text registration_numer
      date creation_date
    }

    holder {
      bigint id PK
      int person_id FK
    }

  %% Relations

  person  ||--|| holder :
  company ||--|| holder :
```

## 2. Les comptes

- Chaque titulaire peut détenir un ou plusieurs compte.
- Un compte bancaire doit pouvoir appartenir à un ou plusieurs titulaires (compte individuel / compte joint).
- Chaque compte dispose d’un numéro de compte (_account number_) unique, d’un solde (_balance_) et d'une date d'ouverture.
- Le solde des comptes ne peuvent être négatifs.
- Dans le cas d'un compte joint, les parts (_share_) de propriété d'un compte doivent pouvoir être précisées.

### 2.1 Exemple de données

- Créez un compte individuel pour Françoise Zanetti.
- Ajouter un nouveau titulaire : Justin Hébrard né le 11/03/1993.
- Créez un compte joint à 50/50 pour Françoise et Justin.

## 3. L'intégrité des données

Lorsque l’on tente d'insèrer une nouvelle personne qui n'a pas l'âge requis. La ligne dans `holder` est d'abord créée, puis l'insertion dans `person` échoue à cause de la vérification d'âge. Mais la ligne du titulaire est toujours présente **sans être rattachée** à une personne. On parle alors d'enregistrement **orphelin**.

Chaque commande SQL est exécutée indépendamment. Si la deuxième commande échoue, la première n’est pas annulée automatiquement.

Réalisez l’insertion d’un titulaire complet (dans holder et person) à l’aide d’une **transaction**.
Testez le cas où la contrainte d’âge échoue et vérifiez que rien n’est inséré dans holder.

```sql
begin;
...
commit;
```

```mermaid
erDiagram
  person {
    bigint id PK
    text firstname
    text lastname
    date birthdate
  }

  company {
    bigint id PK
    text registration_numer
    date creation_date
  }

  bank {
    bigint id PK
  }

  holder {
    bigint id PK
    date creation_date
  }

  account {
    bigint id PK
    date creation_date
    decimal balance
    text currency_code FK
  }

  account_holder {
    bigint account_id FK
    bigint holder_id FK
    decimal share
  }

  transaction {
    bigint id PK
    date transaction_date
    decimal amount
  }

  currency {
    text code PK
  }

  currency {
    text code PK
  }

  exchange_rate {
    date date PK
    text currency_code PK/FK
    decimal rate
  }

  transaction {
    bigint id PK
    date date
    decimal amount
  }

  operation {
    bigint id PK
    bigint transaction_id
    text source_account
    text target_account
    decimal amount
    text direction
  }

  %% Relations

  person  ||--|| holder :
  company ||--|| holder :
  bank    ||--|| holder :
  holder  }|--|{ account_holder :
  account_holder }|--|{ account :
  currency ||--|{ account :
  exchange_rate }o--|| currency :
  transaction }o--|| operation :
  operation }o--|| account :
  operation }o--|| account :
```

## 4. Procédure stockée

Pour fiabiliser le process et être sûr que l'execution s'effectue toujours dans une transaction,
nous allons encapsuler la création d’un titulaire dans une **procédure stockée**.

#### Créez une **procédure stockée** appelée `create_person_holder`.

Cette procédure prend en paramètre :

  * le prénom (`p_firstname text`)
  * le nom (`p_lastname text`)
  * la date de naissance (`p_birthdate date`)

Cette procédure doit :

  * Vérifier que la personne a **au moins 15 ans** ;
  * Créer automatiquement un enregistrement dans `holder` de type `'PERSON'`;
  * Récupérer l’identifiant généré et créer la fiche dans `person` ;
  * Afficher un message de confirmation.

3. Si la personne a moins de 15 ans :

  * La procédure doit **refuser la création** et afficher une erreur claire.

En PL/pgSQL les **procédures** s’écrivent  :

```sql
create or replace procedure nom_procedure(paramètres)
language plpgsql
as $$
declare
    -- variables locales
begin
    -- instructions SQL
end;
$$;
```

Pour afficher un message de réussite :

```sql
raise notice 'message %', variable;
```

Vous pouvez déclencher une erreur métier à l’aide de :

```sql
raise exception 'message';
```

Pour appeler la procédure stockée

```sql
call create_person_holder('Félicien', 'Hébrard', '1970-10-15');
```

>[!NOTE]
> La procédure garantit l’**atomicité** : soit tout est créé, soit rien.

## 5. Cohérence des données

Écrire une requête pour vérifier la somme des parts

Jusqu’à présent, nous savons créer des titulaires (`holder`) de façon sûre.
Il est temps de leur ouvrir des **comptes bancaires**.

Un compte doit toujours :

* être associé à **au moins un titulaire**,
* et la somme des parts (`share`) des titulaires doit être exactement **égale à 1** (c’est-à-dire 100 % du compte).

Nous allons donc écrire une **procédure stockée** `create_account` qui vérifie ces règles avant d’enregistrer les données.

1. Créez une **procédure stockée** `create_account` qui :

  * prend en paramètre :

    * `p_iban text`,
    * `p_name text`,
    * `p_holders int[]` (tableau des identifiants de titulaires),
    * `p_shares numeric[]` (tableau des parts correspondantes).

  * vérifie que :

    * le nombre d’éléments dans `p_holders` et `p_shares` est identique ;
    * la somme des parts est **exactement égale à 1** ;
    * chaque `holder_id` existe dans la table `holder`.

2. Si tout est correct :

  * crée un nouveau compte dans `account` ;
  * insère les lignes correspondantes dans `account_holder`.

3. Si une condition échoue :

  * la procédure doit lever une **erreur explicite** (`RAISE EXCEPTION`) ;
  * aucune insertion ne doit être faite (transaction annulée).

* Les tableaux peuvent être parcourus avec une boucle :

```sql
for i in 1..array_length(p_holders, 1) loop
  ...
end loop;
```

* Pour vérifier la somme :

```sql
select sum(unnest(p_shares));
```

  (ou additionner dans la boucle)

* Vous pouvez lever une erreur personnalisée :

```sql
raise exception 'La somme des parts doit être égale à 1 (%.4f)', somme;
```

### Exemples d’appel

```sql
call create_account(
    'FR761234567890',
    'Compte commun',
    array[1, 5],
    array[0.5, 0.5]
);
```

Crée un compte partagé 50/50 entre les titulaires 1 et 2.


```sql
call create_account(
    'FR009999999999',
    'Compte déséquilibré',
    array[1, 5],
    array[0.7, 0.4]
);
```

Doit refuser la création avec une erreur claire :

```
ERROR:  La somme des parts (1.1000) doit être égale à 1.0000
```

## La double écriture comptable

Jusqu’à présent, les dépôts et retraits modifiaient directement le solde d’un compte.
Mais dans un vrai système bancaire ou comptable, chaque opération financière doit être enregistrée en double :

➕ Un débit sur un compte (celui qui reçoit)
➖ Un crédit sur un autre (celui qui cède)

La somme des débits doit toujours être égale à la somme des crédits.
