Files
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

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

108 lines
4.9 KiB
C#

using MaBibli.Shared.Isbn;
using MaBibli.Shared.Textes;
namespace MaBibli.Shared.Entites;
/// <summary>
/// Une revue — ou un numéro précis — que l'on aimerait acquérir. <b>Personnelle</b>, comme
/// <see cref="LivreSouhaite"/>.
/// </summary>
/// <remarks>
/// <b>Pourquoi une table sœur plutôt que des colonnes de plus sur <see cref="LivreSouhaite"/> ?</b>
/// Une envie de livre est identifiée par un couple (œuvre, auteur) : c'est ce que porte son index
/// unique, et c'est ce qui permet de la rapprocher du catalogue. Un numéro de revue n'a
/// <b>pas d'auteur</b> et se distingue par son <i>numéro</i> — deux notions que la clé de
/// <c>LivreSouhaite</c> ne sait pas exprimer sans devenir fausse pour tout le monde. Y loger les
/// revues aurait obligé à écrire « et qui n'est pas une revue » à chaque rapprochement, à chaque
/// export et à chaque compteur : c'est exactement le raisonnement qui a séparé
/// <see cref="Revue"/> de <see cref="Livre"/>.
/// <para>
/// ⚠️ <b>Le coût est assumé et il est réel</b> : deux tables se fusionnent à l'affichage, dans
/// les deux exports et dans l'instantané hors-ligne. C'est là que le sujet se rate s'il est
/// bâclé — une envie de revue invisible en librairie ne sert à rien.
/// </para>
/// <para>
/// <see cref="Utilisateur"/> est une <b>frontière</b>, pas une trace : aucun point d'entrée
/// n'accepte de nom d'utilisateur, et l'envie d'un autre répond 404, jamais 403.
/// </para>
/// </remarks>
public class RevueSouhaitee
{
public int Id { get; set; }
/// <summary>
/// <c>YNH_USER</c> du propriétaire de l'envie. <b>Frontière</b> : toute lecture filtre dessus.
/// </summary>
public string Utilisateur { get; set; } = string.Empty;
/// <summary>Titre de la revue (« Médor »), sans son numéro.</summary>
public string Titre { get; set; } = string.Empty;
/// <summary>Titre mis à plat. Sert au dédoublonnage.</summary>
/// <remarks>
/// ⚠️ <see cref="NormalisationTexte"/> et non <c>CleOeuvre</c> : le titre d'une revue n'a ni
/// mention de responsabilité ni sous-titre à couper, et « Médor : les yeux ouverts » est une
/// accroche qu'on ne tape pas. C'est la même règle que <see cref="Revue.TitreNormalise"/>.
/// </remarks>
public string TitreNormalise { get; set; } = string.Empty;
/// <summary>
/// Numéro souhaité (« 43 », « hors-série 7 »), ou <c>null</c> pour la revue entière.
/// </summary>
/// <remarks>
/// Facultatif, et c'est la moitié du besoin : on souhaite parfois un numéro précis vu en
/// kiosque, parfois l'abonnement à un titre qu'on ne possède pas encore.
/// </remarks>
public string? Numero { get; set; }
/// <summary>
/// Numéro mis à plat. <b>Jamais <c>null</c></b> — chaîne vide pour « la revue entière ».
/// </summary>
/// <remarks>
/// ⚠️ Même piège que <see cref="LivreSouhaite.AuteurNormalise"/> : SQLite tient deux
/// <c>NULL</c> pour distincts, et l'unicité laisserait alors passer autant de fois « Médor,
/// pas de numéro » qu'on voudrait.
/// </remarks>
public string NumeroNormalise { get; set; } = string.Empty;
/// <summary>
/// ISSN, facultatif, <b>canonisé avec son tiret</b> (« 2466-6718 »).
/// </summary>
/// <remarks>
/// C'est la forme rangée en base pour tout le projet — seule exception à « la valeur stockée
/// reste nue », l'ISSN étant produit à tiret par le code-barres et interrogé à tiret par la
/// BnF. Sans canonisation, un ISSN tapé « 24666718 » ne se rapprocherait de rien.
/// </remarks>
public string? Issn { get; set; }
/// <summary>Note libre : « vu chez le dentiste », « demander au kiosque ».</summary>
public string? Note { get; set; }
public DateTime DateAjout { get; set; }
/// <summary>
/// Rang dans la section « revues » de la liste, du plus désiré au moins désiré.
/// </summary>
/// <remarks>
/// ⚠️ Cette numérotation est <b>indépendante</b> de celle de <see cref="LivreSouhaite.Rang"/> :
/// chaque table repart de zéro par utilisateur. C'est précisément pourquoi la liste affiche
/// deux sections plutôt qu'un seul classement entrelacé — deux suites de rangs sans rapport
/// mélangées produiraient un ordre que personne n'a choisi.
/// </remarks>
public int Rang { get; set; }
public void RecalculerFormes()
{
Titre = Titre.Trim();
TitreNormalise = NormalisationTexte.Normaliser(Titre);
Numero = string.IsNullOrWhiteSpace(Numero) ? null : Numero.Trim();
NumeroNormalise = Numero is null ? string.Empty : NormalisationTexte.Normaliser(Numero);
// Canonique() rend la saisie élaguée quand ce n'est pas un ISSN : un code mal recopié
// est conservé tel quel plutôt que jeté — le voir mal formé vaut mieux que le voir
// disparaître.
Issn = FormatageIssn.Canonique(Issn);
}
}