yavsc/src/PostIt/PostIt/Views/Activity/BillingQueryDetailsPage.axaml
Paul Schneider 70ab05522b
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>
2026-09-24 16:35:36 +01:00

185 lines
No EOL
10 KiB
XML

<ContentPage xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PostIt.ViewModels"
xmlns:client="using:Yavsc.Api.Client"
xmlns:postitControls="using:PostIt.Controls"
x:Class="PostIt.Views.BillingQueryDetailsPage"
x:DataType="vm:BillingQueryDetailsPageViewModel"
Header="Detail commande">
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="12">
<StackPanel Grid.Row="0" Spacing="4">
<TextBlock Text="{Binding Title}"
FontSize="20"
FontWeight="Bold"
Foreground="{Binding TitleForeground}" />
<TextBlock Text="{Binding ContextLabel}" Opacity="0.75" />
</StackPanel>
<Grid Grid.Row="1" Margin="0,10,0,0" ColumnDefinitions="Auto,8,Auto,8,Auto,8,Auto,*">
<Button Grid.Column="0"
Content="Retour"
Command="{Binding BackCommand}" />
<Button Grid.Column="2"
Content="Ouvrir en edition"
Command="{Binding OpenEditorCommand}"
IsVisible="{Binding CanEdit}" />
<Button Grid.Column="4"
Content="Télécharger PDF"
Command="{Binding DownloadPdfCommand}" />
<Button Grid.Column="6"
Content="Télécharger TeX"
Command="{Binding DownloadTexCommand}" />
</Grid>
<ScrollViewer Grid.Row="2" Margin="0,12,0,12">
<StackPanel Spacing="10">
<Border BorderThickness="1"
BorderBrush="#22000000"
CornerRadius="8"
Padding="10">
<Grid ColumnDefinitions="Auto,12,*" RowDefinitions="Auto,Auto" RowSpacing="8">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Statut" FontWeight="SemiBold" />
<Border Grid.Row="0"
Grid.Column="2"
Background="{Binding StatusBadgeBackground}"
BorderBrush="{Binding StatusBadgeBorder}"
BorderThickness="1"
CornerRadius="6"
Padding="8,3"
HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Text="{Binding StatusGlyph}"
Foreground="{Binding StatusBadgeForeground}"
FontWeight="Bold" />
<TextBlock Text="{Binding StatusLabel}"
Foreground="{Binding StatusBadgeForeground}"
FontWeight="SemiBold" />
</StackPanel>
</Border>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Date" FontWeight="SemiBold" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding EventDateLabel}" />
</Grid>
</Border>
<Border BorderThickness="1"
BorderBrush="#22000000"
CornerRadius="8"
Padding="10">
<Grid ColumnDefinitions="Auto,12,*" RowDefinitions="Auto,Auto,Auto,Auto" RowSpacing="8">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Description" FontWeight="SemiBold" />
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" TextWrapping="Wrap" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Motif" FontWeight="SemiBold" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Reason}" TextWrapping="Wrap" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Infos" FontWeight="SemiBold" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding AdditionalInfo}" TextWrapping="Wrap" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Consentement" FontWeight="SemiBold" />
<TextBlock Grid.Row="3" Grid.Column="2" Text="{Binding ConsentLabel}" />
</Grid>
</Border>
<!-- Pièces jointes (par référence à l'espace perso du client) -->
<Border BorderThickness="1"
BorderBrush="#22000000"
CornerRadius="8"
Padding="10">
<StackPanel Spacing="8">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0"
Text="Pièces jointes"
FontWeight="SemiBold"
VerticalAlignment="Center" />
<Button Grid.Column="1"
Content="Ajouter depuis My Files"
Command="{Binding AddQueryAttachmentCommand}"
IsVisible="{Binding CanAttach}" />
</Grid>
<ItemsControl ItemsSource="{Binding Attachments}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="client:AttachmentDto">
<Border BorderThickness="1"
BorderBrush="#11000000"
CornerRadius="6"
Padding="8"
Margin="0,0,0,6">
<Grid ColumnDefinitions="*,Auto,8,Auto">
<TextBlock Grid.Column="0"
Text="{Binding Path}"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" />
<Button Grid.Column="1"
Content="Télécharger"
Command="{Binding $parent[vm:BillingQueryDetailsPageViewModel].DownloadQueryAttachmentCommand}"
CommandParameter="{Binding}" />
<Button Grid.Column="3"
Content="Détacher"
Command="{Binding $parent[vm:BillingQueryDetailsPageViewModel].DetachQueryFileCommand}"
CommandParameter="{Binding}"
IsVisible="{Binding $parent[vm:BillingQueryDetailsPageViewModel].CanAttach}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<Expander Header="Metadonnees techniques"
IsExpanded="False">
<Border BorderThickness="1"
BorderBrush="#22000000"
CornerRadius="8"
Padding="10"
Margin="0,6,0,0">
<Grid ColumnDefinitions="Auto,12,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto" RowSpacing="8">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Code" FontWeight="SemiBold" />
<TextBlock Grid.Row="0"
Grid.Column="2"
Text="{Binding BillingCode}"
FontFamily="Consolas, Courier New, monospace" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Client" FontWeight="SemiBold" />
<TextBlock Grid.Row="1"
Grid.Column="2"
Text="{Binding ClientId}"
FontFamily="Consolas, Courier New, monospace" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Provision" FontWeight="SemiBold" />
<TextBlock Grid.Row="2"
Grid.Column="2"
Text="{Binding ProvisionalLabel}"
FontFamily="Consolas, Courier New, monospace" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Lieu" FontWeight="SemiBold" />
<TextBlock Grid.Row="3"
Grid.Column="2"
Text="{Binding LocationLabel}"
TextWrapping="Wrap"
FontFamily="Consolas, Courier New, monospace" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Prestations" FontWeight="SemiBold" />
<TextBlock Grid.Row="4"
Grid.Column="2"
Text="{Binding PrestationsLabel}"
TextWrapping="Wrap"
FontFamily="Consolas, Courier New, monospace" />
</Grid>
</Border>
</Expander>
</StackPanel>
</ScrollViewer>
<Grid Grid.Row="3" ColumnDefinitions="*,Auto" Margin="0,8,0,0">
<postitControls:StatusBar Grid.Column="0"
DataContext="{Binding ActionStatus}" />
<ProgressBar Grid.Column="1"
Width="120"
IsIndeterminate="True"
IsVisible="{Binding IsBusy}" />
</Grid>
</Grid>
</ContentPage>