Fusionner la gestion des prets
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ Application self-hosted de gestion de bibliothèque personnelle, à héberger su
|
||||
| Auth / multi-utilisateur | SSO YunoHost via **en-têtes SSOwat** (`YNH_USER`) | Pas de login custom. OIDC **écarté** : non documenté par YunoHost (vérifié le 2026-08-17). Voir « Intégration SSO » |
|
||||
| Portée des données | **Collection commune** à tous les utilisateurs, avec traçabilité de qui a ajouté chaque livre | Usage familial : une bibliothèque de foyer, pas des collections étanches. Laisse la possibilité de cloisonner plus tard sans migration lourde |
|
||||
| Statut de lecture | **Par utilisateur**, pas commun (décidé le 2026-08-17, après la phase 3) | Le livre est commun, sa lecture est personnelle : deux membres du foyer lisent le même exemplaire à des rythmes différents. Sort `Statut` de `Livre` vers une table dédiée |
|
||||
| Prêts | **Communs au foyer**, jamais filtrés par utilisateur ; un seul prêt ouvert par livre, garanti par index unique partiel | Un livre absent l'est pour tout le monde, et n'importe qui doit pouvoir noter son retour. Voir « Prêts » |
|
||||
| Auteurs | **Table dédiée** avec nom normalisé, remplaçant le champ texte libre | Nécessaire au regroupement par auteur et à la recherche insensible aux accents. Regroupement **automatique seulement quand c'est sûr**, sinon proposé à l'utilisateur |
|
||||
| Ebooks | **Fiches uniquement**, pas de stockage de fichiers | Inventaire, pas hébergement. Évite l'espace disque YunoHost, les sauvegardes lourdes, et garde le cache hors-ligne léger |
|
||||
| Architecture serveur | **x86_64** → publish `linux-x64` | Serveur PC/VPS confirmé par l'utilisateur |
|
||||
@@ -360,10 +361,12 @@ RapprochementRefuse (mémoire des « non » de l'utilisateur)
|
||||
|
||||
Pret
|
||||
├── Id
|
||||
├── LivreId (FK vers Livre)
|
||||
├── Emprunteur (nom, texte libre)
|
||||
├── DatePret
|
||||
└── DateRetour (nullable — NULL tant que non rendu)
|
||||
├── LivreId (FK Livre, cascade — index simple pour l'historique)
|
||||
├── Emprunteur (nom, texte libre)
|
||||
├── DatePret (UTC)
|
||||
├── DateRetour (UTC, nullable — NULL tant que non rendu)
|
||||
└── UNIQUE (LivreId) WHERE DateRetour IS NULL
|
||||
un seul prêt ouvert par livre ; les prêts clos se répètent librement
|
||||
```
|
||||
|
||||
Garder `Pret` comme table séparée (pas un champ sur `Livre`) pour conserver l'historique complet des prêts passés, pas juste l'état actuel.
|
||||
@@ -396,6 +399,72 @@ La migration recopie l'ancien contenu avec les moyens du bord, puis **`ServiceRe
|
||||
|
||||
**Les prêts ne concernent en pratique que les livres physiques** — les ebooks étant de simples fiches, il n'y a pas d'objet à prêter. `Emprunteur` reste un **texte libre**, sans lien avec les comptes YunoHost : on suit les prêts à des personnes extérieures au foyer, pas les échanges entre utilisateurs de l'app.
|
||||
|
||||
## Prêts — implémenté le 2026-08-18
|
||||
|
||||
### Un prêt ouvert par livre, garanti par la base
|
||||
|
||||
Un exemplaire sorti ne se prête pas une seconde fois. Le service le vérifie et renvoie un message
|
||||
nommant celui qui l'a déjà (« Ce livre est déjà prêté à Marie »), mais entre cette vérification et
|
||||
l'insertion il reste une fenêtre. Elle est fermée par un **index unique partiel** :
|
||||
|
||||
```sql
|
||||
CREATE UNIQUE INDEX IX_Prets_LivreId_EnCours ON Prets (LivreId) WHERE DateRetour IS NULL;
|
||||
```
|
||||
|
||||
Le filtre est ce qui rend la chose possible : seules les lignes ouvertes sont indexées, donc
|
||||
l'historique reste libre d'accumuler autant de prêts clos que nécessaire sur le même livre.
|
||||
Vérifié sur base réelle : un second `INSERT` ouvert échoue en `UNIQUE constraint failed`, un
|
||||
second prêt **clos** passe.
|
||||
|
||||
⚠️ Deux index cohabitent sur `Prets.LivreId` et ce n'est pas une redondance : `IX_Prets_LivreId`
|
||||
sert l'historique (toutes les lignes), l'index partiel sert la contrainte. **Ils doivent porter un
|
||||
nom explicite** — EF Core identifie un index par ses colonnes, et sans nom distinct le second
|
||||
déclaré *remplace* purement et simplement le premier dans la migration générée.
|
||||
|
||||
### Le prêt est commun, contrairement au statut de lecture
|
||||
|
||||
Symétrique inverse de la décision sur les statuts, et il faut tenir les deux :
|
||||
|
||||
| | Statut de lecture | Prêt |
|
||||
|---|---|---|
|
||||
| Portée | **personnel** (par `YNH_USER`) | **commun au foyer** |
|
||||
| Pourquoi | deux personnes lisent le même exemplaire à leur rythme | un livre absent l'est pour tout le monde |
|
||||
|
||||
Aucun point d'entrée des prêts ne reçoit d'identité — le service n'en prend même pas en
|
||||
paramètre, pour qu'on ne puisse pas s'en servir par inadvertance. N'importe qui doit pouvoir
|
||||
noter le retour d'un livre qu'il a récupéré.
|
||||
|
||||
`LivreDto.PreteA` / `PreteDepuis` portent l'état courant pour l'étiquette du catalogue ;
|
||||
l'historique complet se demande à part (`GET /api/livres/{id}/prets`) pour ne pas alourdir chaque
|
||||
liste.
|
||||
|
||||
### Points d'entrée
|
||||
|
||||
| Méthode | Route | Rôle |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/prets/en-cours` | Ce qui n'est pas à la maison, **du prêt le plus ancien au plus récent** |
|
||||
| `POST` | `/api/livres/{id}/prets` | Prêter (400 si déjà sorti, numérique, ou emprunteur vide) |
|
||||
| `GET` | `/api/livres/{id}/prets` | Historique, du plus récent au plus ancien (404 si livre inconnu, `[]` si jamais prêté) |
|
||||
| `POST` | `/api/prets/{id}/retour` | Clore le prêt (400 s'il l'est déjà) |
|
||||
|
||||
L'ordre croissant des prêts en cours est délibéré : ce qu'on cherche dans cette vue, c'est le
|
||||
livre sorti depuis six mois qu'on avait oublié, pas celui prêté hier.
|
||||
|
||||
### Décisions prises là où CLAUDE.md était muet
|
||||
|
||||
- **Supprimer un livre emporte son historique de prêts** (cascade, déjà déclarée, désormais
|
||||
couverte par un test). Un historique orphelin — « quelqu'un a emprunté quelque chose » — ne se
|
||||
lit plus. L'écran de suppression avertit en plus quand le livre est actuellement dehors.
|
||||
- **La date de prêt est modifiable, la date de retour non.** On note souvent un prêt après coup
|
||||
(« Paul a mon Zola depuis Noël ») ; un retour se constate au moment où il a lieu. Les deux
|
||||
refusent une date future, et un retour antérieur à son prêt.
|
||||
- **Rendre un prêt déjà clos est refusé** plutôt que silencieusement ignoré : réécrire la date
|
||||
remplacerait une information exacte par une approximative.
|
||||
- **Toutes les dates sont stockées en UTC**, comme `DateAjout`. Le `<input type="date">` produit
|
||||
une date sans fuseau : le client la déclare **locale** avant de la convertir
|
||||
(`ServiceLivresApi.EnUtc`). Sans cela le prêt se décalerait d'un jour pour la moitié du globe.
|
||||
Vérifié en exécution : saisie du 12/08 → `2026-08-11 22:00` en base (CEST).
|
||||
|
||||
## Historique du projet (pourquoi ces choix)
|
||||
|
||||
L'utilisateur a testé deux solutions existantes avant de se lancer dans un projet custom :
|
||||
|
||||
Reference in New Issue
Block a user