Files
mabibli/MaBibli.Shared/Dtos/EnregistrementLivre.cs
T
mathieuandClaude Opus 5 ad00fd5116 Aligne les thèmes sur le point-virgule des auteurs
Le champ des auteurs se saisit au point-virgule depuis toujours ; celui des
thèmes se saisissait à la virgule. Deux champs voisins du même formulaire ne
doivent pas se saisir de deux façons.

La virgule reste acceptée en repli : c'était l'habitude, et un thème n'en
contient jamais — contrairement à un titre d'article à la une, qui arrive au lot
suivant et pour lequel elle couperait le titre en deux. D'où le paramètre de
`ListeSaisie` plutôt qu'une règle unique.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:24:57 +02:00

152 lines
5.9 KiB
C#

using System.Text.Json.Serialization;
using MaBibli.Shared.Entites;
using MaBibli.Shared.Textes;
namespace MaBibli.Shared.Dtos;
/// <summary>
/// Charge utile de création et de modification d'un livre.
/// </summary>
/// <remarks>
/// <see cref="Livre.AjoutePar"/> n'y figure pas volontairement : il est déterminé par le serveur
/// à partir de l'utilisateur authentifié, jamais par le client.
/// </remarks>
public record EnregistrementLivre
{
/// <summary>Séparateur des auteurs dans la forme texte d'un champ de saisie.</summary>
/// <remarks>
/// Le point-virgule, et non la virgule : celle-ci est déjà prise par la forme
/// bibliographique « Zola, Émile », qu'on doit pouvoir taper telle quelle.
/// </remarks>
public const char SeparateurAuteurs = ';';
/// <summary>Facultatif : certains livres n'ont pas d'ISBN. Normalisé par le serveur.</summary>
public string? Isbn { get; set; }
public string Titre { get; set; } = string.Empty;
/// <summary>
/// Auteurs, dans l'ordre de la couverture, chacun avec son rôle sur <b>ce</b> livre. Le
/// serveur les rattache aux fiches existantes quand c'est sûr, et en crée sinon.
/// </summary>
public List<AuteurSaisi> Auteurs { get; set; } = [];
/// <summary>Thèmes saisis manuellement, séparés par des points-virgules dans l'interface.</summary>
public List<string> Themes { get; set; } = [];
public string? Editeur { get; set; }
public Format Format { get; set; }
/// <summary>Roman, bande dessinée… Facultatif : « non précisé » est une réponse valable.</summary>
public TypeDocument TypeDocument { get; set; }
/// <summary>
/// Statut de lecture <b>de l'appelant</b>. <c>null</c> = non commencé, et aucune ligne n'est
/// alors écrite en base.
/// </summary>
public Statut? Statut { get; set; }
public string? CoverUrl { get; set; }
/// <summary>
/// Notice ayant servi au préremplissage, ou <c>null</c> pour une saisie manuelle.
/// </summary>
public string? UrlNotice { get; set; }
/// <summary>
/// Vue « une seule ligne » de <see cref="Auteurs"/>, pour les champs de saisie simples.
/// </summary>
/// <remarks>
/// Non sérialisée : <see cref="Auteurs"/> est la forme de référence sur le fil. Cette
/// propriété existe pour que l'interface puisse lier un <c>&lt;input&gt;</c> unique sans
/// avoir à gérer une liste dynamique — un foyer saisit un auteur dans l'immense majorité
/// des cas, et les rares livres à quatre auteurs se tapent avec des « ; ».
/// </remarks>
[JsonIgnore]
public string? Auteur
{
get => Auteurs.Count == 0 ? null : string.Join($"{SeparateurAuteurs} ", Auteurs.Select(a => a.Nom));
set => Auteurs = Decouper(value, Auteurs);
}
[JsonIgnore]
public string? ThemesTexte
{
get => Themes.Count == 0 ? null : ListeSaisie.Joindre(Themes);
set => Themes = DecouperThemes(value);
}
/// <summary>
/// Découpe une saisie texte en auteurs, en écartant les entrées vides.
/// </summary>
/// <param name="connus">
/// Auteurs déjà saisis, dont les <b>rôles</b> sont repris quand le nom réapparaît.
/// </param>
/// <remarks>
/// ⚠️ Sans cette reprise, corriger une faute de frappe dans le champ des noms effacerait le
/// rôle des auteurs voisins : le champ se relie à chaque frappe, et il reconstruirait la
/// liste entière à « non précisé ». Le rapprochement se fait sur la forme normalisée du nom,
/// pour survivre à un accent ajouté après coup.
/// </remarks>
public static List<AuteurSaisi> Decouper(string? saisie, IEnumerable<AuteurSaisi>? connus = null)
{
if (string.IsNullOrWhiteSpace(saisie))
{
return [];
}
var roles = new Dictionary<string, RoleAuteur>(StringComparer.Ordinal);
foreach (var connu in connus ?? [])
{
roles.TryAdd(NormalisationTexte.Normaliser(connu.Nom), connu.Role);
}
return saisie
.Split(SeparateurAuteurs, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(nom => new AuteurSaisi(
nom, roles.GetValueOrDefault(NormalisationTexte.Normaliser(nom), RoleAuteur.NonPrecise)))
.ToList();
}
/// <summary>
/// Découpe la ligne des thèmes. Séparateur : le <b>point-virgule</b>, comme celui des
/// auteurs juste au-dessus dans le même formulaire — la virgule reste acceptée, un thème
/// n'en contenant jamais.
/// </summary>
public static List<string> DecouperThemes(string? saisie) =>
ListeSaisie.Decouper(saisie, virguleAussi: true);
}
/// <summary>Un auteur tel qu'il est saisi : son nom, et son rôle sur ce livre.</summary>
/// <remarks>
/// Le rôle n'appartient pas à la personne mais au <b>lien</b> : le même auteur scénarise un
/// album et dessine le suivant. Voir <see cref="RoleAuteur"/>.
/// </remarks>
public record AuteurSaisi(string Nom, RoleAuteur Role = RoleAuteur.NonPrecise);
/// <summary>Demande de renommage d'une fiche auteur.</summary>
public record RenommerAuteur(string Nom);
/// <summary>Résultat d'un renommage, avec collision éventuelle à confirmer.</summary>
public record ResultatRenommageAuteur
{
public AuteurDto? Auteur { get; init; }
public AuteurDto? Collision { get; init; }
public bool EstCollision => Collision is not null;
}
/// <summary>Charge utile du changement de statut de lecture — l'action la plus fréquente.</summary>
/// <remarks>
/// Le statut posé est toujours celui de l'appelant : il n'y a pas de champ « utilisateur »,
/// et il ne doit jamais y en avoir. Personne ne coche « lu » à la place d'un autre.
/// </remarks>
public record ChangementStatut
{
/// <summary><c>null</c> retire le statut : le livre redevient « non commencé » pour l'appelant.</summary>
public Statut? Statut { get; set; }
}