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:
Paul Schneider 2026-09-24 16:35:36 +01:00
commit 70ab05522b
Signed by: notazof
GPG key ID: AD4B9B545FBD7D12
31 changed files with 6515 additions and 20 deletions

View file

@ -1394,6 +1394,21 @@ namespace Yavsc.Migrations
b.ToTable("Estimates");
});
modelBuilder.Entity("Yavsc.Models.Billing.EstimateAttachedFile", b =>
{
b.Property<long>("FileId")
.HasColumnType("bigint");
b.Property<long>("EstimateId")
.HasColumnType("bigint");
b.HasKey("FileId", "EstimateId");
b.HasIndex("EstimateId");
b.ToTable("EstimateAttachedFiles");
});
modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b =>
{
b.Property<long>("Id")
@ -1502,6 +1517,21 @@ namespace Yavsc.Migrations
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Yavsc.Models.Billing.QueryAttachedFile", b =>
{
b.Property<long>("FileId")
.HasColumnType("bigint");
b.Property<long>("CommandId")
.HasColumnType("bigint");
b.HasKey("FileId", "CommandId");
b.HasIndex("CommandId");
b.ToTable("QueryAttachedFiles");
});
modelBuilder.Entity("Yavsc.Models.Billing.Signature", b =>
{
b.Property<long>("Id")
@ -1674,16 +1704,23 @@ namespace Yavsc.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ContentType")
.IsRequired()
.HasColumnType("text");
b.Property<long>("Length")
.HasColumnType("bigint");
b.Property<string>("OwnerId")
.HasColumnType("text");
b.Property<string>("Path")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("UploadedFiles");
});
@ -3827,6 +3864,25 @@ namespace Yavsc.Migrations
b.Navigation("Query");
});
modelBuilder.Entity("Yavsc.Models.Billing.EstimateAttachedFile", b =>
{
b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate")
.WithMany()
.HasForeignKey("EstimateId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Blog.UploadedFile", "File")
.WithMany()
.HasForeignKey("FileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Estimate");
b.Navigation("File");
});
modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
@ -3860,6 +3916,25 @@ namespace Yavsc.Migrations
b.Navigation("Regularization");
});
modelBuilder.Entity("Yavsc.Models.Billing.QueryAttachedFile", b =>
{
b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Command")
.WithMany()
.HasForeignKey("CommandId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Blog.UploadedFile", "File")
.WithMany()
.HasForeignKey("FileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Command");
b.Navigation("File");
});
modelBuilder.Entity("Yavsc.Models.Billing.Signature", b =>
{
b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate")
@ -3952,6 +4027,15 @@ namespace Yavsc.Migrations
b.Navigation("Post");
});
modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b =>
{
b.HasOne("Yavsc.Models.ApplicationUser", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b =>
{
b.HasOne("Yavsc.Models.Blog.BlogPost", "Post")