Gestion des fichiers utilisateur et pièces jointes (demande/devis)
Espace de stockage personnel géré depuis PostIt, avec attachement par
référence (sans duplication) des fichiers à la demande du client
(NominativeServiceCommand) et au devis du fournisseur (Estimate).
Backend:
- UploadedFile.OwnerId (FK ApplicationUser, nullable) ; les téléversements
vers api/v1/fs créent/mettent à jour une ligne propriétaire.
- Tables de jointure EstimateAttachedFile et QueryAttachedFile (PK composite),
migration UserFileAttachments.
- EstimateApiController: GET/POST/DELETE estimate/{id}/attachments
(fournisseur uniquement, fichier devant appartenir au caller).
- FrontOfficeApiController: GET/POST/DELETE front/query/{id}/attachments
(client uniquement).
- FileSystemApiController: GET fs/file/{fileId} sert les octets depuis le
host Blogs (autorisation propriétaire ou partie au devis/demande via DB
partagée) — le host API ne gère que les métadonnées, contrainte des
conteneurs docker à disques distincts.
Client .NET:
- UserFilesApiClient (list/upload/delete/download) pointant vers BlogsApiUrl.
- DTOs UserDirectoryDto/FileEntryDto/AttachmentDto/FileReceivedDto/UploadFile.
- Méthodes d'attachement sur EstimateApiClient et FrontOfficeApiClient.
PostIt (Avalonia):
- Page « Mes fichiers » (naviguer/téléverser/télécharger/supprimer), servant
aussi de sélecteur pour l'attachement.
- Section pièces jointes dans l'édition du devis (fournisseur) et le détail
de la demande (client), avec ajout depuis My Files, détachement,
téléchargement.
- Enregistrement DI + mapping ViewLocator + bouton d'accès.
Tests:
- EstimateEditionPageViewModelTests mis à jour (nouveau ctor fsClient,
historique d'appels du stub pour les assertions post-envoi).
Nommage HTTP/méthodes en anglais, documentation en français.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
eb58fc8862
commit
70ab05522b
31 changed files with 6515 additions and 20 deletions
|
|
@ -122,6 +122,48 @@ public sealed class FrontOfficeApiClient
|
|||
return _api.DownloadAsync(HttpMethod.Get, Absolute(path), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liste les pièces jointes (fichiers de l'espace perso du client)
|
||||
/// rattachées à la demande <paramref name="queryId"/> par référence
|
||||
/// (<c>GET front/query/{queryId}/attachments</c>).
|
||||
/// </summary>
|
||||
public Task<List<AttachmentDto>> GetQueryAttachmentsAsync(string billingCode, long queryId, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(billingCode))
|
||||
throw new ArgumentException("billingCode is required.", nameof(billingCode));
|
||||
if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId));
|
||||
var path = $"{PathPrefix}/query/{queryId}/attachments?billingCode={Uri.EscapeDataString(billingCode)}";
|
||||
return _api.CallAsync<List<AttachmentDto>>(HttpMethod.Get, Absolute(path), ct: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attache un fichier de l'espace perso du client à sa demande
|
||||
/// (<c>POST front/query/{queryId}/attachments</c>). Réservé au client.
|
||||
/// </summary>
|
||||
public Task AttachQueryFileAsync(string billingCode, long queryId, long fileId, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(billingCode))
|
||||
throw new ArgumentException("billingCode is required.", nameof(billingCode));
|
||||
if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId));
|
||||
if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId));
|
||||
var path = $"{PathPrefix}/query/{queryId}/attachments?billingCode={Uri.EscapeDataString(billingCode)}";
|
||||
return _api.CallAsync(HttpMethod.Post, Absolute(path), body: new { fileId }, ct: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Détache un fichier de la demande
|
||||
/// (<c>DELETE front/query/{queryId}/attachments/{fileId}</c>). Réservé au client.
|
||||
/// </summary>
|
||||
public Task DetachQueryFileAsync(string billingCode, long queryId, long fileId, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(billingCode))
|
||||
throw new ArgumentException("billingCode is required.", nameof(billingCode));
|
||||
if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId));
|
||||
if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId));
|
||||
var path = $"{PathPrefix}/query/{queryId}/attachments/{fileId}?billingCode={Uri.EscapeDataString(billingCode)}";
|
||||
return _api.CallAsync(HttpMethod.Delete, Absolute(path), ct: ct);
|
||||
}
|
||||
|
||||
private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString();
|
||||
|
||||
private Uri ResolveBusinessBaseAddress()
|
||||
|
|
|
|||
Loading…
Reference in a new issue