Cataloguer les revues : une fiche, ses numéros à l'intérieur

Clôt la décision du premier lot, qui écartait les périodiques faute de
modèle. Douze numéros d'un magazine ne font plus douze fiches identiques.

Table à part, pour la troisième fois et pour le même motif que la liste
d'envies et les séries : logée dans Livres, une revue entrerait dans le
catalogue, ses compteurs, les doublons et les bibliographies, et il
faudrait écrire « et qui n'est pas une revue » à chaque lecture.

Un numéro est recensé, rien de plus — ni prêt ni statut, tous deux liés à
Livre par clé étrangère. Réversible.

« Créer ou retrouver » et non « créer » : scanner le numéro suivant du même
magazine retombe forcément sur la même revue. L'unicité de l'ISSN est un
index partiel, sans quoi les revues sans ISSN se bloqueraient entre elles.

Le flux 977 mène désormais à la fiche de la revue, et ne pré-remplit plus
le formulaire d'un livre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-19 22:19:08 +02:00
co-authored by Claude Opus 5
parent 4e372ab694
commit ca0a9dc18c
20 changed files with 2110 additions and 42 deletions
@@ -48,6 +48,15 @@ public static class ClesCache
/// </para>
/// </remarks>
public const string Series = "series";
/// <summary>
/// Les revues, avec les numéros possédés. Communes au foyer, comme les séries.
/// </summary>
/// <remarks>
/// Sa raison d'être est la même que celle de la liste d'envies : savoir <b>si l'on a déjà
/// ce numéro</b> se demande devant le présentoir d'un kiosque, là où le réseau manque.
/// </remarks>
public const string Revues = "revues";
}
/// <summary>Un instantané relu du cache, avec la date de la synchronisation qui l'a produit.</summary>
@@ -48,6 +48,7 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat
await ListerPretsEnCoursAsync(ct);
await ListerSouhaitsAsync(ct);
await ListerSeriesAsync(ct);
await ListerRevuesAsync(ct);
await ObtenirUtilisateurAsync(ct);
return reseau.EnLigne;
}
@@ -518,6 +519,7 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat
await ListerAuteursAsync();
await ListerSouhaitsAsync();
await ListerSeriesAsync();
await ListerRevuesAsync();
}
catch (Exception)
{
@@ -807,6 +809,53 @@ public sealed class ServiceLivresApi(HttpClient http, CacheHorsLigne cache, Etat
public Task<string?> SupprimerSerieAsync(int id, CancellationToken ct = default) =>
SupprimerAsync($"api/series/{id}", ct);
// ─────────────────────────────────────────────────────────────────────────
// Revues et magazines. Communes au foyer ; une revue n'est pas un livre et ne passe donc
// jamais par le catalogue.
// ─────────────────────────────────────────────────────────────────────────
public async Task<IReadOnlyList<RevueDto>> ListerRevuesAsync(CancellationToken ct = default)
{
var (ok, revues) = await EssayerAsync(
() => http.GetFromJsonAsync<List<RevueDto>>("api/revues", Json, ct));
if (ok)
{
await MemoriserAsync(ClesCache.Revues, revues ?? []);
return revues ?? [];
}
var instantane = await LireCacheAsync<List<RevueDto>>(ClesCache.Revues);
return instantane?.Donnees ?? [];
}
/// <summary>
/// Crée la revue, ou rend celle qui existe déjà — même ISSN, ou même titre.
/// </summary>
/// <remarks>
/// C'est ce que réclame le scan d'un code-barres <c>977</c> : le second numéro d'un
/// magazine retombe forcément sur la même revue, et l'utilisateur ne fait jamais qu'un
/// geste, « voici un numéro de plus ».
/// </remarks>
public async Task<ResultatApi<RevueDto>> CreerRevueAsync(
EnregistrementRevue saisie, CancellationToken ct = default) =>
await EcrireAsync<RevueDto>(() => http.PostAsJsonAsync("api/revues", saisie, Json, ct), ct);
public async Task<ResultatApi<RevueDto>> ModifierRevueAsync(
int id, EnregistrementRevue saisie, CancellationToken ct = default) =>
await EcrireAsync<RevueDto>(() => http.PutAsJsonAsync($"api/revues/{id}", saisie, Json, ct), ct);
public async Task<ResultatApi<RevueDto>> AjouterNumeroAsync(
int revueId, AjoutNumeroRevue saisie, CancellationToken ct = default) =>
await EcrireAsync<RevueDto>(
() => http.PostAsJsonAsync($"api/revues/{revueId}/numeros", saisie, Json, ct), ct);
public Task<string?> RetirerNumeroAsync(int numeroId, CancellationToken ct = default) =>
SupprimerAsync($"api/revues/numeros/{numeroId}", ct);
public Task<string?> SupprimerRevueAsync(int id, CancellationToken ct = default) =>
SupprimerAsync($"api/revues/{id}", ct);
/// <summary>Suppression générique : refus hors-ligne, 204 attendu, instantanés rafraîchis.</summary>
private async Task<string?> SupprimerAsync(string url, CancellationToken ct)
{