Signaler les doublons à l'ajout d'un livre, sans jamais les refuser

Rien n'empêchait de rescanner un livre déjà catalogué. Plutôt qu'un index
unique — qui interdirait le second exemplaire, cas parfaitement légitime —
la création répond 409 avec les fiches semblables, et « confirmerDoublon »
enregistre la même saisie.

Deux critères, dont aucun n'est une clé : ISBN identique, ou clé d'œuvre
et auteur communs. La clé d'œuvre étant un préfixe de TitreNormalise, SQL
dégrossit sur la colonne indexée et l'égalité exacte se vérifie ensuite en
mémoire.

Vérifié en exécution : avertissement, retour au formulaire intact, et ajout
confirmé créant bien un second exemplaire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-19 21:11:39 +02:00
co-authored by Claude Opus 5
parent 278d6579f7
commit 383ed43227
10 changed files with 600 additions and 69 deletions
+59
View File
@@ -835,6 +835,65 @@ livre sorti depuis six mois qu'on avait oublié, pas celui prêté hier.
(`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).
## Doublons du catalogue — avertir, jamais refuser (2026-08-19)
Défaut remonté en usage : rien n'empêchait de rescanner un livre déjà catalogué, et
`ServiceCatalogue.CreerAsync` ne faisait aucun contrôle.
**Décidé : un avertissement confirmable**, pas un index unique. `HasIndex(l => l.Isbn)` reste
délibérément **non unique**, et il ne faut pas le rendre unique « pour bien faire » :
- **posséder deux exemplaires est légitime** — on garde le sien et on prête l'autre. C'est
précisément le cas qu'un index unique rendrait impossible, sans recours ;
- **l'ISBN est facultatif** (livres anciens, tirages sans ISBN) : il ne peut pas porter à lui
seul l'unicité, et SQLite tient deux `NULL` pour distincts de toute façon.
⚠️ Le contraste avec la liste d'envies est voulu : `LivreSouhaite` **refuse** le doublon par
index unique. Une envie décrit une œuvre, dont on ne souhaite pas deux exemplaires ; un livre
décrit un **objet**, qu'on peut posséder en double. Ne pas uniformiser les deux.
### Deux critères, dont aucun n'est une clé
| Critère | Attrape | Rate, ou signale à tort |
|---|---|---|
| **ISBN identique** | le rescan du même livre, cas visé | rien quand l'ISBN manque |
| **`CleOeuvre` + un auteur commun** | la saisie manuelle sans ISBN, le sous-titre, l'ordre `Nom, Prénom` | le poche à côté du grand format — deux exemplaires bien réels |
L'auteur est **exigé en plus du titre** (au sens de `RapprochementAuteurs`) : sans lui, deux
recueils « Nouvelles » sans rapport se signaleraient l'un l'autre. Deux fiches **sans aucun**
auteur concordent, le titre étant alors tout ce dont on dispose.
⚠️ **La clé d'œuvre est un préfixe de `TitreNormalise`** — la troncature du sous-titre précède
la normalisation, qui préserve l'ordre des caractères. C'est ce qui permet de dégrossir en SQL
sur la colonne indexée (`== cle` ou `StartsWith(cle + " ")`) puis de vérifier l'égalité exacte
des clés en mémoire. Sans cette seconde passe, « Germinal les années noires » serait signalé
comme doublon de « Germinal » ; sans la première, il faudrait charger tout le catalogue à
chaque ajout.
### 409, et surtout pas 400
`POST /api/livres` répond **409 Conflict** avec `DoublonsLivre` (message + les fiches en cause),
et `?confirmerDoublon=true` enregistre la même saisie sans plus rien demander.
⚠️ **Un doublon n'est pas une erreur**, et le code de statut n'est pas un détail : la saisie est
valide, c'est le catalogue qui contient déjà quelque chose. D'où un **troisième état** dans
`ResultatEcriture` et dans `ResultatCreation` côté client, au lieu d'un message logé dans
`Erreur` — un texte rouge dirait « c'est raté » là où l'écran doit proposer « ajouter quand
même ». Le 409 traverse donc `EnvoyerAsync` sans passer par le traitement d'erreur générique,
qui l'aurait aplati en « L'enregistrement a échoué (409) ».
### L'écran montre les fiches, il ne les décrit pas
`AvertissementDoublon` remplace le formulaire tant que la question n'est pas tranchée (la saisie
attend dans `_saisie` et revient intacte si l'on renonce), et affiche chaque livre en cause
**en lien vers sa fiche** : la vraie question est « est-ce bien le même livre ? », et on n'y
répond qu'en regardant celui qui est déjà là. Le prêt en cours y figure — un exemplaire dehors
est justement une raison d'en vouloir un second.
**Ce qui n'est délibérément pas fait** : le contrôle ne porte que sur la **création**. Une
édition qui rendrait deux fiches identiques est un geste délibéré sur une fiche existante, pas
un scan répété par mégarde.
## Liste d'envies — implémentée le 2026-08-18
### Une table à part, et non un statut de plus