Files
mabibli/MaBibli.Shared/Entites/Serie.cs
T
mathieuandClaude Opus 5 4e372ab694 Ajouter les sagas et cycles, avec leurs tomes manquants
Première relation entre livres du projet. Une série contient des séries
(un cycle EST une série de séries) et des PLACES dans l'ordre de lecture —
pas des livres : une place sans livre est le trou qu'on vient voir.

L'ordre est stocké, jamais déduit d'une année : une préquelle se lit avant
le livre paru dix ans plus tôt. Les tomes non possédés se saisissent à la
main, aucune source ne donnant l'ordre de lecture d'une saga.

Le titre de la place survit à la suppression du livre (SetNull, pas de
cascade), sans quoi perdre un exemplaire effacerait le tome 3 de la série.

Les séries sont communes au foyer. Seule exception : la mise en envies d'un
tome manquant, qui écrit dans une liste personnelle — c'est le seul point
où les deux portées se rencontrent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:03:08 +02:00

110 lines
4.6 KiB
C#

using MaBibli.Shared.Textes;
namespace MaBibli.Shared.Entites;
/// <summary>
/// Un regroupement ordonné de livres : trilogie, série, cycle, intégrale.
/// </summary>
/// <remarks>
/// C'est la première notion du projet qui porte sur une <b>relation entre livres</b> plutôt que
/// sur un livre. Trois choses en découlent, et un simple champ texte « série » sur
/// <see cref="Livre"/> n'en couvrirait aucune.
/// <para>
/// <b>1. Deux niveaux, au moins.</b> <i>La Légende de Drizzt</i> regroupe plusieurs trilogies.
/// D'où <see cref="SerieParenteId"/>, une auto-référence : un cycle <b>est</b> une série qui
/// contient des séries. Un champ « cycle » séparé figerait la profondeur à deux et obligerait à
/// tout refaire au troisième niveau.
/// </para>
/// <para>
/// <b>2. L'ordre de lecture n'est pas l'ordre de publication.</b> <i>L'Elfe noir</i> est une
/// préquelle écrite après. C'est précisément l'information qu'on vient chercher : elle est
/// <b>stockée</b> (<see cref="ElementSerie.Position"/>), jamais déduite d'une année.
/// </para>
/// <para>
/// <b>3. La portée est COMMUNE au foyer</b>, comme le catalogue et les prêts, contrairement au
/// statut de lecture et à la liste d'envies. L'ordre de lecture d'une saga est une propriété de
/// l'œuvre : il ne change pas selon qui regarde. <see cref="AjoutePar"/> est donc une trace,
/// pas une frontière — <b>ne jamais filtrer dessus</b>, comme <see cref="Livre.AjoutePar"/>.
/// </para>
/// </remarks>
public class Serie
{
public int Id { get; set; }
public string Titre { get; set; } = string.Empty;
/// <summary>
/// Titre mis à plat. Porte un index <b>unique</b> : une série, une fiche.
/// </summary>
/// <remarks>
/// L'unicité est globale et non « par série parente », parce que
/// <see cref="SerieParenteId"/> est nullable et que SQLite tient deux <c>NULL</c> pour
/// distincts : une unicité incluant le parent laisserait passer autant de doublons qu'on
/// veut à la racine — exactement le piège documenté sur <c>LivreSouhaite.AuteurNormalise</c>.
/// </remarks>
public string TitreNormalise { get; set; } = string.Empty;
/// <summary>La série qui contient celle-ci, quand c'en est une partie d'un cycle.</summary>
public int? SerieParenteId { get; set; }
public Serie? SerieParente { get; set; }
public List<Serie> SousSeries { get; set; } = [];
/// <summary>Rang de cette série dans sa série parente. Sans objet à la racine.</summary>
public int Position { get; set; }
public List<ElementSerie> Elements { get; set; } = [];
public DateTime DateAjout { get; set; }
/// <summary>Qui a créé la série. <b>Trace, pas frontière</b> : la série est commune.</summary>
public string? AjoutePar { get; set; }
public void RecalculerFormes()
{
Titre = Titre.Trim();
TitreNormalise = NormalisationTexte.Normaliser(Titre);
}
}
/// <summary>
/// Une <b>position</b> dans une série : le tome n, qu'on le possède ou non.
/// </summary>
/// <remarks>
/// ⚠️ <b>Ce n'est pas « un livre de la série » mais une place dans l'ordre de lecture.</b> La
/// nuance est tout l'intérêt de l'écran : sans position sans livre, on ne pourrait pas montrer
/// les trous — « il vous manque le tome 3 » —, qui est la demande d'origine. Aucune source ne
/// donne l'ordre de lecture d'une saga (la BnF ne l'expose pas, et l'ordre de publication est
/// justement faux pour une préquelle) : les tomes absents n'existent que si on les saisit.
/// <para>
/// <see cref="Titre"/> est donc <b>toujours renseigné</b>, y compris quand
/// <see cref="LivreId"/> l'est : c'est ce qui permet à la suppression d'un livre de laisser un
/// trou nommé plutôt qu'une ligne muette. La clé étrangère est en <c>SetNull</c> pour cette
/// raison précise — supprimer un livre ne doit pas trouer la structure de la saga.
/// </para>
/// </remarks>
public class ElementSerie
{
public int Id { get; set; }
public int SerieId { get; set; }
public Serie? Serie { get; set; }
/// <summary>Rang dans l'ordre de <b>lecture</b>, à partir de 0.</summary>
public int Position { get; set; }
/// <summary>Le livre du catalogue, s'il est possédé. <c>null</c> = un trou.</summary>
public int? LivreId { get; set; }
public Livre? Livre { get; set; }
/// <summary>
/// Titre du tome. Renseigné même quand le livre est là, pour survivre à sa suppression.
/// </summary>
public string Titre { get; set; } = string.Empty;
public void RecalculerFormes() => Titre = Titre.Trim();
}