Ajoute les notices sources et le renommage fusionné des auteurs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
LIMONIER Mathieu
2026-08-20 14:27:42 +02:00
co-authored by Copilot
parent 6b8f08ef26
commit e28d5bce32
24 changed files with 935 additions and 3 deletions
@@ -18,6 +18,10 @@ public interface IServiceAuteurs
Task<AuteurDto?> ObtenirAsync(int id, CancellationToken ct = default);
/// <summary>Renomme une fiche, ou signale la fiche cible à fusionner.</summary>
Task<ResultatRenommageAuteur> RenommerAsync(
int id, string nom, bool confirmerFusion = false, CancellationToken ct = default);
/// <summary>Rapprochements ambigus à soumettre à l'utilisateur, refus déjà écartés.</summary>
Task<IReadOnlyList<SuggestionRapprochement>> SuggestionsAsync(CancellationToken ct = default);
@@ -139,6 +143,62 @@ public sealed class ServiceAuteurs(MaBibliDbContext db) : IServiceAuteurs
})
.FirstOrDefaultAsync(ct);
public async Task<ResultatRenommageAuteur> RenommerAsync(
int id, string nom, bool confirmerFusion = false, CancellationToken ct = default)
{
var auteur = await db.Auteurs.FirstOrDefaultAsync(a => a.Id == id, ct);
if (auteur is null)
{
return new ResultatRenommageAuteur();
}
nom = nom.Trim();
var cle = RapprochementAuteurs.Cle(nom);
if (cle.Length == 0)
{
return new ResultatRenommageAuteur
{
Auteur = new AuteurDto { Id = id, Nom = auteur.Nom },
};
}
var collision = await db.Auteurs
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id != id && a.CleRegroupement == cle, ct);
if (collision is not null)
{
var dto = new AuteurDto
{
Id = collision.Id,
Nom = collision.Nom,
NombreDeLivres = await db.LivreAuteurs.CountAsync(l => l.AuteurId == collision.Id, ct),
};
if (!confirmerFusion)
{
return new ResultatRenommageAuteur { Collision = dto };
}
await FusionnerAsync(id, collision.Id, ct);
return new ResultatRenommageAuteur { Auteur = dto };
}
auteur.Nom = nom;
auteur.RecalculerFormes();
await db.SaveChangesAsync(ct);
return new ResultatRenommageAuteur
{
Auteur = new AuteurDto
{
Id = auteur.Id,
Nom = auteur.Nom,
NombreDeLivres = await db.LivreAuteurs.CountAsync(l => l.AuteurId == auteur.Id, ct),
},
};
}
public async Task<IReadOnlyList<SuggestionRapprochement>> SuggestionsAsync(
CancellationToken ct = default)
{
@@ -217,6 +277,15 @@ public sealed class ServiceAuteurs(MaBibliDbContext db) : IServiceAuteurs
// la clé primaire du lien est (LivreId, AuteurId).
if (!dejaLies.Add(lien.LivreId))
{
var existant = cible.Livres.FirstOrDefault(l => l.LivreId == lien.LivreId);
if (existant is not null)
{
existant.Position = Math.Min(existant.Position, lien.Position);
if (existant.Role == RoleAuteur.NonPrecise)
{
existant.Role = lien.Role;
}
}
db.LivreAuteurs.Remove(lien);
continue;
}
@@ -227,6 +296,7 @@ public sealed class ServiceAuteurs(MaBibliDbContext db) : IServiceAuteurs
LivreId = lien.LivreId,
AuteurId = cible.Id,
Position = lien.Position,
Role = lien.Role,
});
}
@@ -132,6 +132,7 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
Format = saisie.Format,
TypeDocument = saisie.TypeDocument,
CoverUrl = Vide(saisie.CoverUrl),
UrlNotice = Vide(saisie.UrlNotice),
DateAjout = DateTime.UtcNow,
// Renseigné par le serveur à partir de l'utilisateur authentifié, jamais par le client.
@@ -171,6 +172,7 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
livre.Format = saisie.Format;
livre.TypeDocument = saisie.TypeDocument;
livre.CoverUrl = Vide(saisie.CoverUrl);
livre.UrlNotice = Vide(saisie.UrlNotice);
livre.RecalculerFormes();
// DateAjout et AjoutePar ne sont jamais réécrits : ce sont des traces de la saisie
@@ -494,6 +496,7 @@ public sealed class ServiceCatalogue(MaBibliDbContext db, IServiceAuteurs auteur
TypeDocument = livre.TypeDocument,
Statut = statut,
CoverUrl = livre.CoverUrl,
UrlNotice = livre.UrlNotice,
DateAjout = livre.DateAjout,
AjoutePar = livre.AjoutePar,
PreteA = pretOuvert?.Emprunteur,