Distinguer scénario, dessin et couleurs sur chaque livre

Le rôle vit sur le LIEN livre-auteur, jamais sur l'auteur : le même auteur
scénarise un album et dessine le suivant, et il ne doit pas exister en deux
fiches pour autant — ce que l'index unique sur la clé de regroupement
interdit de toute façon.

Les rôles se retrouvent par clé d'auteur résolue et non par position :
ResoudreAsync écarte les noms vides et fusionne les variantes, donc sa
liste peut être plus courte que la saisie.

Piège traité : le champ des noms reconstruit la liste à chaque frappe.
Decouper reprend les rôles connus en rapprochant sur la forme normalisée,
sans quoi corriger une faute de frappe les effacerait tous.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-19 22:37:20 +02:00
co-authored by Claude Opus 5
parent 9ab6f23b8c
commit 07cb48b2be
16 changed files with 895 additions and 22 deletions
@@ -288,8 +288,10 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
db.Livres.AsNoTracking().Include(l => l.Auteurs).ThenInclude(la => la.Auteur);
/// <summary>Un des auteurs du livre catalogué est-il l'un de ceux qu'on est en train de saisir ?</summary>
private static bool MemeAuteur(Livre livre, IReadOnlyList<string> saisis) =>
RapprochementAuteurs.PartagentUnAuteur(livre.Auteurs.Select(la => la.Auteur?.Nom), saisis);
/// <remarks>Le rôle n'entre pas dans la comparaison : c'est la personne qui compte ici.</remarks>
private static bool MemeAuteur(Livre livre, IReadOnlyList<AuteurSaisi> saisis) =>
RapprochementAuteurs.PartagentUnAuteur(
livre.Auteurs.Select(la => la.Auteur?.Nom), saisis.Select(a => a.Nom));
/// <summary>Phrase d'avertissement, écrite pour être lue telle quelle par l'utilisateur.</summary>
/// <remarks>
@@ -399,11 +401,18 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
await db.SaveChangesAsync(ct);
}
/// <summary>Remplace les auteurs du livre par ceux de la saisie, en conservant leur ordre.</summary>
/// <summary>
/// Remplace les auteurs du livre par ceux de la saisie, en conservant leur ordre et leur rôle.
/// </summary>
/// <remarks>
/// ⚠️ Le <b>rôle vit sur le lien</b>, pas sur la fiche de l'auteur : le même dessinateur
/// peut avoir scénarisé un autre album, et il ne doit pas exister en deux fiches pour
/// autant. C'est pour cela que <c>ResoudreAsync</c> ne reçoit que des noms.
/// </remarks>
private async Task RattacherAuteursAsync(
Livre livre, IEnumerable<string> noms, CancellationToken ct)
Livre livre, IReadOnlyList<AuteurSaisi> saisis, CancellationToken ct)
{
var resolus = await auteurs.ResoudreAsync(noms, ct);
var resolus = await auteurs.ResoudreAsync(saisis.Select(a => a.Nom), ct);
// Les liens existants sont retirés puis reconstruits : c'est la façon la plus simple de
// gérer à la fois l'ajout, le retrait et le réordonnancement des auteurs.
@@ -413,9 +422,23 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
livre.Auteurs.Clear();
}
// ResoudreAsync écarte les noms vides et fusionne les variantes : sa liste peut être
// plus courte que la saisie. Les rôles se retrouvent donc par NOM RÉSOLU, jamais par
// position — sans quoi un nom vide au milieu décalerait tous les rôles suivants.
var roles = new Dictionary<string, RoleAuteur>(StringComparer.Ordinal);
foreach (var saisi in saisis)
{
roles.TryAdd(RapprochementAuteurs.Cle(saisi.Nom), saisi.Role);
}
for (var i = 0; i < resolus.Count; i++)
{
livre.Auteurs.Add(new LivreAuteur { Auteur = resolus[i], Position = i });
livre.Auteurs.Add(new LivreAuteur
{
Auteur = resolus[i],
Position = i,
Role = roles.GetValueOrDefault(resolus[i].CleRegroupement, RoleAuteur.NonPrecise),
});
}
}
@@ -464,7 +487,7 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
Auteurs = livre.Auteurs
.OrderBy(la => la.Position)
.Where(la => la.Auteur is not null)
.Select(la => new AuteurDto { Id = la.Auteur!.Id, Nom = la.Auteur.Nom })
.Select(la => new AuteurDto { Id = la.Auteur!.Id, Nom = la.Auteur.Nom, Role = la.Role })
.ToList(),
Editeur = livre.Editeur,
Format = livre.Format,