Files
mabibli/MaBibli.Client/Services/FiltreLivresLocal.cs
T
mathieuandClaude Opus 5 49eb8e1c98 Distinguer romans et BD par un type de document
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>
2026-08-19 21:45:37 +02:00

77 lines
3.1 KiB
C#

using MaBibli.Shared.Catalogue;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Textes;
namespace MaBibli.Client.Services;
/// <summary>
/// Recherche, filtrage et tri du catalogue <b>hors-ligne</b>, appliqués à l'instantané complet
/// rangé en IndexedDB.
/// </summary>
/// <remarks>
/// C'est le pendant navigateur de <see cref="FiltreLivres"/>, qui s'exécute côté base. Les deux
/// ne peuvent pas être le même code : le serveur travaille sur des entités aux colonnes
/// normalisées déjà calculées, le client sur des <see cref="LivreDto"/> qui n'en portent pas.
/// La normalisation est donc refaite ici à la volée — sans coût perceptible sur une bibliothèque
/// de foyer, et surtout par <b>les mêmes fonctions</b> (<see cref="NormalisationTexte"/>,
/// <see cref="RapprochementAuteurs.Cle"/>), ce qui est la seule garantie que « emile » trouve
/// « Émile » des deux côtés.
/// <para>
/// Un test compare les deux implémentations sur les mêmes données : une divergence silencieuse
/// donnerait une bibliothèque qui ne se cherche pas pareil selon qu'on a du réseau ou non.
/// </para>
/// <para>
/// Aucun filtre sur <c>AjoutePar</c>, ici comme côté serveur : la bibliothèque est commune.
/// </para>
/// </remarks>
public static class FiltreLivresLocal
{
public static IReadOnlyList<LivreDto> Appliquer(
IEnumerable<LivreDto> source, CritereLivres criteres)
{
var livres = source;
if (criteres.Format is { } format)
{
livres = livres.Where(l => l.Format == format);
}
if (criteres.TypeDocument is { } type)
{
livres = livres.Where(l => l.TypeDocument == type);
}
if (criteres.Statut is { } statut)
{
// LivreDto.Statut est déjà celui de l'utilisateur courant : le serveur l'a résolu
// avant de l'envoyer. Le client n'a donc personne à désigner, et ne le pourrait pas.
livres = livres.Where(l => l.Statut == statut);
}
if (criteres.AuteurId is { } auteurId)
{
livres = livres.Where(l => l.Auteurs.Any(a => a.Id == auteurId));
}
var terme = NormalisationTexte.Normaliser(criteres.Recherche);
if (terme.Length > 0)
{
var cle = RapprochementAuteurs.Cle(criteres.Recherche);
livres = livres.Where(l =>
NormalisationTexte.Normaliser(l.Titre).Contains(terme, StringComparison.Ordinal)
|| l.Auteurs.Any(a =>
NormalisationTexte.Normaliser(a.Nom).Contains(terme, StringComparison.Ordinal)
|| RapprochementAuteurs.Cle(a.Nom).Contains(cle, StringComparison.Ordinal)));
}
// Même ordre que le serveur : titre normalisé, puis identifiant pour rendre le tri
// déterministe. La comparaison est ordinale parce que c'est ce que fait SQLite sur une
// colonne sans collation — un tri « intelligent » ici ferait diverger les deux vues.
return livres
.OrderBy(l => NormalisationTexte.Normaliser(l.Titre), StringComparer.Ordinal)
.ThenBy(l => l.Id)
.ToList();
}
}