399 lines
No EOL
14 KiB
C#
399 lines
No EOL
14 KiB
C#
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 Yavsc;
|
|
using Yavsc.Abstract.Workflow;
|
|
using Yavsc.Api.Client;
|
|
using Yavsc.Models.Billing;
|
|
using Yavsc.Models.Haircut;
|
|
using Yavsc.Models.Relationship;
|
|
|
|
namespace PostIt.ViewModels;
|
|
|
|
public partial class BillingCommandPageViewModel : ViewModelBase
|
|
{
|
|
private readonly BillingApiClient _billingClient;
|
|
|
|
public ActivityBrowseItemDto Activity { get; }
|
|
public ActivityUserDisplayItem Performer { get; }
|
|
public CommandFormSummaryDto 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 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(
|
|
ActivityBrowseItemDto activity,
|
|
ActivityUserDisplayItem performer,
|
|
CommandFormSummaryDto 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));
|
|
}
|
|
|
|
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 (!TryParseCoordinate(LatitudeText, out var latitude))
|
|
{
|
|
StatusMessage = "Latitude invalide.";
|
|
return;
|
|
}
|
|
|
|
if (!TryParseCoordinate(LongitudeText, out var longitude))
|
|
{
|
|
StatusMessage = "Longitude invalide.";
|
|
return;
|
|
}
|
|
|
|
IsBusy = true;
|
|
try
|
|
{
|
|
var location = new Location
|
|
{
|
|
Address = Address.Trim(),
|
|
Latitude = latitude,
|
|
Longitude = 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 = location.Address,
|
|
Latitude = location.Latitude,
|
|
Longitude = location.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 = location,
|
|
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 = location,
|
|
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 = location,
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
LongitudeText = existingQuery.Location.Longitude.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
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);
|
|
}
|
|
} |