Format disait physique ou numérique, jamais roman ou BD. Trois valeurs seulement : une valeur s'ajoute sans migration, la renommer une fois posée sur des centaines de fiches, non. Le défaut est « non précisé », pas « roman » : les fiches existantes n'ont pas été saisies avec cette question en tête, et un AddColumn suffit justement parce que la valeur par défaut ne prétend rien. L'interface n'affiche donc que ce qui a été choisi, comme pour le format. Filtre ajouté aux DEUX implémentations, serveur et hors-ligne, avec quatre jeux de critères de plus dans le test qui les confronte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.7 KiB
C#
89 lines
3.7 KiB
C#
using MaBibli.Shared.Entites;
|
|
using MaBibli.Shared.Textes;
|
|
|
|
namespace MaBibli.Shared.Catalogue;
|
|
|
|
/// <summary>
|
|
/// Filtrage, recherche et tri du catalogue.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Écrit sur <see cref="IQueryable{T}"/> pour être exécuté par EF Core côté base <b>et</b>
|
|
/// testable en mémoire sur une simple liste, sans base ni réseau.
|
|
/// <para>
|
|
/// La recherche porte sur les colonnes <b>normalisées</b> (<see cref="Livre.TitreNormalise"/>,
|
|
/// <see cref="Auteur.NomNormalise"/>), pas sur les colonnes d'affichage : SQLite ne sait pas
|
|
/// ignorer les accents, et sa collation <c>NOCASE</c> 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 ».
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class FiltreLivres
|
|
{
|
|
/// <param name="utilisateur">
|
|
/// Personne dont on regarde le statut. Sert <b>uniquement</b> à
|
|
/// <see cref="CritereLivres.Statut"/> : il ne restreint jamais la liste elle-même.
|
|
/// </param>
|
|
public static IQueryable<Livre> Appliquer(
|
|
IQueryable<Livre> 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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tri par titre, puis par identifiant pour rendre l'ordre déterministe.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// <para>
|
|
/// Le tri porte sur le titre normalisé : sinon « Œuvres » et « Éducation » atterrissent
|
|
/// après « Zola » dans l'ordre des points de code.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static IQueryable<Livre> Trier(IQueryable<Livre> source) =>
|
|
source.OrderBy(l => l.TitreNormalise).ThenBy(l => l.Id);
|
|
}
|