refactoring the command form creation
This commit is contained in:
parent
2a36c19610
commit
fc820362b9
24 changed files with 940 additions and 698 deletions
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
|
@ -7,7 +7,7 @@
|
||||||
"fileLocation": ["relative", "${workspaceFolder}"],
|
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||||
"source": "dotnet",
|
"source": "dotnet",
|
||||||
"pattern": {
|
"pattern": {
|
||||||
"regexp": "^\\s+(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.+): (.*)$",
|
"regexp": "^\\s*(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.*)$",
|
||||||
"file": 1,
|
"file": 1,
|
||||||
"line": 2,
|
"line": 2,
|
||||||
"column": 3,
|
"column": 3,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,19 @@ Les tests sont répartis en :
|
||||||
item « Tests d'intégration smoke par BC ».
|
item « Tests d'intégration smoke par BC ».
|
||||||
- `src/PostIt.Tests/` — tests unitaires du client desktop PostIt.
|
- `src/PostIt.Tests/` — tests unitaires du client desktop PostIt.
|
||||||
|
|
||||||
|
## Onboarding assiste par agents IA
|
||||||
|
|
||||||
|
Pour accelerer la prise en main du depot avec Copilot/Plan/Explore :
|
||||||
|
|
||||||
|
- Parcours pas-a-pas : [doc/onboarding-agents.md](./doc/onboarding-agents.md)
|
||||||
|
- Playbook d'usage des agents : [doc/agent-playbook.md](./doc/agent-playbook.md)
|
||||||
|
- Matrice intentions -> agent -> preuves : [doc/agent-intent-matrix.md](./doc/agent-intent-matrix.md)
|
||||||
|
|
||||||
|
Regle minimale en contribution assistee par agent :
|
||||||
|
- expliciter l'impact architecture,
|
||||||
|
- justifier le niveau de tests execute,
|
||||||
|
- documenter les risques residuels.
|
||||||
|
|
||||||
## Le CHANGELOG.md
|
## Le CHANGELOG.md
|
||||||
|
|
||||||
Le `CHANGELOG.md` est un document de changement de version
|
Le `CHANGELOG.md` est un document de changement de version
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ sous [`doc/`](./doc/). Voir l'[index de la documentation](./doc/README.md)
|
||||||
pour le sommaire complet. La racine de l'architecture est
|
pour le sommaire complet. La racine de l'architecture est
|
||||||
[Architecture.md](./doc/Architecture.md).
|
[Architecture.md](./doc/Architecture.md).
|
||||||
|
|
||||||
|
Pour une prise en main guidee avec agents IA:
|
||||||
|
- parcours onboarding: [doc/onboarding-agents.md](./doc/onboarding-agents.md)
|
||||||
|
- playbook d'usage: [doc/agent-playbook.md](./doc/agent-playbook.md)
|
||||||
|
|
||||||
|
|
||||||
# Construction et déploiement
|
# Construction et déploiement
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ La racine de l'architecture est [Architecture.md](Architecture.md).
|
||||||
| [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding |
|
| [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding |
|
||||||
| [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) |
|
| [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) |
|
||||||
| [testing.md](testing.md) | Stratégie de test : conventions des dossiers, EF Core in-memory, auth stubs, scaffold partagé |
|
| [testing.md](testing.md) | Stratégie de test : conventions des dossiers, EF Core in-memory, auth stubs, scaffold partagé |
|
||||||
|
| [onboarding-agents.md](onboarding-agents.md) | Parcours pas-à-pas pour prise en main agents IA + architecture + tests |
|
||||||
|
| [agent-playbook.md](agent-playbook.md) | Playbook d'usage de Copilot, Plan, Explore avec scénarios et anti-patterns |
|
||||||
|
| [agent-intent-matrix.md](agent-intent-matrix.md) | Matrice intentions développeur -> agent -> preuves attendues |
|
||||||
|
|
||||||
## Roadmap & design exploration
|
## Roadmap & design exploration
|
||||||
|
|
||||||
|
|
|
||||||
20
doc/agent-intent-matrix.md
Normal file
20
doc/agent-intent-matrix.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Matrice intentions -> agent -> preuves
|
||||||
|
|
||||||
|
Cette matrice aide a choisir rapidement l'agent adapte et a exiger
|
||||||
|
une sortie verifiable.
|
||||||
|
|
||||||
|
| Intention developpeur | Agent principal | Entrees minimales | Sortie minimale attendue | Verification |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Comprendre un BC avant changement | Explore | BC cible, profondeur, contrainte de perimetre | Composants, points d'entree, tests relies, risques | Lire les fichiers cites + confirmer tests proposes |
|
||||||
|
| Decomposer une tache transverse | Plan | Objectif, contraintes, definition of done | Etapes ordonnees, dependances, criteres de verif | Verifier que chaque etape a une preuve observable |
|
||||||
|
| Implementer une modif locale | Copilot | Fichier cible, comportement attendu, conventions | Patch minimal, justification courte | Build/test du projet impacte |
|
||||||
|
| Ajouter un test smoke | Copilot (+Explore) | Route/endpoint, projet de test cible | Test + commande cible | Execution test cible |
|
||||||
|
| Corriger une regression | Plan + Copilot | Symptome, zone suspecte, test attendu | Fix + test NonRegression | Test rouge avant, vert apres |
|
||||||
|
| Diagnostiquer flux PostIt/OIDC | Explore + Plan | Flux, symptome, plateforme | Carte du flux + hypotheses testables | Verification manuelle + tests existants |
|
||||||
|
|
||||||
|
## Regles d'arbitrage
|
||||||
|
|
||||||
|
- Si l'intention est "comprendre": commencer par Explore.
|
||||||
|
- Si l'intention est "orchestrer": commencer par Plan.
|
||||||
|
- Si l'intention est "produire": utiliser Copilot apres cadrage.
|
||||||
|
- Si une sortie n'inclut pas de preuve, elle est incomplete.
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using PostIt.Helpers;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
using PostIt.ViewModels.Commands;
|
||||||
using Yavsc;
|
using Yavsc;
|
||||||
using Yavsc.Abstract.Workflow;
|
using Yavsc.Abstract.Workflow;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
@ -16,19 +17,19 @@ public class BillingCommandPageViewModelTests
|
||||||
{
|
{
|
||||||
var api = new RecordingApi();
|
var api = new RecordingApi();
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "dev", Name = "Développement" },
|
new ActivityInfo { Code = "dev", Name = "Développement" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
||||||
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" },
|
client) as RdvViewModel;
|
||||||
client)
|
|
||||||
{
|
vm.EventDate = DateTime.Parse("2026-09-02 14:30");
|
||||||
EventDateText = "2026-09-02 14:30",
|
vm.Reason = "Point de cadrage";
|
||||||
Reason = "Point de cadrage",
|
vm.Address = "1 rue du Test";
|
||||||
Address = "1 rue du Test",
|
vm.Latitude = 48.8566;
|
||||||
LatitudeText = "48.8566",
|
vm.Longitude = 2.3522;
|
||||||
LongitudeText = "2.3522",
|
vm.Consent = true;
|
||||||
Consent = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vm.SubmitCommand.ExecuteAsync(null);
|
await vm.SubmitCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
|
@ -47,10 +48,12 @@ public class BillingCommandPageViewModelTests
|
||||||
{
|
{
|
||||||
var api = new RecordingApi();
|
var api = new RecordingApi();
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
|
||||||
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 13, ActionName = "Book", Title = "Réservation" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "book", Name = "Book" },
|
new ActivityInfo { Code = "book", Name = "Book" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
||||||
new CommandFormSummary { Id = 13, ActionName = "Book", Title = "Réservation" },
|
|
||||||
client);
|
client);
|
||||||
|
|
||||||
await vm.SubmitCommand.ExecuteAsync(null);
|
await vm.SubmitCommand.ExecuteAsync(null);
|
||||||
|
|
@ -64,19 +67,19 @@ public class BillingCommandPageViewModelTests
|
||||||
{
|
{
|
||||||
var api = new RecordingApi();
|
var api = new RecordingApi();
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
|
||||||
|
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "dev", Name = "Développement" },
|
new ActivityInfo { Code = "dev", Name = "Développement" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
||||||
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" },
|
client) as RdvViewModel;
|
||||||
client)
|
vm.EventDate = DateTime.Parse("2026-09-02 14:30");
|
||||||
{
|
vm.Reason = "Point de cadrage";
|
||||||
EventDateText = "2026-09-02 14:30",
|
vm.Address = "1 rue du Test";
|
||||||
Reason = "Point de cadrage",
|
vm.Latitude = 0;
|
||||||
Address = "1 rue du Test",
|
vm.Longitude = 0;
|
||||||
LatitudeText = string.Empty,
|
vm.Consent = true;
|
||||||
LongitudeText = string.Empty,
|
|
||||||
Consent = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vm.SubmitCommand.ExecuteAsync(null);
|
await vm.SubmitCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
|
@ -97,16 +100,17 @@ public class BillingCommandPageViewModelTests
|
||||||
|
|
||||||
var api = new RecordingApi();
|
var api = new RecordingApi();
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "dev", Name = "Développement" },
|
new ActivityInfo { Code = "dev", Name = "Développement" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" },
|
||||||
new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" },
|
client) as RdvViewModel;
|
||||||
client);
|
|
||||||
|
|
||||||
await vm.UseCurrentLocationCommand.ExecuteAsync(null);
|
await vm.UseCurrentLocationCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
Assert.Equal("48.8566", vm.LatitudeText);
|
Assert.Equal(48.8566, vm.Latitude);
|
||||||
Assert.Equal("2.3522", vm.LongitudeText);
|
Assert.Equal(2.3522, vm.Longitude);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -126,19 +130,18 @@ public class BillingCommandPageViewModelTests
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "brush", Name = "Brush" },
|
new ActivityInfo { Code = "brush", Name = "Brush" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
||||||
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" },
|
client) as BrushViewModel;
|
||||||
client)
|
vm.EventDate = DateTime.Parse("2026-09-02 14:30");
|
||||||
{
|
vm.Address = "1 rue du Test";
|
||||||
EventDateText = "2026-09-02 14:30",
|
vm.Latitude = 48.8566;
|
||||||
Address = "1 rue du Test",
|
vm.Longitude = 2.3522;
|
||||||
LatitudeText = "48.8566",
|
vm.Consent = true;
|
||||||
LongitudeText = "2.3522",
|
vm.AdditionalInfo = "Prévoir shampoing";
|
||||||
Consent = true,
|
|
||||||
AdditionalInfo = "Prévoir shampoing",
|
|
||||||
};
|
|
||||||
|
|
||||||
await vm.InitializeAsync();
|
await vm.InitializeAsync();
|
||||||
vm.SelectedPrestation = vm.AvailablePrestations[1];
|
vm.SelectedPrestation = vm.AvailablePrestations[1];
|
||||||
|
|
@ -162,18 +165,17 @@ public class BillingCommandPageViewModelTests
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "mbrush", Name = "MBrush" },
|
new ActivityInfo { Code = "mbrush", Name = "MBrush" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" },
|
new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" },
|
||||||
new CommandFormSummary { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" },
|
client) as PostIt.ViewModels.Commands.BrushViewModel;
|
||||||
client)
|
vm.EventDate = DateTime.Parse("2026-09-03 10:00");
|
||||||
{
|
vm.Address = "2 rue du Test";
|
||||||
EventDateText = "2026-09-03 10:00",
|
vm.Latitude = 48.8567;
|
||||||
Address = "2 rue du Test",
|
vm.Longitude = 2.3523;
|
||||||
LatitudeText = "48.8567",
|
vm.Consent = true;
|
||||||
LongitudeText = "2.3523",
|
|
||||||
Consent = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await vm.InitializeAsync();
|
await vm.InitializeAsync();
|
||||||
vm.MultiPrestations[0].IsSelected = true;
|
vm.MultiPrestations[0].IsSelected = true;
|
||||||
|
|
@ -200,12 +202,12 @@ public class BillingCommandPageViewModelTests
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
var client = new BillingApiClient(api, "https://business.example/api/v1/");
|
||||||
var vm = new BillingCommandPageViewModel(
|
var vm =
|
||||||
|
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" }
|
||||||
|
.CreateCommandPageViewModel(
|
||||||
new ActivityInfo { Code = "brush", Name = "Brush" },
|
new ActivityInfo { Code = "brush", Name = "Brush" },
|
||||||
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" },
|
||||||
new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" },
|
client) as BrushViewModel;
|
||||||
client);
|
|
||||||
|
|
||||||
await vm.InitializeAsync(new BillingQueryDetailsDto
|
await vm.InitializeAsync(new BillingQueryDetailsDto
|
||||||
{
|
{
|
||||||
Id = 77,
|
Id = 77,
|
||||||
|
|
|
||||||
45
src/PostIt/PostIt/Helpers/FormHelpers.cs
Normal file
45
src/PostIt/PostIt/Helpers/FormHelpers.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using PostIt.ViewModels;
|
||||||
|
using Yavsc.Abstract.Workflow;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
namespace PostIt.Helpers;
|
||||||
|
|
||||||
|
public static class FormHelpers
|
||||||
|
{
|
||||||
|
public static BillingCommandPageViewModel
|
||||||
|
CreateCommandPageViewModel(
|
||||||
|
this CommandFormSummary form,
|
||||||
|
ActivityInfo activity,
|
||||||
|
ActivityUserDisplayItem performer,
|
||||||
|
BillingApiClient billingClient)
|
||||||
|
{
|
||||||
|
|
||||||
|
string formVMName = form.ActionName + "ViewModel";
|
||||||
|
|
||||||
|
string formOnActivityVMName = activity.Code + formVMName;
|
||||||
|
|
||||||
|
var vmType = Type.GetType(formOnActivityVMName);
|
||||||
|
if (vmType == null)
|
||||||
|
{
|
||||||
|
vmType = Type.GetType(formVMName);
|
||||||
|
}
|
||||||
|
if (vmType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot find type '{formOnActivityVMName}' or '{formVMName}'");
|
||||||
|
}
|
||||||
|
if (!typeof(BillingCommandPageViewModel).IsAssignableFrom(vmType))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"The type '{formOnActivityVMName}' or '{formVMName}' is not a BillingCommandPageViewModel");
|
||||||
|
}
|
||||||
|
|
||||||
|
var vm = Activator.CreateInstance(vmType);
|
||||||
|
|
||||||
|
if (vm == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot create instance of '{formOnActivityVMName}' or '{formVMName}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm as BillingCommandPageViewModel ?? throw new InvalidOperationException($"The type '{formOnActivityVMName}' or '{formVMName}' is not a BillingCommandPageViewModel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.Services;
|
using PostIt.Services;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
using PostIt.Views.Commands;
|
||||||
using Yavsc.Api.Client;
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
namespace PostIt.Helpers;
|
namespace PostIt.Helpers;
|
||||||
|
|
@ -49,7 +50,8 @@ public static class ServiceCollectionHelpers
|
||||||
services.AddSingleton<CirclesPage>();
|
services.AddSingleton<CirclesPage>();
|
||||||
services.AddSingleton<ActivitiesPage>();
|
services.AddSingleton<ActivitiesPage>();
|
||||||
services.AddTransient<CommandFormsPage>();
|
services.AddTransient<CommandFormsPage>();
|
||||||
services.AddTransient<BillingCommandPage>();
|
services.AddTransient<RdvPage>();
|
||||||
|
services.AddTransient<BrushPage>();
|
||||||
services.AddTransient<BillingQueriesPage>();
|
services.AddTransient<BillingQueriesPage>();
|
||||||
// ViewModels
|
// ViewModels
|
||||||
services.AddSingleton(settings);
|
services.AddSingleton(settings);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using PostIt.ViewModels;
|
using PostIt.ViewModels;
|
||||||
|
using PostIt.ViewModels.Commands;
|
||||||
using PostIt.Views;
|
using PostIt.Views;
|
||||||
|
using PostIt.Views.Commands;
|
||||||
|
|
||||||
namespace PostIt;
|
namespace PostIt;
|
||||||
|
|
||||||
|
|
@ -41,8 +43,7 @@ public class ViewLocator : IDataTemplate
|
||||||
HomePageViewModel => services.GetRequiredService<HomePage>(),
|
HomePageViewModel => services.GetRequiredService<HomePage>(),
|
||||||
ActivitiesPageViewModel => services.GetRequiredService<ActivitiesPage>(),
|
ActivitiesPageViewModel => services.GetRequiredService<ActivitiesPage>(),
|
||||||
CommandFormsPageViewModel => services.GetRequiredService<CommandFormsPage>(),
|
CommandFormsPageViewModel => services.GetRequiredService<CommandFormsPage>(),
|
||||||
BillingCommandPageViewModel => services.GetRequiredService<BillingCommandPage>(),
|
RdvViewModel => services.GetRequiredService<RdvPage>(),
|
||||||
BillingQueriesPageViewModel => services.GetRequiredService<BillingQueriesPage>(),
|
|
||||||
SignaturePageViewModel => services.GetRequiredService<SignaturePage>(),
|
SignaturePageViewModel => services.GetRequiredService<SignaturePage>(),
|
||||||
AddCircleMemberDialogViewModel => services.GetRequiredService<AddCircleMemberDialog>(),
|
AddCircleMemberDialogViewModel => services.GetRequiredService<AddCircleMemberDialog>(),
|
||||||
CirclesPageViewModel => services.GetRequiredService<CirclesPage>(),
|
CirclesPageViewModel => services.GetRequiredService<CirclesPage>(),
|
||||||
|
|
|
||||||
|
|
@ -1,490 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using PostIt.Services;
|
|
||||||
using Yavsc;
|
|
||||||
using Yavsc.Abstract.Workflow;
|
|
||||||
using Yavsc.Api.Client;
|
|
||||||
using Yavsc.Models.Billing;
|
|
||||||
using Yavsc.Models.Haircut;
|
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
|
||||||
|
|
||||||
public partial class BillingCommandPageViewModel : ViewModelBase
|
|
||||||
{
|
|
||||||
private readonly BillingApiClient _billingClient;
|
|
||||||
|
|
||||||
public ActivityInfo Activity { get; }
|
|
||||||
public ActivityUserDisplayItem Performer { get; }
|
|
||||||
public CommandFormSummary Form { get; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial bool IsBusy { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string StatusMessage { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string EventDateText { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string Reason { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string Address { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string LatitudeText { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string LongitudeText { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial bool Consent { get; set; } = true;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial ObservableCollection<HairPrestationDto> AvailablePrestations { get; set; } = new();
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial HairPrestationDto? SelectedPrestation { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial ObservableCollection<SelectableHairPrestationItem> MultiPrestations { get; set; } = new();
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial string AdditionalInfo { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial long? ExistingQueryId { get; set; }
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
public partial QueryStatus CommandStatus { get; set; } = QueryStatus.Inserted;
|
|
||||||
|
|
||||||
public bool CanUseCurrentLocation => IsSupported && !IsBusy;
|
|
||||||
|
|
||||||
public string Title => Form.Title;
|
|
||||||
public string PerformerLabel => Performer.UserName;
|
|
||||||
public string ActivityLabel => Activity.Name;
|
|
||||||
public bool IsSupported => IsRdv || IsBrush || IsMultiBrush;
|
|
||||||
public bool IsRdv => string.Equals(Form.ActionName, BillingCodes.Rdv, StringComparison.Ordinal);
|
|
||||||
public bool IsBrush => string.Equals(Form.ActionName, BillingCodes.Brush, StringComparison.Ordinal);
|
|
||||||
public bool IsMultiBrush => string.Equals(Form.ActionName, BillingCodes.MBrush, StringComparison.Ordinal);
|
|
||||||
public bool ShowsReason => IsRdv;
|
|
||||||
public bool ShowsAdditionalInfo => IsBrush;
|
|
||||||
public bool ShowsSinglePrestation => IsBrush;
|
|
||||||
public bool ShowsMultiplePrestations => IsMultiBrush;
|
|
||||||
public string BillingRoute => $"/billing/{Form.ActionName}";
|
|
||||||
public bool IsEditingExisting => ExistingQueryId.HasValue;
|
|
||||||
public string SubmitLabel => IsEditingExisting ? "Mettre à jour la commande" : "Poster la commande";
|
|
||||||
public string SupportMessage => IsSupported
|
|
||||||
? IsRdv
|
|
||||||
? "Complétez les informations du rendez-vous puis postez la commande."
|
|
||||||
: IsBrush
|
|
||||||
? "Choisissez une prestation coiffure puis postez la commande."
|
|
||||||
: "Choisissez une ou plusieurs prestations coiffure puis postez la commande."
|
|
||||||
: $"Le formulaire {Form.ActionName} n'est pas encore pris en charge dans PostIt.";
|
|
||||||
|
|
||||||
public override bool CanNavigateNext
|
|
||||||
{
|
|
||||||
get => false;
|
|
||||||
protected set { _ = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanNavigatePrevious
|
|
||||||
{
|
|
||||||
get => true;
|
|
||||||
protected set { _ = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public BillingCommandPageViewModel(
|
|
||||||
ActivityInfo activity,
|
|
||||||
ActivityUserDisplayItem performer,
|
|
||||||
CommandFormSummary form,
|
|
||||||
BillingApiClient billingClient)
|
|
||||||
{
|
|
||||||
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));
|
|
||||||
|
|
||||||
EventDateText = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
|
|
||||||
StatusMessage = SupportMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnExistingQueryIdChanged(long? value)
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(IsEditingExisting));
|
|
||||||
OnPropertyChanged(nameof(SubmitLabel));
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void OnIsBusyChanged(bool value)
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(CanUseCurrentLocation));
|
|
||||||
UseCurrentLocationCommand.NotifyCanExecuteChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitializeAsync(BillingQueryDetailsDto? existingQuery = null)
|
|
||||||
{
|
|
||||||
if (!IsBrush && !IsMultiBrush)
|
|
||||||
{
|
|
||||||
if (existingQuery is not null)
|
|
||||||
{
|
|
||||||
ApplyExistingQuery(existingQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBusy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var prestations = await _billingClient.GetHairPrestationsAsync(Form.ActionName).ConfigureAwait(true);
|
|
||||||
AvailablePrestations = new ObservableCollection<HairPrestationDto>(prestations ?? new List<HairPrestationDto>());
|
|
||||||
SelectedPrestation = AvailablePrestations.FirstOrDefault();
|
|
||||||
MultiPrestations = new ObservableCollection<SelectableHairPrestationItem>(AvailablePrestations.Select(SelectableHairPrestationItem.FromDto));
|
|
||||||
|
|
||||||
StatusMessage = AvailablePrestations.Count == 0
|
|
||||||
? "Aucune prestation coiffure disponible."
|
|
||||||
: SupportMessage;
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
|
||||||
{
|
|
||||||
StatusMessage = "Accès refusé au catalogue de prestations (scope 'api'). Déconnectez puis reconnectez-vous.";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
StatusMessage = $"Erreur lors du chargement des prestations: {ex.Message}";
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingQuery is not null)
|
|
||||||
{
|
|
||||||
ApplyExistingQuery(existingQuery);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private async Task SubmitAsync()
|
|
||||||
{
|
|
||||||
if (!IsSupported)
|
|
||||||
{
|
|
||||||
StatusMessage = SupportMessage;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Consent)
|
|
||||||
{
|
|
||||||
StatusMessage = "Le consentement est requis pour poster la commande.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryParseEventDate(out var eventDate))
|
|
||||||
{
|
|
||||||
StatusMessage = "La date doit être saisie au format yyyy-MM-dd HH:mm.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsRdv && string.IsNullOrWhiteSpace(Reason))
|
|
||||||
{
|
|
||||||
StatusMessage = "Le motif du rendez-vous est requis.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Address))
|
|
||||||
{
|
|
||||||
StatusMessage = "L'adresse du rendez-vous est requise.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryParseCoordinates(out var latitude, out var longitude, out var coordinateError))
|
|
||||||
{
|
|
||||||
StatusMessage = coordinateError;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBusy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var address = Address.Trim();
|
|
||||||
var locationPayload = BuildLocationPayload(address, latitude, longitude);
|
|
||||||
|
|
||||||
var payload = new BillingQueryDetailsDto
|
|
||||||
{
|
|
||||||
Id = ExistingQueryId ?? 0,
|
|
||||||
BillingCode = Form.ActionName,
|
|
||||||
ActivityCode = Activity.Code,
|
|
||||||
PerformerId = Performer.PerformerId,
|
|
||||||
Consent = Consent,
|
|
||||||
EventDate = eventDate,
|
|
||||||
Status = CommandStatus,
|
|
||||||
Reason = Reason.Trim(),
|
|
||||||
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(),
|
|
||||||
Location = new BillingLocationDto
|
|
||||||
{
|
|
||||||
Address = address,
|
|
||||||
Latitude = latitude,
|
|
||||||
Longitude = longitude,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IsRdv)
|
|
||||||
{
|
|
||||||
if (IsEditingExisting)
|
|
||||||
{
|
|
||||||
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _billingClient.CreateAsync(Form.ActionName, new
|
|
||||||
{
|
|
||||||
ActivityCode = Activity.Code,
|
|
||||||
PerformerId = Performer.PerformerId,
|
|
||||||
Consent,
|
|
||||||
EventDate = eventDate,
|
|
||||||
Location = locationPayload,
|
|
||||||
Reason = payload.Reason,
|
|
||||||
Status = payload.Status,
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IsBrush)
|
|
||||||
{
|
|
||||||
if (SelectedPrestation is null)
|
|
||||||
{
|
|
||||||
StatusMessage = "Sélectionnez une prestation coiffure.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.PrestationId = SelectedPrestation.Id;
|
|
||||||
|
|
||||||
if (IsEditingExisting)
|
|
||||||
{
|
|
||||||
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _billingClient.CreateAsync(Form.ActionName, new
|
|
||||||
{
|
|
||||||
ActivityCode = Activity.Code,
|
|
||||||
PerformerId = Performer.PerformerId,
|
|
||||||
Consent,
|
|
||||||
EventDate = (DateTime?)eventDate,
|
|
||||||
Location = locationPayload,
|
|
||||||
PrestationId = SelectedPrestation.Id,
|
|
||||||
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? null : AdditionalInfo.Trim(),
|
|
||||||
Status = payload.Status,
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (IsMultiBrush)
|
|
||||||
{
|
|
||||||
var selectedPrestations = MultiPrestations.Where(x => x.IsSelected).ToList();
|
|
||||||
if (selectedPrestations.Count == 0)
|
|
||||||
{
|
|
||||||
StatusMessage = "Sélectionnez au moins une prestation coiffure.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.PrestationIds = selectedPrestations.Select(x => x.Id).ToList();
|
|
||||||
|
|
||||||
if (IsEditingExisting)
|
|
||||||
{
|
|
||||||
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _billingClient.CreateAsync(Form.ActionName, new
|
|
||||||
{
|
|
||||||
ActivityCode = Activity.Code,
|
|
||||||
PerformerId = Performer.PerformerId,
|
|
||||||
Consent,
|
|
||||||
EventDate = eventDate,
|
|
||||||
Location = locationPayload,
|
|
||||||
Prestations = selectedPrestations.Select(x => new { PrestationId = x.Id }).ToList(),
|
|
||||||
Status = payload.Status,
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage = IsEditingExisting
|
|
||||||
? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}."
|
|
||||||
: $"Commande transmise sur {BillingRoute} pour {Performer.UserName}.";
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
|
||||||
{
|
|
||||||
StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous.";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
StatusMessage = $"Erreur lors de l'envoi: {ex.Message}";
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanUseCurrentLocation))]
|
|
||||||
private async Task UseCurrentLocationAsync()
|
|
||||||
{
|
|
||||||
if (!CanUseCurrentLocation)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBusy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var result = await Platform.TryGetCurrentLocationAsync(default).ConfigureAwait(true);
|
|
||||||
if (!result.IsSuccess || !result.Latitude.HasValue || !result.Longitude.HasValue)
|
|
||||||
{
|
|
||||||
StatusMessage = result.Message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LatitudeText = result.Latitude.Value.ToString(CultureInfo.InvariantCulture);
|
|
||||||
LongitudeText = result.Longitude.Value.ToString(CultureInfo.InvariantCulture);
|
|
||||||
StatusMessage = string.IsNullOrWhiteSpace(Address)
|
|
||||||
? "Position récupérée. Complétez l'adresse puis envoyez la commande."
|
|
||||||
: result.Message;
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
StatusMessage = "La récupération de la position a été annulée.";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
StatusMessage = $"Impossible de récupérer la position: {ex.Message}";
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyExistingQuery(BillingQueryDetailsDto existingQuery)
|
|
||||||
{
|
|
||||||
ExistingQueryId = existingQuery.Id;
|
|
||||||
CommandStatus = existingQuery.Status;
|
|
||||||
Consent = existingQuery.Consent;
|
|
||||||
Reason = existingQuery.Reason ?? string.Empty;
|
|
||||||
AdditionalInfo = existingQuery.AdditionalInfo ?? string.Empty;
|
|
||||||
|
|
||||||
if (existingQuery.EventDate is not null)
|
|
||||||
{
|
|
||||||
EventDateText = existingQuery.EventDate.Value
|
|
||||||
.ToLocalTime()
|
|
||||||
.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingQuery.Location is not null)
|
|
||||||
{
|
|
||||||
Address = existingQuery.Location.Address ?? string.Empty;
|
|
||||||
LatitudeText = existingQuery.Location.Latitude?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
|
||||||
LongitudeText = existingQuery.Location.Longitude?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsBrush && existingQuery.PrestationId is not null)
|
|
||||||
{
|
|
||||||
SelectedPrestation = AvailablePrestations.FirstOrDefault(x => x.Id == existingQuery.PrestationId.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsMultiBrush)
|
|
||||||
{
|
|
||||||
var selectedIds = existingQuery.PrestationIds is null
|
|
||||||
? new HashSet<long>()
|
|
||||||
: new HashSet<long>(existingQuery.PrestationIds);
|
|
||||||
foreach (var item in MultiPrestations)
|
|
||||||
{
|
|
||||||
item.IsSelected = selectedIds.Contains(item.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage = $"Commande #{existingQuery.Id} chargée.";
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryParseEventDate(out DateTime eventDate)
|
|
||||||
{
|
|
||||||
return DateTime.TryParse(
|
|
||||||
EventDateText,
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
DateTimeStyles.AssumeLocal,
|
|
||||||
out eventDate)
|
|
||||||
|| DateTime.TryParse(
|
|
||||||
EventDateText,
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
DateTimeStyles.AssumeLocal,
|
|
||||||
out eventDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryParseCoordinate(string text, out double value)
|
|
||||||
{
|
|
||||||
return double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value)
|
|
||||||
|| double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static object BuildLocationPayload(string address, double? latitude, double? longitude)
|
|
||||||
{
|
|
||||||
if (latitude.HasValue && longitude.HasValue)
|
|
||||||
{
|
|
||||||
return new
|
|
||||||
{
|
|
||||||
Address = address,
|
|
||||||
Latitude = latitude.Value,
|
|
||||||
Longitude = longitude.Value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new
|
|
||||||
{
|
|
||||||
Address = address,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryParseCoordinates(out double? latitude, out double? longitude, out string error)
|
|
||||||
{
|
|
||||||
latitude = null;
|
|
||||||
longitude = null;
|
|
||||||
error = string.Empty;
|
|
||||||
|
|
||||||
var latitudeMissing = string.IsNullOrWhiteSpace(LatitudeText);
|
|
||||||
var longitudeMissing = string.IsNullOrWhiteSpace(LongitudeText);
|
|
||||||
|
|
||||||
if (latitudeMissing && longitudeMissing)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (latitudeMissing != longitudeMissing)
|
|
||||||
{
|
|
||||||
error = "Latitude et longitude doivent être renseignées ensemble, ou laissées vides toutes les deux.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryParseCoordinate(LatitudeText, out var parsedLatitude))
|
|
||||||
{
|
|
||||||
error = "Latitude invalide.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryParseCoordinate(LongitudeText, out var parsedLongitude))
|
|
||||||
{
|
|
||||||
error = "Longitude invalide.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
latitude = parsedLatitude;
|
|
||||||
longitude = parsedLongitude;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -133,7 +133,7 @@ public partial class BillingQueriesPageViewModel : ViewModelBase
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var details = await _billingClient.GetQueryAsync(Form.ActionName, SelectedQuery.Id).ConfigureAwait(true);
|
var details = await _billingClient.GetQueryAsync(Form.ActionName, SelectedQuery.Id).ConfigureAwait(true);
|
||||||
var vm = new BillingCommandPageViewModel(Activity, Performer, Form, _billingClient);
|
var vm = Form.CreateCommandPageViewModel(Activity, Performer, _billingClient);
|
||||||
await vm.InitializeAsync(details).ConfigureAwait(true);
|
await vm.InitializeAsync(details).ConfigureAwait(true);
|
||||||
await app.PushPageAsync(vm).ConfigureAwait(true);
|
await app.PushPageAsync(vm).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ public partial class CommandFormsPageViewModel : ViewModelBase
|
||||||
throw new InvalidOperationException("Application PostIt indisponible.");
|
throw new InvalidOperationException("Application PostIt indisponible.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var vm = SelectedForm.CreateCommandPageViewModel(Activity, Performer, _billingClient);
|
var vm = SelectedForm.CreateCommandPageViewModel(
|
||||||
|
Activity, Performer, _billingClient);
|
||||||
await vm.InitializeAsync();
|
await vm.InitializeAsync();
|
||||||
await app.PushPageAsync(vm);
|
await app.PushPageAsync(vm);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Yavsc;
|
||||||
|
using Yavsc.Abstract.Workflow;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
using Yavsc.Models.Billing;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
|
public abstract partial class BillingCommandPageViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
protected readonly BillingApiClient _billingClient;
|
||||||
|
|
||||||
|
public ActivityInfo Activity { get; }
|
||||||
|
public ActivityUserDisplayItem Performer { get; }
|
||||||
|
public CommandFormSummary Form { get; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool IsBusy { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string StatusMessage { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string Reason { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial bool Consent { get; set; } = true;
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string AdditionalInfo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial long? ExistingQueryId { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial QueryStatus CommandStatus { get; set; } = QueryStatus.Inserted;
|
||||||
|
|
||||||
|
public bool CanUseCurrentLocation => IsSupported && !IsBusy;
|
||||||
|
|
||||||
|
public string Title => Form.Title;
|
||||||
|
public string PerformerLabel => Performer.UserName;
|
||||||
|
public string ActivityLabel => Activity.Name;
|
||||||
|
public bool IsSupported => IsRdv || IsBrush || IsMultiBrush;
|
||||||
|
public bool IsRdv => string.Equals(Form.ActionName, BillingCodes.Rdv, StringComparison.Ordinal);
|
||||||
|
public bool IsBrush => string.Equals(Form.ActionName, BillingCodes.Brush, StringComparison.Ordinal);
|
||||||
|
public bool IsMultiBrush => string.Equals(Form.ActionName, BillingCodes.MBrush, StringComparison.Ordinal);
|
||||||
|
public bool ShowsReason => IsRdv;
|
||||||
|
public bool ShowsAdditionalInfo => IsBrush;
|
||||||
|
public bool ShowsSinglePrestation => IsBrush;
|
||||||
|
public bool ShowsMultiplePrestations => IsMultiBrush;
|
||||||
|
public string BillingRoute => $"/billing/{Form.ActionName}";
|
||||||
|
public bool IsEditingExisting => ExistingQueryId.HasValue;
|
||||||
|
public string SubmitLabel => IsEditingExisting ? "Mettre à jour la commande" : "Poster la commande";
|
||||||
|
public string SupportMessage => IsSupported
|
||||||
|
? IsRdv
|
||||||
|
? "Complétez les informations du rendez-vous puis postez la commande."
|
||||||
|
: IsBrush
|
||||||
|
? "Choisissez une prestation coiffure puis postez la commande."
|
||||||
|
: "Choisissez une ou plusieurs prestations coiffure puis postez la commande."
|
||||||
|
: $"Le formulaire {Form.ActionName} n'est pas encore pris en charge dans PostIt.";
|
||||||
|
|
||||||
|
public override bool CanNavigateNext
|
||||||
|
{
|
||||||
|
get => false;
|
||||||
|
protected set { _ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanNavigatePrevious
|
||||||
|
{
|
||||||
|
get => true;
|
||||||
|
protected set { _ = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public BillingCommandPageViewModel(
|
||||||
|
ActivityInfo activity,
|
||||||
|
ActivityUserDisplayItem performer,
|
||||||
|
CommandFormSummary form,
|
||||||
|
BillingApiClient billingClient)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
|
||||||
|
StatusMessage = SupportMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnExistingQueryIdChanged(long? value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(IsEditingExisting));
|
||||||
|
OnPropertyChanged(nameof(SubmitLabel));
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnIsBusyChanged(bool value)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(CanUseCurrentLocation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync(BillingQueryDetailsDto? existingQuery = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (existingQuery is not null)
|
||||||
|
{
|
||||||
|
ApplyExistingQuery(existingQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ApplyExistingQuery(BillingQueryDetailsDto existingQuery);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
protected abstract Task SubmitAsync();
|
||||||
|
}
|
||||||
182
src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs
Normal file
182
src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Yavsc.Abstract.Workflow;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
using Yavsc.Models.Haircut;
|
||||||
|
namespace PostIt.ViewModels.Commands;
|
||||||
|
public partial class BrushViewModel : RdvViewModel
|
||||||
|
{
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ObservableCollection<HairPrestationDto> AvailablePrestations { get; set; } = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial HairPrestationDto? SelectedPrestation { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ObservableCollection<SelectableHairPrestationItem> MultiPrestations { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
public BrushViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient)
|
||||||
|
: base(activity, performer, form, billingClient)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async void ApplyExistingQuery(BillingQueryDetailsDto existingQuery)
|
||||||
|
{
|
||||||
|
if (IsBrush && existingQuery.PrestationId is not null)
|
||||||
|
{
|
||||||
|
SelectedPrestation = AvailablePrestations.FirstOrDefault(x => x.Id == existingQuery.PrestationId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsMultiBrush)
|
||||||
|
{
|
||||||
|
var selectedIds = existingQuery.PrestationIds is null
|
||||||
|
? new HashSet<long>()
|
||||||
|
: new HashSet<long>(existingQuery.PrestationIds);
|
||||||
|
foreach (var item in MultiPrestations)
|
||||||
|
{
|
||||||
|
item.IsSelected = selectedIds.Contains(item.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prestations = await _billingClient.GetHairPrestationsAsync
|
||||||
|
(Form.ActionName).ConfigureAwait(true);
|
||||||
|
AvailablePrestations = new ObservableCollection<HairPrestationDto>
|
||||||
|
(prestations ?? new List<HairPrestationDto>());
|
||||||
|
SelectedPrestation = AvailablePrestations.FirstOrDefault();
|
||||||
|
MultiPrestations = new ObservableCollection<SelectableHairPrestationItem>(AvailablePrestations.Select(SelectableHairPrestationItem.FromDto));
|
||||||
|
|
||||||
|
StatusMessage = AvailablePrestations.Count == 0
|
||||||
|
? "Aucune prestation coiffure disponible."
|
||||||
|
: SupportMessage;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
StatusMessage = "Accès refusé au catalogue de prestations (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"Erreur lors du chargement des prestations: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var address = Address.Trim();
|
||||||
|
var locationPayload = BuildLocationPayload(address, Latitude, Longitude);
|
||||||
|
|
||||||
|
var payload = new BillingQueryDetailsDto
|
||||||
|
{
|
||||||
|
Id = ExistingQueryId ?? 0,
|
||||||
|
BillingCode = Form.ActionName,
|
||||||
|
ActivityCode = Activity.Code,
|
||||||
|
PerformerId = Performer.PerformerId,
|
||||||
|
Consent = Consent,
|
||||||
|
EventDate = EventDate,
|
||||||
|
Status = CommandStatus,
|
||||||
|
Reason = Reason.Trim(),
|
||||||
|
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(),
|
||||||
|
Location = new BillingLocationDto
|
||||||
|
{
|
||||||
|
Address = address,
|
||||||
|
Latitude = Latitude,
|
||||||
|
Longitude = Longitude,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsBrush)
|
||||||
|
{
|
||||||
|
if (SelectedPrestation is null)
|
||||||
|
{
|
||||||
|
StatusMessage = "Sélectionnez une prestation coiffure.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.PrestationId = SelectedPrestation.Id;
|
||||||
|
|
||||||
|
if (IsEditingExisting)
|
||||||
|
{
|
||||||
|
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _billingClient.CreateAsync(Form.ActionName, new
|
||||||
|
{
|
||||||
|
ActivityCode = Activity.Code,
|
||||||
|
PerformerId = Performer.PerformerId,
|
||||||
|
Consent,
|
||||||
|
EventDate = (DateTime?)EventDate,
|
||||||
|
Location = locationPayload,
|
||||||
|
PrestationId = SelectedPrestation.Id,
|
||||||
|
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? null : AdditionalInfo.Trim(),
|
||||||
|
Status = payload.Status,
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsMultiBrush)
|
||||||
|
{
|
||||||
|
var selectedPrestations = MultiPrestations.Where(x => x.IsSelected).ToList();
|
||||||
|
if (selectedPrestations.Count == 0)
|
||||||
|
{
|
||||||
|
StatusMessage = "Sélectionnez au moins une prestation coiffure.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.PrestationIds = selectedPrestations.Select(x => x.Id).ToList();
|
||||||
|
|
||||||
|
if (IsEditingExisting)
|
||||||
|
{
|
||||||
|
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _billingClient.CreateAsync(Form.ActionName, new
|
||||||
|
{
|
||||||
|
ActivityCode = Activity.Code,
|
||||||
|
PerformerId = Performer.PerformerId,
|
||||||
|
Consent,
|
||||||
|
EventDate = EventDate,
|
||||||
|
Location = locationPayload,
|
||||||
|
Prestations = selectedPrestations.Select(x => new { PrestationId = x.Id }).ToList(),
|
||||||
|
Status = payload.Status,
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"Erreur lors de l'envoi de la commande: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/PostIt/PostIt/ViewModels/Commands/MBrushViewModel cs
Normal file
16
src/PostIt/PostIt/ViewModels/Commands/MBrushViewModel cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using PostIt.ViewModels;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels.Comands;
|
||||||
|
|
||||||
|
public class MBrushViewModel : BillingCommandPageViewModel
|
||||||
|
{
|
||||||
|
public MBrushViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient)
|
||||||
|
: base(activity, performer, form, billingClient)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
210
src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs
Normal file
210
src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using PostIt.Services;
|
||||||
|
using Yavsc.Abstract.Workflow;
|
||||||
|
using Yavsc.Api.Client;
|
||||||
|
|
||||||
|
namespace PostIt.ViewModels.Commands;
|
||||||
|
|
||||||
|
public partial class RdvViewModel : BillingCommandPageViewModel
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string Address { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial double Latitude { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial double Longitude { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial DateTime EventDate { get; set; }
|
||||||
|
|
||||||
|
public RdvViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient)
|
||||||
|
: base(activity, performer, form, billingClient)
|
||||||
|
{
|
||||||
|
EventDate = DateTime.Now.AddDays(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery)
|
||||||
|
{
|
||||||
|
ExistingQueryId = existingQuery.Id;
|
||||||
|
CommandStatus = existingQuery.Status;
|
||||||
|
Consent = existingQuery.Consent;
|
||||||
|
Reason = existingQuery.Reason ?? string.Empty;
|
||||||
|
AdditionalInfo = existingQuery.AdditionalInfo ?? string.Empty;
|
||||||
|
|
||||||
|
if (existingQuery.EventDate is not null)
|
||||||
|
{
|
||||||
|
EventDate = existingQuery.EventDate.Value
|
||||||
|
.ToLocalTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingQuery.Location is not null)
|
||||||
|
{
|
||||||
|
Address = existingQuery.Location.Address ?? string.Empty;
|
||||||
|
Latitude = existingQuery.Location.Latitude ?? 0;
|
||||||
|
Longitude = existingQuery.Location.Longitude ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
StatusMessage = $"Commande #{existingQuery.Id} chargée.";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanUseCurrentLocation))]
|
||||||
|
private async Task UseCurrentLocationAsync()
|
||||||
|
{
|
||||||
|
if (!CanUseCurrentLocation)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await Platform.TryGetCurrentLocationAsync(default).ConfigureAwait(true);
|
||||||
|
if (!result.IsSuccess || !result.Latitude.HasValue || !result.Longitude.HasValue)
|
||||||
|
{
|
||||||
|
StatusMessage = result.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Latitude = result.Latitude.Value;
|
||||||
|
Longitude = result.Longitude.Value;
|
||||||
|
StatusMessage = string.IsNullOrWhiteSpace(Address)
|
||||||
|
? "Position récupérée. Complétez l'adresse puis envoyez la commande."
|
||||||
|
: result.Message;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
StatusMessage = "La récupération de la position a été annulée.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"Impossible de récupérer la position: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static object BuildLocationPayload(string address, double? latitude, double? longitude)
|
||||||
|
{
|
||||||
|
if (latitude.HasValue && longitude.HasValue)
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Address = address,
|
||||||
|
Latitude = latitude.Value,
|
||||||
|
Longitude = longitude.Value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Address = address,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override async Task SubmitAsync()
|
||||||
|
{
|
||||||
|
if (!IsSupported)
|
||||||
|
{
|
||||||
|
StatusMessage = SupportMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Consent)
|
||||||
|
{
|
||||||
|
StatusMessage = "Le consentement est requis pour poster la commande.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Address))
|
||||||
|
{
|
||||||
|
StatusMessage = "L'adresse du rendez-vous est requise.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (IsRdv && string.IsNullOrWhiteSpace(Reason))
|
||||||
|
{
|
||||||
|
StatusMessage = "Le motif du rendez-vous est requis.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var address = Address.Trim();
|
||||||
|
var locationPayload = BuildLocationPayload(address, Latitude, Longitude);
|
||||||
|
|
||||||
|
var payload = new BillingQueryDetailsDto
|
||||||
|
{
|
||||||
|
Id = ExistingQueryId ?? 0,
|
||||||
|
BillingCode = Form.ActionName,
|
||||||
|
ActivityCode = Activity.Code,
|
||||||
|
PerformerId = Performer.PerformerId,
|
||||||
|
Consent = Consent,
|
||||||
|
EventDate = EventDate,
|
||||||
|
Status = CommandStatus,
|
||||||
|
Reason = Reason.Trim(),
|
||||||
|
AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(),
|
||||||
|
Location = new BillingLocationDto
|
||||||
|
{
|
||||||
|
Address = address,
|
||||||
|
Latitude = Latitude,
|
||||||
|
Longitude = Longitude,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsRdv)
|
||||||
|
{
|
||||||
|
if (IsEditingExisting)
|
||||||
|
{
|
||||||
|
await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _billingClient.CreateAsync(Form.ActionName, new
|
||||||
|
{
|
||||||
|
ActivityCode = Activity.Code,
|
||||||
|
PerformerId = Performer.PerformerId,
|
||||||
|
Consent,
|
||||||
|
EventDate = EventDate,
|
||||||
|
Location = locationPayload,
|
||||||
|
Reason = payload.Reason,
|
||||||
|
Status = payload.Status,
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
|
||||||
|
StatusMessage = IsEditingExisting
|
||||||
|
? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}."
|
||||||
|
: $"Commande transmise sur {BillingRoute} pour {Performer.UserName}.";
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"Erreur lors de l'envoi: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace PostIt.ViewModels;
|
namespace PostIt.ViewModels;
|
||||||
|
|
||||||
public abstract partial class ViewModelBase : ObservableObject
|
public abstract class ViewModelBase : ObservableObject
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets if the user can navigate to the next page
|
/// Gets if the user can navigate to the next page
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
ItemsSource="{Binding Activities}"
|
ItemsSource="{Binding Activities}"
|
||||||
SelectedItem="{Binding SelectedActivity, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedActivity, Mode=TwoWay}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="wf:ActivityBrowseItemDto">
|
<DataTemplate x:DataType="wf:ActivityInfo">
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
ItemsSource="{Binding Specializations}"
|
ItemsSource="{Binding Specializations}"
|
||||||
SelectedItem="{Binding SelectedSpecialization, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedSpecialization, Mode=TwoWay}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="wf:ActivityBrowseItemDto">
|
<DataTemplate x:DataType="wf:ActivityInfo">
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
<ContentPage xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:vm="using:PostIt.ViewModels"
|
|
||||||
x:Class="PostIt.Views.BillingCommandPage"
|
|
||||||
x:DataType="vm:BillingCommandPageViewModel"
|
|
||||||
Header="Commande billing">
|
|
||||||
<ScrollViewer>
|
|
||||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" Margin="12">
|
|
||||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="2"
|
|
||||||
Text="{Binding Title}"
|
|
||||||
FontSize="18"
|
|
||||||
FontWeight="Bold" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="1" Grid.ColumnSpan="2"
|
|
||||||
Margin="0,4,0,12"
|
|
||||||
Text="{Binding SupportMessage}"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Opacity="0.8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" Text="Utilisateur" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding PerformerLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Text="Activité" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding ActivityLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="4" Text="Date" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding EventDateText, Mode=TwoWay}" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="5"
|
|
||||||
Text="Motif"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,0,12,8"
|
|
||||||
IsVisible="{Binding ShowsReason}" />
|
|
||||||
<TextBox Grid.Row="5"
|
|
||||||
Grid.Column="1"
|
|
||||||
Text="{Binding Reason, Mode=TwoWay}"
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
IsVisible="{Binding ShowsReason}" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="6" Text="Adresse" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Address, Mode=TwoWay}" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<Button Grid.Row="7"
|
|
||||||
Grid.ColumnSpan="2"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
Content="Utiliser ma position"
|
|
||||||
Command="{Binding UseCurrentLocationCommand}" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="8" Text="Latitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding LatitudeText, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="9" Text="Longitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
|
||||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding LongitudeText, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="10"
|
|
||||||
Text="Prestation"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,0,12,8"
|
|
||||||
IsVisible="{Binding ShowsSinglePrestation}" />
|
|
||||||
<ComboBox Grid.Row="10"
|
|
||||||
Grid.Column="1"
|
|
||||||
ItemsSource="{Binding AvailablePrestations}"
|
|
||||||
SelectedItem="{Binding SelectedPrestation, Mode=TwoWay}"
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
IsVisible="{Binding ShowsSinglePrestation}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Spacing="2">
|
|
||||||
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
|
||||||
<TextBlock Text="{Binding Details}" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="11"
|
|
||||||
Text="Prestations"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
Margin="0,0,12,8"
|
|
||||||
IsVisible="{Binding ShowsMultiplePrestations}" />
|
|
||||||
<ListBox Grid.Row="11"
|
|
||||||
Grid.Column="1"
|
|
||||||
ItemsSource="{Binding MultiPrestations}"
|
|
||||||
IsVisible="{Binding ShowsMultiplePrestations}"
|
|
||||||
MaxHeight="180"
|
|
||||||
Margin="0,0,0,8">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="0,0,0,8">
|
|
||||||
<StackPanel Spacing="2">
|
|
||||||
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
|
||||||
<TextBlock Text="{Binding Details}" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
|
|
||||||
</StackPanel>
|
|
||||||
</CheckBox>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="12"
|
|
||||||
Text="Informations"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,0,12,8"
|
|
||||||
IsVisible="{Binding ShowsAdditionalInfo}" />
|
|
||||||
<TextBox Grid.Row="12"
|
|
||||||
Grid.Column="1"
|
|
||||||
Text="{Binding AdditionalInfo, Mode=TwoWay}"
|
|
||||||
Margin="0,0,0,8"
|
|
||||||
IsVisible="{Binding ShowsAdditionalInfo}" />
|
|
||||||
|
|
||||||
<CheckBox Grid.Row="13" Grid.ColumnSpan="2"
|
|
||||||
Content="Je consens à la création de cette commande"
|
|
||||||
IsChecked="{Binding Consent, Mode=TwoWay}"
|
|
||||||
Margin="0,4,0,12" />
|
|
||||||
|
|
||||||
<Grid Grid.Row="14" Grid.ColumnSpan="2" ColumnDefinitions="Auto,12,*,Auto">
|
|
||||||
<Button Grid.Column="0"
|
|
||||||
Content="{Binding SubmitLabel}"
|
|
||||||
Command="{Binding SubmitCommand}"
|
|
||||||
IsEnabled="{Binding IsSupported}" />
|
|
||||||
<TextBox Grid.Column="2"
|
|
||||||
Text="{Binding StatusMessage}"
|
|
||||||
IsReadOnly="True"
|
|
||||||
AcceptsReturn="True"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
BorderThickness="0"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
Padding="0"
|
|
||||||
Background="Transparent"
|
|
||||||
VerticalAlignment="Center" />
|
|
||||||
<ProgressBar Grid.Column="3"
|
|
||||||
Width="120"
|
|
||||||
IsIndeterminate="True"
|
|
||||||
IsVisible="{Binding IsBusy}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</ScrollViewer>
|
|
||||||
</ContentPage>
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
ItemsSource="{Binding Forms}"
|
ItemsSource="{Binding Forms}"
|
||||||
SelectedItem="{Binding SelectedForm, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedForm, Mode=TwoWay}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="wf:CommandFormSummaryDto">
|
<DataTemplate x:DataType="wf:CommandFormSummary">
|
||||||
<StackPanel Spacing="2" Margin="0,0,0,10">
|
<StackPanel Spacing="2" Margin="0,0,0,10">
|
||||||
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
||||||
<TextBlock Text="{Binding ActionName, StringFormat='Route billing : /billing/{0}'}"
|
<TextBlock Text="{Binding ActionName, StringFormat='Route billing : /billing/{0}'}"
|
||||||
|
|
@ -53,4 +53,4 @@
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentPage>
|
</ContentPage>
|
||||||
|
|
|
||||||
142
src/PostIt/PostIt/Views/Commands/BrushPage.axaml
Normal file
142
src/PostIt/PostIt/Views/Commands/BrushPage.axaml
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:cvm="using:PostIt.ViewModels.Commands"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="PostIt.Views.Commands.BrushPage"
|
||||||
|
|
||||||
|
x:DataType="cvm:BrushViewModel"
|
||||||
|
Header="Commande billing">
|
||||||
|
<ScrollViewer>
|
||||||
|
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" Margin="12">
|
||||||
|
<TextBlock Grid.Row="0" Grid.ColumnSpan="2"
|
||||||
|
Text="{Binding Title}"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.ColumnSpan="2"
|
||||||
|
Margin="0,4,0,12"
|
||||||
|
Text="{Binding SupportMessage}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Opacity="0.8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Text="Utilisateur" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding PerformerLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Text="Activité" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding ActivityLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Text="Date" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<DatePicker Grid.Row="4" Grid.Column="1" DayFormat="ddd dd" SelectedDate="{Binding EventDate, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="5"
|
||||||
|
Text="Motif"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsReason}" />
|
||||||
|
<TextBox Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding Reason, Mode=TwoWay}"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
IsVisible="{Binding ShowsReason}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="6" Text="Adresse" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Address, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<Button Grid.Row="7"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Utiliser ma position"
|
||||||
|
Command="{Binding UseCurrentLocationCommand}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="8" Text="Latitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding Latitude, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="9" Text="Longitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Longitude, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="10"
|
||||||
|
Text="Prestation"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsSinglePrestation}" />
|
||||||
|
<ComboBox Grid.Row="10"
|
||||||
|
Grid.Column="1"
|
||||||
|
ItemsSource="{Binding AvailablePrestations}"
|
||||||
|
SelectedItem="{Binding SelectedPrestation, Mode=TwoWay}"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
IsVisible="{Binding ShowsSinglePrestation}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
||||||
|
<TextBlock Text="{Binding Details}" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="11"
|
||||||
|
Text="Prestations"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsMultiplePrestations}" />
|
||||||
|
<ListBox Grid.Row="11"
|
||||||
|
Grid.Column="1"
|
||||||
|
ItemsSource="{Binding MultiPrestations}"
|
||||||
|
IsVisible="{Binding ShowsMultiplePrestations}"
|
||||||
|
MaxHeight="180"
|
||||||
|
Margin="0,0,0,8">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="0,0,0,8">
|
||||||
|
<StackPanel Spacing="2">
|
||||||
|
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
|
||||||
|
<TextBlock Text="{Binding Details}" FontSize="11" Opacity="0.7" TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</CheckBox>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="12"
|
||||||
|
Text="Informations"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||||
|
<TextBox Grid.Row="12"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding AdditionalInfo, Mode=TwoWay}"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="13" Grid.ColumnSpan="2"
|
||||||
|
Content="Je consens à la création de cette commande"
|
||||||
|
IsChecked="{Binding Consent, Mode=TwoWay}"
|
||||||
|
Margin="0,4,0,12" />
|
||||||
|
|
||||||
|
<Grid Grid.Row="14" Grid.ColumnSpan="2" ColumnDefinitions="Auto,12,*,Auto">
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Content="{Binding SubmitLabel}"
|
||||||
|
Command="{Binding SubmitCommand}"
|
||||||
|
IsEnabled="{Binding IsSupported}" />
|
||||||
|
<TextBox Grid.Column="2"
|
||||||
|
Text="{Binding StatusMessage}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
BorderThickness="0"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Padding="0"
|
||||||
|
Background="Transparent"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ProgressBar Grid.Column="3"
|
||||||
|
Width="120"
|
||||||
|
IsIndeterminate="True"
|
||||||
|
IsVisible="{Binding IsBusy}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</ContentPage>
|
||||||
13
src/PostIt/PostIt/Views/Commands/BrushPage.axaml.cs
Normal file
13
src/PostIt/PostIt/Views/Commands/BrushPage.axaml.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace PostIt.Views.Commands;
|
||||||
|
|
||||||
|
public partial class BrushPage : ContentPage
|
||||||
|
{
|
||||||
|
public BrushPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/PostIt/PostIt/Views/Commands/RdvPage.axaml
Normal file
95
src/PostIt/PostIt/Views/Commands/RdvPage.axaml
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<ContentPage xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:cvm="using:PostIt.ViewModels.Commands"
|
||||||
|
x:Class="PostIt.Views.Commands.RdvPage"
|
||||||
|
x:DataType="cvm:RdvViewModel"
|
||||||
|
Header="Commande billing">
|
||||||
|
<ScrollViewer>
|
||||||
|
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,*" Margin="12">
|
||||||
|
<TextBlock Grid.Row="0" Grid.ColumnSpan="2"
|
||||||
|
Text="{Binding Title}"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.ColumnSpan="2"
|
||||||
|
Margin="0,4,0,12"
|
||||||
|
Text="{Binding SupportMessage}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Opacity="0.8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Text="Utilisateur" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding PerformerLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Text="Activité" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding ActivityLabel}" IsReadOnly="True" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Text="Date" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<DatePicker Grid.Row="4" Grid.Column="1" DayFormat="ddd dd" SelectedDate="{Binding EventDate, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="5"
|
||||||
|
Text="Motif"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsReason}" />
|
||||||
|
<TextBox Grid.Row="5"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding Reason, Mode=TwoWay}"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
IsVisible="{Binding ShowsReason}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="6" Text="Adresse" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding Address, Mode=TwoWay}" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<Button Grid.Row="7"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Content="Utiliser ma position"
|
||||||
|
Command="{Binding UseCurrentLocationCommand}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="8" Text="Latitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding Latitude, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="9" Text="Longitude" VerticalAlignment="Center" Margin="0,0,12,8" />
|
||||||
|
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Longitude, Mode=TwoWay}" PlaceholderText="Optionnel" Margin="0,0,0,8" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="12"
|
||||||
|
Text="Informations"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,12,8"
|
||||||
|
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||||
|
<TextBox Grid.Row="12"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="{Binding AdditionalInfo, Mode=TwoWay}"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
IsVisible="{Binding ShowsAdditionalInfo}" />
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="13" Grid.ColumnSpan="2"
|
||||||
|
Content="Je consens à la création de cette commande"
|
||||||
|
IsChecked="{Binding Consent, Mode=TwoWay}"
|
||||||
|
Margin="0,4,0,12" />
|
||||||
|
|
||||||
|
<Grid Grid.Row="14" Grid.ColumnSpan="2" ColumnDefinitions="Auto,12,*,Auto">
|
||||||
|
<Button Grid.Column="0"
|
||||||
|
Content="{Binding SubmitLabel}"
|
||||||
|
Command="{Binding SubmitCommand}"
|
||||||
|
IsEnabled="{Binding IsSupported}" />
|
||||||
|
<TextBox Grid.Column="2"
|
||||||
|
Text="{Binding StatusMessage}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
BorderThickness="0"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Padding="0"
|
||||||
|
Background="Transparent"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ProgressBar Grid.Column="3"
|
||||||
|
Width="120"
|
||||||
|
IsIndeterminate="True"
|
||||||
|
IsVisible="{Binding IsBusy}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</ContentPage>
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace PostIt.Views;
|
namespace PostIt.Views.Commands;
|
||||||
|
|
||||||
public partial class BillingCommandPage : ContentPage
|
public partial class RdvPage : ContentPage
|
||||||
{
|
{
|
||||||
public BillingCommandPage()
|
public RdvPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
@ -14,4 +14,4 @@ public partial class BillingCommandPage : ContentPage
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue