using MaBibli.Shared.Entites; using MaBibli.Shared.Textes; namespace MaBibli.Shared.Catalogue; /// /// Filtrage, recherche et tri du catalogue. /// /// /// Écrit sur pour être exécuté par EF Core côté base et /// testable en mémoire sur une simple liste, sans base ni réseau. /// /// La recherche porte sur les colonnes normalisées (, /// ), pas sur les colonnes d'affichage : SQLite ne sait pas /// ignorer les accents, et sa collation NOCASE s'arrête à l'ASCII. Le terme cherché passe /// par la même normalisation que la valeur stockée — c'est ce qui fait que « emile » trouve /// « Émile ». /// /// public static class FiltreLivres { /// /// Personne dont on regarde le statut. Sert uniquement à /// : il ne restreint jamais la liste elle-même. /// public static IQueryable Appliquer( IQueryable source, CritereLivres criteres, string? utilisateur) { // AUCUN filtre sur AjoutePar : la bibliothèque est commune (CLAUDE.md). // Ce commentaire est là pour qu'une régression future saute aux yeux en relecture. if (criteres.Format is { } format) { source = source.Where(l => l.Format == format); } if (criteres.TypeDocument is { } type) { source = source.Where(l => l.TypeDocument == type); } if (criteres.Statut is { } statut) { // Le statut vit dans une table par personne : on interroge la ligne de l'appelant, // et lui seulement. Sans utilisateur identifié, personne n'a de statut : la sélection // est vide, ce qui vaut mieux que de montrer celui d'un autre. source = utilisateur is null ? source.Where(_ => false) : source.Where(l => l.Statuts.Any(s => s.Utilisateur == utilisateur && s.Statut == statut)); } if (criteres.AuteurId is { } auteurId) { source = source.Where(l => l.Auteurs.Any(la => la.AuteurId == auteurId)); } if (criteres.Prete is { } prete) { // Un prêt EN COURS est une ligne sans date de retour : l'historique clos ne compte // pas. Aucune identité n'intervient — le prêt est commun au foyer, contrairement au // statut de lecture juste au-dessus. source = prete ? source.Where(l => l.Prets.Any(p => p.DateRetour == null)) : source.Where(l => !l.Prets.Any(p => p.DateRetour == null)); } var terme = NormalisationTexte.Normaliser(criteres.Recherche); if (terme.Length > 0) { // Le terme est confronté aux deux formes stockées de chaque auteur : le nom dans // l'ordre de saisie, et la clé aux mots triés. La seconde est ce qui fait que // « zola émile » trouve « Émile Zola » — le terme est trié de la même façon. var cle = RapprochementAuteurs.Cle(criteres.Recherche); source = source.Where(l => l.TitreNormalise.Contains(terme) || l.Auteurs.Any(la => la.Auteur!.NomNormalise.Contains(terme) || la.Auteur.CleRegroupement.Contains(cle))); } return Trier(source); } /// /// Tri par titre, puis par identifiant pour rendre l'ordre déterministe. /// /// /// CLAUDE.md ne tranche pas le tri. L'ordre alphabétique a été retenu parce qu'une /// bibliothèque se parcourt par titre ; l'ordre d'ajout n'a d'intérêt que le jour de la saisie. /// /// Le tri porte sur le titre normalisé : sinon « Œuvres » et « Éducation » atterrissent /// après « Zola » dans l'ordre des points de code. /// /// public static IQueryable Trier(IQueryable source) => source.OrderBy(l => l.TitreNormalise).ThenBy(l => l.Id); }