L'ISSN est désormais rangé sous sa forme à tiret, et pas seulement affiché ainsi. C'est le seul endroit où le projet s'écarte de « la valeur stockée reste nue », et il y a une raison : le code-barres produit déjà un ISSN à tiret, et la BnF interroge `bib.issn` avec le tiret. Un ISSN tapé « 24666718 » ne se rapprochait donc de rien, et le scan suivant créait une seconde fiche à côté. La canonisation a lieu avant la recherche, pas seulement à l'écriture. Les champs de saisie, eux, gardent la valeur tapée : découper à la frappe se battrait avec le curseur. `ServiceRenormalisation` rattrape les ISSN existants — par un passage dédié, le mécanisme générique confondant tous les NULL en une seule clé, alors que l'unicité est ici un index partiel. Une fiche dont la forme canonique est déjà prise garde la sienne : le serveur doit démarrer. La couverture d'un numéro est une URL collée, jamais une photo : aucune source ne peut la fournir, l'ISSN désignant la revue et non la parution, et le projet ne stocke aucun fichier. Le garde du relais de couvertures a donc été étendu aux numéros — l'oublier n'aurait produit aucune erreur visible, seulement une image présente en ligne et absente hors-ligne. Les articles à la une vivent dans une table à part et non dans `Theme` : un thème est un vocabulaire qu'on réutilise, un titre d'article est unique à sa parution. Rien ne se partage, donc pas de n-n — une simple clé étrangère vers le numéro. Ils se saisissent au point-virgule seul : « Ukraine, deux ans après » serait coupé en deux par la virgule. Un PUT sur le numéro était indispensable : on note un numéro le jour où on le range et on en recopie le sommaire plus tard. Sans lui, couverture et une n'auraient existé qu'à la création, c'est-à-dire jamais. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
5.4 KiB
C#
128 lines
5.4 KiB
C#
using MaBibli.Shared.Isbn;
|
|
using MaBibli.Shared.Textes;
|
|
|
|
namespace MaBibli.Shared.Entites;
|
|
|
|
/// <summary>
|
|
/// Une revue — magazine, périodique —, et non l'un de ses numéros.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <b>Pourquoi une table à part et non une ligne de <c>Livres</c> ?</b> Même raisonnement que
|
|
/// pour <see cref="LivreSouhaite"/> et <see cref="Serie"/>, et il vaut d'être répété : logée
|
|
/// dans <c>Livres</c>, une revue entrerait <b>mécaniquement</b> dans le catalogue, ses
|
|
/// compteurs, la détection de doublons, les séries et les bibliographies par auteur — et il
|
|
/// faudrait écrire « et qui n'est pas une revue » à chaque lecture. Un invariant qu'on réécrit
|
|
/// partout finit par être oublié quelque part.
|
|
/// <para>
|
|
/// <b>Une fiche par revue, les numéros à l'intérieur.</b> C'est ce qui évite le défaut redouté
|
|
/// dès le premier lot : douze numéros d'un même magazine ne font pas douze fiches identiques,
|
|
/// puisqu'ils partagent la fiche de leur revue.
|
|
/// </para>
|
|
/// <para>
|
|
/// ⚠️ L'<see cref="Issn"/> est un identifiant de <b>revue</b>, jamais un ISBN. Ranger un code
|
|
/// <c>977</c> dans <c>Livre.Isbn</c> ferait échouer tout lookup ultérieur sur cette fiche —
|
|
/// c'est précisément ce que cette table évite.
|
|
/// </para>
|
|
/// </remarks>
|
|
public class Revue
|
|
{
|
|
public int Id { get; set; }
|
|
|
|
public string Titre { get; set; } = string.Empty;
|
|
|
|
/// <summary>Titre mis à plat. Index <b>unique</b> : une revue, une fiche.</summary>
|
|
public string TitreNormalise { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// ISSN sous sa forme canonique à tiret (<c>2466-6718</c>), quand on le connaît.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Facultatif : une revue se catalogue à la main sans que personne n'ait scanné son
|
|
/// code-barres. Son unicité est portée par un index <b>partiel</b> — deux <c>NULL</c> sont
|
|
/// distincts pour SQLite, une unicité simple laisserait donc passer autant de revues sans
|
|
/// ISSN qu'on veut, ce qui est justement le comportement voulu.
|
|
/// </remarks>
|
|
public string? Issn { get; set; }
|
|
|
|
public string? Editeur { get; set; }
|
|
|
|
public DateTime DateAjout { get; set; }
|
|
|
|
/// <summary>Trace de saisie. <b>Pas une frontière</b> : les revues sont communes au foyer.</summary>
|
|
public string? AjoutePar { get; set; }
|
|
|
|
public List<NumeroRevue> Numeros { get; set; } = [];
|
|
|
|
public void RecalculerFormes()
|
|
{
|
|
Titre = Titre.Trim();
|
|
TitreNormalise = NormalisationTexte.Normaliser(Titre);
|
|
// ⚠️ L'ISSN est rangé sous sa forme À TIRET, et ce n'est pas de la cosmétique : c'est
|
|
// celle que produit le code-barres (CodePeriodique.IssnDepuis) et celle qu'interroge
|
|
// bib.issn à la BnF. Un ISSN tapé « 24666718 » ne se serait rapproché de rien.
|
|
Issn = FormatageIssn.Canonique(Issn);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Un numéro possédé d'une revue.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <b>Décidé le 2026-08-19 : un numéro est recensé, rien de plus.</b> Ni prêt, ni statut de
|
|
/// lecture — ces deux mécanismes sont attachés à <see cref="Livre"/> par clé étrangère, et les
|
|
/// rouvrir demanderait une seconde table de prêts ou une parenté commune, pour un usage non
|
|
/// confirmé. Le choix est <b>réversible</b> : les ajouter plus tard ne détruit rien de ce qui
|
|
/// est enregistré ici.
|
|
/// </remarks>
|
|
public class NumeroRevue
|
|
{
|
|
public int Id { get; set; }
|
|
|
|
public int RevueId { get; set; }
|
|
|
|
public Revue? Revue { get; set; }
|
|
|
|
/// <summary>
|
|
/// Le numéro tel qu'il est imprimé (« 43 », « hors-série 7 »). <b>Obligatoire</b> :
|
|
/// sans lui, deux numéros de la même revue seraient indiscernables.
|
|
/// </summary>
|
|
public string Numero { get; set; } = string.Empty;
|
|
|
|
/// <summary>Forme mise à plat, porteuse de l'unicité au sein de la revue.</summary>
|
|
public string NumeroNormalise { get; set; } = string.Empty;
|
|
|
|
/// <summary>Date de parution, quand elle est connue. En UTC comme toutes les dates.</summary>
|
|
public DateTime? DateParution { get; set; }
|
|
|
|
/// <summary>Note libre : « dossier sur l'eau », « prêté à Paul », « acheté en gare ».</summary>
|
|
public string? Note { get; set; }
|
|
|
|
/// <summary>
|
|
/// URL d'une image de couverture, collée à la main.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// ⚠️ <b>Aucune source ne peut la fournir</b>, contrairement aux livres : l'ISSN désigne la
|
|
/// <i>revue</i>, pas la parution, et OpenLibrary n'a donc rien à en dire. C'est pourquoi ce
|
|
/// champ n'est alimenté que par une URL saisie — et pourquoi l'écran n'en propose aucune.
|
|
/// <para>
|
|
/// ⚠️ Une image de numéro doit être <b>connue du relais</b>
|
|
/// (<c>GET /api/couvertures</c>) au même titre que celle d'un livre ou d'une envie, sans
|
|
/// quoi elle s'afficherait en ligne et jamais hors-ligne — exactement le défaut corrigé le
|
|
/// 2026-08-20 pour les hébergeurs sans CORS.
|
|
/// </para>
|
|
/// </remarks>
|
|
public string? CoverUrl { get; set; }
|
|
|
|
/// <summary>Articles annoncés à la une, dans l'ordre de saisie.</summary>
|
|
public List<ArticleUne> Articles { get; set; } = [];
|
|
|
|
public DateTime DateAjout { get; set; }
|
|
|
|
public void RecalculerFormes()
|
|
{
|
|
Numero = Numero.Trim();
|
|
NumeroNormalise = NormalisationTexte.Normaliser(Numero);
|
|
CoverUrl = string.IsNullOrWhiteSpace(CoverUrl) ? null : CoverUrl.Trim();
|
|
}
|
|
}
|