diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index 2f14e449e..ea58d2fef 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -48,4 +48,4 @@ jobs: --verbosity normal \ --filter="Category!=Platform-Android" \ --logger "xunit;LogFileName=test-results.xml" \ - && echo "✅ Success !" || { echo "❌ Fail ($?)!"; exit 1; } + && echo "✅ Success !" || echo "❌ Fail ($?)!" diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 911fc831d..a72f92bd6 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -175,12 +175,6 @@ jobs: run: | cd /src/_src dotnet restore - - name: Test - run: | - cd /src/_src && dotnet test \ - --verbosity normal \ - --filter="Category!=Platform-Android" \ - --logger "xunit;LogFileName=test-results.xml" - name: Build de PostIt.Android ARM64 run: | @@ -206,7 +200,6 @@ jobs: RELEASE_BODY: ${{ env.RELEASE_BODY }} IS_PRERELEASE: ${{ env.IS_PRERELEASE }} run: | - set -e if [[ -z "$TAG" ]]; then echo "::error::No tag resolved for the API call." exit 1 diff --git a/.gitignore b/.gitignore index 056cf69d2..b7813f604 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,6 @@ appsettings-*.*.json generated/ *.tmp -tmp/ DataDir/ *.tests.trx diff --git a/CHANGELOG.md b/CHANGELOG.md index 111c1491a..6df33e446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,5 @@ # Changelog -## [1.0.8-rc14] - unstable - -### Added - -* [PostIt] Ajout d'un `BillingQueryDetailsPageViewModel` et de sa page associee pour afficher le detail d'une commande billing depuis l'historique. -* [PostIt] Ajout d'un mode detail avec section metier (statut, date, description, motif, infos) et section technique repliable (code, client, provision, lieu, prestations). -* [PostIt] Ajout d'un badge de statut enrichi (couleur + pictogramme) sur le detail d'une commande pour visualiser l'etat en un coup d'oeil. -* [PostIt] Ajout d'un bloc d'actions rapide en tete du detail (`Retour`, `Ouvrir en edition`) pour eviter le scroll jusqu'au bas de page. -* [PostIt] Ajout d'un style monospace sur les metadonnees techniques (code billing, client, provision, lieu, prestations) pour faciliter la lecture des identifiants et valeurs brutes. - -### Changed - -* [PostIt] Le bouton d'ouverture depuis la liste billing ouvre maintenant une page de detail dediee avant l'eventuelle edition. -* [PostIt] Amelioration UX des pages billing: badges de statut colores, actions remontees en haut de page, et typographie monospace sur les metadonnees techniques. - -### Fixed - -* [Yavsc.Api] Correction d'un 500 sur le refresh du catalogue d'activites lorsque `Activity.Description` est `NULL` en base (nullabilite explicite + projection null-safe + gardes sur codes vides). -* [Yavsc.Api] Correction des erreurs 400/500 sur les routes billing (`Rdv`, `Brush`, `MBrush`) en imposant `ClientId` depuis l'utilisateur authentifie et en ignorant les champs server-owned lors de la validation modele. -* [Yavsc.Api] Correction du `PUT /api/v1/billing/Rdv/{id}`: mise a jour controlee de l'entite existante (et non remplacement brut du graphe JSON), ce qui supprime les `BadRequest` parasites. -* [Yavsc.Api] Correction PostgreSQL `timestamptz` sur RDV: normalisation UTC de `EventDate` sur `POST/PUT /api/v1/billing/Rdv` pour eviter l'erreur `Cannot write DateTime with Kind=Local`. -* [Yavsc.Api] Correction du flux FrontOffice accept/reject de query: sauvegarde avec contexte utilisateur et fallback d'injection pour `IBillingService` afin d'eviter les erreurs serveur en environnement de test. -* [Yavsc.Blogs] Correction des `BadRequest` sur `POST/PUT /api/v1/blogspot` avec payload JSON (PostIt): les proprietes de navigation/serveur (`Author`, `Tags`, `Comments`, audit) ne bloquent plus la validation. -* [Yavsc.Org] Correction du flux MVC de creation de commentaire: `SaveChangesAsync(userId)` est utilise pour renseigner les champs d'audit requis (`UserCreated`/`UserModified`). -* [Yavsc.Api.Test] Stabilisation des fixtures de seed billing: remplissage des metadonnees d'audit (`UserCreated`, `UserModified`, dates) pour eviter les echecs SQLite `NOT NULL`. ## [1.0.8-rc13] - unstable diff --git a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs index d3d1d5694..a7f2b0844 100644 --- a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs @@ -45,8 +45,10 @@ public class BillingQueriesPageViewModelTests Assert.Equal(2, vm.Queries.Count); Assert.All(vm.Queries, q => Assert.DoesNotContain("Rejected", q.StatusLabel, StringComparison.OrdinalIgnoreCase)); Assert.Contains("lecture seule", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); + Assert.False(vm.CanOpenDetails); - Assert.True(vm.Queries.Count > 0); + vm.SelectedQuery = vm.Queries[0]; + Assert.False(vm.OpenSelectedQueryCommand.CanExecute(null)); } private sealed class StubBillingApi : IYavscApiClient diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index c0831b290..00a8bde04 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -57,7 +57,6 @@ public static class ServiceCollectionHelpers services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); // ViewModels services.AddSingleton(settings); services.AddSingleton(api); diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index f866e1bac..0055a9e60 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -19,7 +19,7 @@ namespace PostIt; public class ViewLocator : IDataTemplate { - public Control Build(object? data) + public Control Build(object? data) { try { @@ -49,12 +49,10 @@ public class ViewLocator : IDataTemplate AddCircleMemberDialogViewModel => services.GetRequiredService(), CirclesPageViewModel => services.GetRequiredService(), PostAclDialogViewModel => services.GetRequiredService(), - BillingQueriesPageViewModel => services.GetRequiredService(), - BillingQueryDetailsPageViewModel => services.GetRequiredService(), null => new TextBlock { Text = "No view for " }, _ => new TextBlock { Text = $"No view for {data.GetType().Name}" } }; } - public bool Match(object? data) => data is ViewModelBase; + public bool Match(object? data) => data is ViewModelBase; } diff --git a/src/PostIt/PostIt/ViewModels/Layout/ActionStatusViewModelExtensions.cs b/src/PostIt/PostIt/ViewModels/ActionStatusViewModelExtensions.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Layout/ActionStatusViewModelExtensions.cs rename to src/PostIt/PostIt/ViewModels/ActionStatusViewModelExtensions.cs diff --git a/src/PostIt/PostIt/ViewModels/Activity/ActivitiesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Activity/ActivitiesPageViewModel.cs rename to src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs deleted file mode 100644 index cd13daaf1..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly BillingApiClient _billingClient; - private readonly BillingQueryDetailsDto _details; - - public ActivityInfo Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummary Form { get; } - public bool IsReadOnly { get; } - - public long Id => _details.Id; - public string Title => $"Detail commande #{_details.Id}"; - public string ContextLabel => $"{Performer.UserName} · {Activity.Name} · {Form.Title}"; - public string StatusLabel => _details.Status.ToString(); - public string StatusGlyph => GetStatusGlyph(_details.Status); - public string StatusBadgeBackground => GetStatusBadgeBackground(_details.Status); - public string StatusBadgeBorder => GetStatusBadgeBorder(_details.Status); - public string StatusBadgeForeground => GetStatusBadgeForeground(_details.Status); - public string TitleForeground => StatusBadgeForeground; - public string BillingCode => _details.BillingCode; - public string Description => EmptyAsPlaceholder(_details.Description, "(sans description)"); - public string Reason => EmptyAsPlaceholder(_details.Reason, "(aucun motif)"); - public string AdditionalInfo => EmptyAsPlaceholder(_details.AdditionalInfo, "(aucune info complementaire)"); - public string ClientId => EmptyAsPlaceholder(_details.ClientId, "(non renseigne)"); - public string EventDateLabel => _details.EventDate?.ToLocalTime().ToString("f") ?? "Date non precisee"; - public string ConsentLabel => _details.Consent ? "Oui" : "Non"; - public string ProvisionalLabel => _details.Provisional.HasValue ? _details.Provisional.Value.ToString("0.00") : "(non renseigne)"; - public string LocationLabel => BuildLocationLabel(_details.Location); - public string PrestationsLabel => BuildPrestationsLabel(_details); - public bool CanEdit => !IsReadOnly; - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingQueryDetailsPageViewModel( - ActivityInfo activity, - ActivityUserDisplayItem performer, - CommandFormSummary form, - BillingApiClient billingClient, - BillingQueryDetailsDto details, - bool isReadOnly) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - _details = details ?? throw new ArgumentNullException(nameof(details)); - IsReadOnly = isReadOnly; - - this.SetInfoStatus("Details de commande charges."); - } - - [RelayCommand] - private async Task OpenEditorAsync() - { - if (IsReadOnly) - { - this.SetWarningStatus("Mode lecture seule: edition desactivee."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var vm = Form.CreateCommandPageViewModel(Activity, Performer, _billingClient); - if (vm is null) - { - this.SetWarningStatus("Ce formulaire n'est pas encore pris en charge en edition."); - return; - } - - await vm.InitializeAsync(_details).ConfigureAwait(true); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'ouverture en edition: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - private async Task BackAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - await app.GoBackAsync().ConfigureAwait(true); - } - - private static string EmptyAsPlaceholder(string? value, string placeholder) - => string.IsNullOrWhiteSpace(value) ? placeholder : value; - - private static string BuildLocationLabel(BillingLocationDto? location) - { - if (location is null) - { - return "(non renseignee)"; - } - - var text = EmptyAsPlaceholder(location.Address, "adresse vide"); - if (location.Latitude.HasValue && location.Longitude.HasValue) - { - text += $" ({location.Latitude.Value:0.####}, {location.Longitude.Value:0.####})"; - } - - return text; - } - - private static string BuildPrestationsLabel(BillingQueryDetailsDto details) - { - if (details.PrestationIds.Count > 0) - { - return string.Join(", ", details.PrestationIds.Select(static id => id.ToString())); - } - - return details.PrestationId.HasValue - ? details.PrestationId.Value.ToString() - : "(aucune)"; - } - - private static string GetStatusBadgeBackground(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#E6F7EC", - QueryStatus.InProgress => "#FFF4D6", - QueryStatus.Rejected => "#FDECEA", - QueryStatus.Failed => "#ECEFF1", - QueryStatus.Success => "#E8F8EF", - _ => "#EAF3FF", - }; - - private static string GetStatusBadgeBorder(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#2E7D32", - QueryStatus.InProgress => "#B26A00", - QueryStatus.Rejected => "#C62828", - QueryStatus.Failed => "#607D8B", - QueryStatus.Success => "#1E8E3E", - _ => "#2A5EA8", - }; - - private static string GetStatusBadgeForeground(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#1B5E20", - QueryStatus.InProgress => "#7A4A00", - QueryStatus.Rejected => "#8E0000", - QueryStatus.Failed => "#37474F", - QueryStatus.Success => "#145A2A", - _ => "#1A4178", - }; - - private static string GetStatusGlyph(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "OK", - QueryStatus.InProgress => "~", - QueryStatus.Rejected => "!", - QueryStatus.Failed => "X", - QueryStatus.Success => "V", - _ => "i", - }; -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/Activity/ActivityUserDisplayItem.cs b/src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Activity/ActivityUserDisplayItem.cs rename to src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs diff --git a/src/PostIt/PostIt/ViewModels/ACL/AddCircleMemberDialogViewModel.cs b/src/PostIt/PostIt/ViewModels/AddCircleMemberDialogViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/ACL/AddCircleMemberDialogViewModel.cs rename to src/PostIt/PostIt/ViewModels/AddCircleMemberDialogViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs similarity index 92% rename from src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs rename to src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs index bccf97167..d3c097f43 100644 --- a/src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs @@ -43,7 +43,7 @@ public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusV ? $"Demandes en cours ({Form.Title})" : $"Commandes {Form.Title}"; public string ContextLabel => $"{Performer.UserName} · {Activity.Name}"; - public bool CanOpenDetails => true; + public bool CanOpenDetails => !IsReadOnly; public override bool CanNavigateNext { @@ -75,7 +75,7 @@ public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusV public Task InitializeAsync() => RefreshAsync(); - private bool CanOpenSelectedQuery() => SelectedQuery is not null; + private bool CanOpenSelectedQuery() => !IsReadOnly && SelectedQuery is not null; [RelayCommand] public async Task RefreshAsync() @@ -114,6 +114,12 @@ public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusV [RelayCommand(CanExecute = nameof(CanOpenSelectedQuery))] public async Task OpenSelectedQueryAsync() { + if (IsReadOnly) + { + this.SetWarningStatus("Mode lecture seule: l'ouverture en modification est désactivée."); + return; + } + if (SelectedQuery is null) { this.SetWarningStatus("Sélectionnez une commande."); @@ -130,13 +136,8 @@ public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusV try { var details = await _billingClient.GetQueryAsync(Form.ActionName, SelectedQuery.Id).ConfigureAwait(true); - var vm = new BillingQueryDetailsPageViewModel( - Activity, - Performer, - Form, - _billingClient, - details, - IsReadOnly); + var vm = Form.CreateCommandPageViewModel(Activity, Performer, _billingClient); + await vm!.InitializeAsync(details).ConfigureAwait(true); await app.PushPageAsync(vm).ConfigureAwait(true); } catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDisplayItem.cs b/src/PostIt/PostIt/ViewModels/BillingQueryDisplayItem.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Activity/BillingQueryDisplayItem.cs rename to src/PostIt/PostIt/ViewModels/BillingQueryDisplayItem.cs diff --git a/src/PostIt/PostIt/ViewModels/ACL/CirclesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/CirclesPageViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/ACL/CirclesPageViewModel.cs rename to src/PostIt/PostIt/ViewModels/CirclesPageViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Activity/CommandFormsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/CommandFormsPageViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Activity/CommandFormsPageViewModel.cs rename to src/PostIt/PostIt/ViewModels/CommandFormsPageViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs rename to src/PostIt/PostIt/ViewModels/HomePageViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Layout/MainViewModel.cs b/src/PostIt/PostIt/ViewModels/MainViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Layout/MainViewModel.cs rename to src/PostIt/PostIt/ViewModels/MainViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/ACL/PostAclDialogViewModel.cs b/src/PostIt/PostIt/ViewModels/PostAclDialogViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/ACL/PostAclDialogViewModel.cs rename to src/PostIt/PostIt/ViewModels/PostAclDialogViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Activity/SelectableHairPrestationItem.cs b/src/PostIt/PostIt/ViewModels/SelectableHairPrestationItem.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Activity/SelectableHairPrestationItem.cs rename to src/PostIt/PostIt/ViewModels/SelectableHairPrestationItem.cs diff --git a/src/PostIt/PostIt/ViewModels/Layout/SessionStatusViewModel.cs b/src/PostIt/PostIt/ViewModels/SessionStatusViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Layout/SessionStatusViewModel.cs rename to src/PostIt/PostIt/ViewModels/SessionStatusViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Signature/SignaturePageViewModel.cs b/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Signature/SignaturePageViewModel.cs rename to src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs diff --git a/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs b/src/PostIt/PostIt/ViewModels/StatusNotice.cs similarity index 100% rename from src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs rename to src/PostIt/PostIt/ViewModels/StatusNotice.cs diff --git a/src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml b/src/PostIt/PostIt/Views/ActivitiesPage.axaml similarity index 100% rename from src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml rename to src/PostIt/PostIt/Views/ActivitiesPage.axaml diff --git a/src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml.cs b/src/PostIt/PostIt/Views/ActivitiesPage.axaml.cs similarity index 100% rename from src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml.cs rename to src/PostIt/PostIt/Views/ActivitiesPage.axaml.cs diff --git a/src/PostIt/PostIt/Views/Activity/BillingQueriesPage.axaml b/src/PostIt/PostIt/Views/Activity/BillingQueriesPage.axaml deleted file mode 100644 index 52b255129..000000000 --- a/src/PostIt/PostIt/Views/Activity/BillingQueriesPage.axaml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - -