diff --git a/MaBibli.Client/Pages/AjoutSouhait.razor b/MaBibli.Client/Pages/AjoutSouhait.razor
index 915873b..d815bdf 100644
--- a/MaBibli.Client/Pages/AjoutSouhait.razor
+++ b/MaBibli.Client/Pages/AjoutSouhait.razor
@@ -76,9 +76,32 @@
title="@MotifBlocage" @onclick="ChercherParIsbnAsync">
@(_recherche ? "Recherche…" : "Chercher")
+
+ @*
+ J2 : le cas d'usage est direct — le livre est en main, en librairie. C'est le même
+ composant de scan que l'écran d'ajout au catalogue, sans création de Livre au bout :
+ un souhait n'est pas une possession.
+ *@
+
}
+@if (_scan)
+{
+
+}
+
+@* L1 : la BnF met environ une seconde à répondre. Sans indicateur, l'écran ne dit rien et
+ l'on reclique — donc on relance l'appel. *@
+@if (_recherche)
+{
+
+}
+
@if (_erreur is not null)
{
@_erreur
@@ -196,6 +219,9 @@
private TriResultats _tri = TriResultats.Date;
private bool _recherche;
private bool _ajout;
+
+ /// Le scanner est ouvert. Il ne l'est que depuis la voie « par ISBN ».
+ private bool _scan;
private string? _erreur;
protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
@@ -239,8 +265,37 @@
}
}
+ private void OuvrirScanner()
+ {
+ _erreur = null;
+ _candidats = [];
+ _avertissements = [];
+ _scan = true;
+ }
+
+ ///
+ /// Enchaîne la recherche sur le code lu, sans bouton à confirmer.
+ ///
+ ///
+ /// Le code reste dans le champ : un décodage erroné se corrige et se relance. C'est la
+ /// décision déjà actée pour l'écran d'ajout au catalogue, et la seule cohérente avec une
+ /// douchette, qui envoie Entrée d'elle-même.
+ ///
+ /// ⚠️ L'add-on EAN-2 (numéro de parution) est ignoré ici, et ce n'est pas un oubli : il ne
+ /// concerne que les revues, qui ne se souhaitent pas — cet écran alimente une liste
+ /// d'œuvres. Un code de périodique est d'ailleurs intercepté plus bas et nommé.
+ ///
+ ///
+ private async Task SurCodeDetecteAsync(CodeScanne lecture)
+ {
+ _scan = false;
+ _isbn = lecture.Code;
+ await ChercherParIsbnAsync();
+ }
+
private void Basculer(Mode mode)
{
+ _scan = false;
_mode = mode;
_focusAFaire = true;
_erreur = null;
diff --git a/MaBibli.Client/Pages/Serie.razor b/MaBibli.Client/Pages/Serie.razor
index d2d7cce..ac7a7c2 100644
--- a/MaBibli.Client/Pages/Serie.razor
+++ b/MaBibli.Client/Pages/Serie.razor
@@ -226,6 +226,34 @@ else
title="@MotifBlocage" @onclick="ChercherLookupAsync">
@(_lookupEnCours ? "Recherche…" : "Préremplir")
+
+ @*
+ J1 : le scanner s'ouvre ICI, dans la place visée, et non sur
+ l'écran d'ajout.
+
+ ⚠️ C'est ce qui répond à l'exigence « le retour doit ramener
+ sur la série ET sur la place visée » : il n'y a pas de
+ retour, on n'a jamais quitté la place. Passer par /ajout
+ obligeait à revenir rattacher à la main — donc à risquer de
+ rattacher le mauvais tome.
+ *@
+
+
+ @if (_lookupScan)
+ {
+
+ }
+
+ @* L1 : la cascade BnF puis OpenLibrary prend une seconde ou deux. *@
+ @if (_lookupEnCours)
+ {
+
+ }
@if (_lookupCandidats.Count > 0)
{
@foreach (var candidat in _lookupCandidats)
@@ -288,8 +316,57 @@ else
@onclick="() => OuvrirRattachement(AjoutDepuisCatalogue)">
Depuis le catalogue
+
+ @*
+ Lot P — le chemin inverse de « place vide → envie ».
+
+ ⚠️ Ce geste n'ÉPARGNE QU'UNE RESAISIE : il crée une place ordinaire, LivreId à
+ NULL, titre repris de l'envie. Rien ne relie les deux en base, et c'est délibéré —
+ les séries sont COMMUNES au foyer, la liste d'envies est PERSONNELLE, et son sens
+ même est de préparer un cadeau sans que l'autre le voie venir. Une clé étrangère
+ vers l'envie afficherait « tome 3 souhaité par untel » à tout le monde.
+ *@
+
+ @if (_enviesOuvertes)
+ {
+
+ Votre liste d'envies est vide — ou ne contient rien qui ne soit déjà un tome
+ de cette série.
+
+ }
+ else
+ {
+
+ Le tome est ajouté comme manquant, avec le titre de l'envie.
+ L'envie reste dans votre liste : rien n'a été acheté, et
+ personne d'autre ne verra qu'elle est à vous.
+
@@ -419,6 +496,13 @@ else
private IReadOnlyList _lookupCandidats = [];
private bool _lookupEnCours;
+ /// Le scanner est ouvert sur la place visée par le lookup ISBN (lot J1).
+ private bool _lookupScan;
+
+ private bool _enviesOuvertes;
+ private bool _enviesChargement;
+ private IReadOnlyList _envies = [];
+
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
private IReadOnlySet CheminRacine => new HashSet { Id };
@@ -616,11 +700,93 @@ else
}
}
+ private void OuvrirScanTome()
+ {
+ _erreur = null;
+ _lookupCandidats = [];
+ _lookupScan = true;
+ }
+
+ ///
+ /// Enchaîne le lookup sur le code lu, sans quitter la place visée.
+ ///
+ ///
+ /// Le code reste dans le champ : un décodage erroné se corrige et se relance, comme sur
+ /// l'écran d'ajout au catalogue.
+ ///
+ /// ⚠️ Un code de périodique (977) est refusé plus bas par ChercherLookupAsync :
+ /// ElementSerie.LivreId ne pointe que vers Livre, et les revues restent
+ /// délibérément hors de ce flux.
+ ///
+ ///
+ private async Task SurCodeTomeAsync(CodeScanne lecture)
+ {
+ _lookupScan = false;
+ _lookupIsbn = lecture.Code;
+ await ChercherLookupAsync();
+ }
+
+ ///
+ /// Ouvre la liste des envies dans laquelle puiser un tome à recenser.
+ ///
+ ///
+ /// ⚠️ Les envies déjà présentes dans la série sont écartées de la liste : les proposer
+ /// mènerait droit au doublon, la place étant identifiée par son seul titre.
+ ///
+ private async Task OuvrirEnviesAsync()
+ {
+ _erreur = null;
+ _message = null;
+ _enviesOuvertes = true;
+ _enviesChargement = true;
+
+ var toutes = await Api.ListerSouhaitsAsync();
+
+ var deja = new HashSet(
+ _serie?.Elements.Select(e => NormalisationTexte.Normaliser(e.Titre)) ?? [],
+ StringComparer.Ordinal);
+
+ _envies = toutes
+ .Where(envie => !deja.Contains(NormalisationTexte.Normaliser(envie.Titre)))
+ .ToList();
+
+ _enviesChargement = false;
+ }
+
+ ///
+ /// Recense un tome à partir d'une envie, sans rien lier.
+ ///
+ ///
+ /// La place créée est celle qu'on aurait saisie à la main : LivreId à NULL,
+ /// titre repris de l'envie. L'envie survit — c'est la règle déjà tenue par « une
+ /// envie déjà au catalogue est signalée, jamais supprimée », et à plus forte raison ici
+ /// puisque rien n'a été acheté.
+ ///
+ /// ⚠️ Ce qui doit rester invisible au foyer n'est pas l'existence du tome — on l'aurait
+ /// saisie à la main de toute façon — mais le lien avec la liste de quelqu'un. Il n'y
+ /// en a aucun en base.
+ ///
+ ///
+ private async Task AjouterDepuisEnvieAsync(SouhaitDto envie)
+ {
+ await EcrireAsync(() => Api.AjouterElementSerieAsync(
+ Id, new AjoutElementSerie { Titre = envie.Titre }));
+
+ if (_erreur is null)
+ {
+ _enviesOuvertes = false;
+ _envies = [];
+ _message = $"« {envie.Titre} » est recensé comme tome manquant. "
+ + "Il reste dans votre liste d'envies.";
+ }
+ }
+
private void OuvrirLookup(ElementSerieDto element)
{
_lookupElement = element;
_lookupIsbn = string.Empty;
_lookupCandidats = [];
+ _lookupScan = false;
_erreur = null;
}