Auteurs cherchables et triables, filtre de prêt, rang dans les exports, couvertures d'envies
Auteurs - Recherche et tri (par nom, ou du plus présent au moins présent), en mémoire : la liste est déjà chargée en entier — c'est elle qui sert l'instantané hors-ligne — et la filtrer côté serveur aurait rendu la page inutilisable sans réseau. - Trois colonnes dont une seule est élastique : compteur à gauche sur une largeur figée, nom au milieu, actions à droite. Les compteurs et les boutons s'alignent alors d'une ligne à l'autre au lieu de suivre la longueur du nom. Catalogue - Filtre « À la maison / Prêtés », posé dans les deux implémentations (serveur et hors-ligne) et dans le test qui les confronte, avec un cas de prêt CLOS — c'est exactement là que les deux auraient pu diverger en silence. - La ligne disparaît tant que rien n'est prêté, comme celles des formats et des types. Envies - ⚠️ Les couvertures manquaient parce que seul l'écran « ajouter une envie » remplissait CoverUrl : la bibliographie et les tomes manquants transmettent un ISBN mais aucune image. Le repli est posé à la LECTURE, donc il vaut aussi pour les envies déjà enregistrées, sans migration. - Les exports .txt et .csv portent le rang d'envie. Le .txt en a le plus besoin : son groupement par auteur détruit précisément cet ordre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ public static class LivresEndpoints
|
||||
TypeDocument? typeDocument,
|
||||
Statut? statut,
|
||||
int? auteurId,
|
||||
bool? prete,
|
||||
IServiceCatalogue service,
|
||||
IFournisseurUtilisateur utilisateurs,
|
||||
CancellationToken ct) =>
|
||||
@@ -42,13 +43,14 @@ public static class LivresEndpoints
|
||||
TypeDocument = typeDocument,
|
||||
Statut = statut,
|
||||
AuteurId = auteurId,
|
||||
Prete = prete,
|
||||
};
|
||||
|
||||
var utilisateur = utilisateurs.Obtenir().Identifiant;
|
||||
return Results.Ok(await service.ListerAsync(criteres, utilisateur, ct));
|
||||
})
|
||||
.WithName("ListerLivres")
|
||||
.WithSummary("Liste le catalogue, filtrable par format, type de document, statut et auteur ; cherchable sur titre et auteurs.")
|
||||
.WithSummary("Liste le catalogue, filtrable par format, type de document, statut, auteur et prêt en cours ; cherchable sur titre et auteurs.")
|
||||
.WithDescription(
|
||||
"Catalogue unique : physiques et numériques cohabitent, distingués par leur format. "
|
||||
+ "La bibliothèque est commune — la liste n'est jamais restreinte à l'utilisateur courant. "
|
||||
|
||||
@@ -84,6 +84,13 @@ public static class ExportSouhaits
|
||||
|
||||
sortie.Append('\n').Append(souhaits.Count).Append(souhaits.Count > 1 ? " livres" : " livre").Append('\n');
|
||||
|
||||
// ⚠️ Le groupement par auteur DÉTRUIT l'ordre d'envie : c'est le prix, assumé, d'une
|
||||
// liste rangée comme une librairie. Le rang est donc réinscrit devant chaque titre,
|
||||
// faute de quoi le fichier emporté ne dit plus par quoi commencer — précisément
|
||||
// l'information que l'écran de réordonnancement sert à établir.
|
||||
var rangs = Rangs(souhaits);
|
||||
sortie.Append("(le chiffre entre crochets est votre ordre d'envie, 1 = le plus désiré)\n");
|
||||
|
||||
// « Auteur inconnu » est rejeté en fin de liste : ce sont les entrées les moins
|
||||
// exploitables en rayon, elles ne doivent pas ouvrir le fichier.
|
||||
var groupes = souhaits
|
||||
@@ -97,7 +104,7 @@ public static class ExportSouhaits
|
||||
|
||||
foreach (var souhait in groupe.OrderBy(s => s.Titre, StringComparer.CurrentCultureIgnoreCase))
|
||||
{
|
||||
sortie.Append(" - ").Append(souhait.Titre);
|
||||
sortie.Append(" - [").Append(rangs[souhait.Id]).Append("] ").Append(souhait.Titre);
|
||||
|
||||
// Éditeur, année et ISBN sur la même ligne, entre parenthèses : c'est ce qu'on
|
||||
// épelle à un libraire, et ça reste lisible sur un écran de téléphone.
|
||||
@@ -127,12 +134,18 @@ public static class ExportSouhaits
|
||||
{
|
||||
var sortie = new StringBuilder();
|
||||
|
||||
Ligne(sortie, "Titre", "Auteur", "Éditeur", "Année", "ISBN", "Note", "Ajouté le");
|
||||
// Le rang ouvre le tableau : c'est la colonne sur laquelle on trie dans un tableur, et
|
||||
// celle qui répond à « par quoi commence-t-on ? » quand on se répartit des achats.
|
||||
// Le CSV suit déjà cet ordre, mais un ordre implicite se perd au premier tri par titre.
|
||||
Ligne(sortie, "Rang", "Titre", "Auteur", "Éditeur", "Année", "ISBN", "Note", "Ajouté le");
|
||||
|
||||
var rangs = Rangs(souhaits);
|
||||
|
||||
foreach (var souhait in souhaits)
|
||||
{
|
||||
Ligne(
|
||||
sortie,
|
||||
rangs[souhait.Id].ToString(CultureInfo.InvariantCulture),
|
||||
souhait.Titre,
|
||||
souhait.Auteur,
|
||||
souhait.Editeur,
|
||||
@@ -145,6 +158,18 @@ public static class ExportSouhaits
|
||||
return sortie.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rang affiché de chaque envie, à partir de 1.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Déduit de la <b>position dans la liste reçue</b>, et non lu sur une propriété du DTO :
|
||||
/// <c>ListerAsync</c> trie déjà par <c>Rang</c>, et la colonne en base peut comporter des
|
||||
/// trous (une suppression ne renumérote pas). Ce qu'on veut imprimer est « troisième de ma
|
||||
/// liste », pas la valeur brute stockée.
|
||||
/// </remarks>
|
||||
private static Dictionary<int, int> Rangs(IReadOnlyList<SouhaitDto> souhaits) =>
|
||||
souhaits.Select((s, i) => (s.Id, Rang: i + 1)).ToDictionary(x => x.Id, x => x.Rang);
|
||||
|
||||
/// <summary>
|
||||
/// ISBN découpé pour la ligne texte : c'est un numéro qu'on <b>épelle à un libraire</b>,
|
||||
/// et les tranches sont ce qui permet de ne pas se perdre au milieu de treize chiffres.
|
||||
|
||||
@@ -330,7 +330,21 @@ public sealed class ServiceSouhaits(MaBibliDbContext db) : IServiceSouhaits
|
||||
Editeur = souhait.Editeur,
|
||||
Annee = souhait.Annee,
|
||||
Isbn = souhait.Isbn,
|
||||
CoverUrl = souhait.CoverUrl,
|
||||
|
||||
// ⚠️ La couverture se déduit de l'ISBN quand elle n'a pas été fournie.
|
||||
//
|
||||
// Constaté en usage : aucune vignette dans la liste d'envies. La cause n'était pas le
|
||||
// composant Couverture mais le chemin d'ajout — seul l'écran « ajouter une envie »
|
||||
// remplissait CoverUrl, alors que la plupart des envies arrivent par la bibliographie
|
||||
// d'un auteur ou par un tome manquant d'une série, qui transmettent bien un ISBN mais
|
||||
// aucune image.
|
||||
//
|
||||
// Le repli est posé ICI, à la lecture, et non à l'écriture : il vaut alors aussi pour
|
||||
// les envies DÉJÀ enregistrées, sans migration ni rattrapage. La règle « sans ISBN,
|
||||
// pas de couverture, et on n'en invente pas » est intacte — on n'invente rien, on
|
||||
// applique la formule OpenLibrary habituelle à un ISBN qu'on possède déjà.
|
||||
CoverUrl = souhait.CoverUrl
|
||||
?? (souhait.Isbn is null ? null : IsbnUtils.UrlCouverture(souhait.Isbn)),
|
||||
Note = souhait.Note,
|
||||
DateAjout = souhait.DateAjout,
|
||||
Possede = livreId is not null,
|
||||
|
||||
Reference in New Issue
Block a user