Compare commits

...
Author SHA1 Message Date
d92456e141
fixe tests
Some checks failed
Dotnet build and test / build (pull_request) Failing after 6m27s
2026-09-12 16:49:36 +01:00
5b48e0bfdb
GetOpenIdConfiguration_returns_ok against testing host 2026-09-12 14:30:50 +01:00
3836faf872
Use a shared YavscMessageSender 2026-09-12 13:57:25 +01:00
2397f4d3a8
Add provider ongoing requests flow and sort persistence 2026-09-12 13:29:49 +01:00
86091990c5
generic estimate 2026-09-12 12:59:44 +01:00
27 changed files with 7102 additions and 568 deletions

View file

@ -0,0 +1,208 @@
using System.Net.Http;
using PostIt.ViewModels;
using Yavsc;
using Yavsc.Api.Client;
namespace PostIt.Tests;
public class ProviderOngoingRequestsPageViewModelTests
{
[Fact]
public async Task RefreshAsync_calls_provider_endpoint_and_filters_out_unknown_billing_codes()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
Assert.Contains("https://business.example/api/v1/bill/provider/ongoing", api.Paths);
Assert.Equal(3, vm.Queries.Count);
Assert.Equal(12, vm.Queries[0].Id);
Assert.Equal(11, vm.Queries[1].Id);
Assert.Equal(10, vm.Queries[2].Id);
}
[Fact]
public async Task FilterText_filters_by_activity_code_and_status()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
vm.FilterText = "mbrush";
Assert.Single(vm.Queries);
Assert.Equal("MBrush", vm.Queries[0].BillingCode);
vm.FilterText = "accepted";
Assert.Single(vm.Queries);
Assert.Equal(QueryStatus.Accepted, vm.Queries[0].Status);
}
[Fact]
public async Task OpenSelectedEditorCommand_can_execute_only_when_selection_exists()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
Assert.False(vm.OpenSelectedEditorCommand.CanExecute(null));
vm.SelectedQuery = vm.Queries[0];
Assert.True(vm.OpenSelectedEditorCommand.CanExecute(null));
}
[Fact]
public async Task SelectedSortOption_date_keeps_most_recent_first()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByDate;
Assert.Equal(new long[] { 12, 11, 10 }, vm.Queries.Select(q => q.Id).ToArray());
}
[Fact]
public async Task SelectedSortOption_date_ascending_keeps_oldest_first()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByDateAsc;
Assert.Equal(new long[] { 10, 11, 12 }, vm.Queries.Select(q => q.Id).ToArray());
}
[Fact]
public async Task SelectedSortOption_status_prioritizes_inprogress_then_accepted_then_inserted()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var vm = new ProviderOngoingRequestsPageViewModel(client);
await vm.InitializeAsync();
vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByStatus;
Assert.Equal(new long[] { 11, 12, 10 }, vm.Queries.Select(q => q.Id).ToArray());
}
[Fact]
public void Constructor_reads_saved_sort_option_from_settings()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var settings = new Settings
{
ProviderOngoingRequestsSortOption = ProviderOngoingRequestsPageViewModel.SortByStatus,
};
var vm = new ProviderOngoingRequestsPageViewModel(client, settings);
Assert.Equal(ProviderOngoingRequestsPageViewModel.SortByStatus, vm.SelectedSortOption);
}
[Fact]
public void Constructor_falls_back_to_default_when_saved_sort_is_invalid()
{
var api = new StubProviderApi();
var client = new BillingApiClient(api, "https://business.example/api/v1/");
var settings = new Settings
{
ProviderOngoingRequestsSortOption = "invalide",
};
var vm = new ProviderOngoingRequestsPageViewModel(client, settings);
Assert.Equal(ProviderOngoingRequestsPageViewModel.SortByDate, vm.SelectedSortOption);
}
private sealed class StubProviderApi : IYavscApiClient
{
public HttpClient Http { get; } = new();
public List<string> Paths { get; } = new();
public Task<T> CallAsync<T>(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
{
Paths.Add(path);
if (typeof(T) == typeof(List<BillingQuerySummaryDto>))
{
var data = new List<BillingQuerySummaryDto>
{
new()
{
Id = 10,
BillingCode = "Rdv",
ActivityCode = "dev",
PerformerId = "perf-1",
ClientId = "cli-1",
Status = QueryStatus.Inserted,
Description = "Rendez-vous",
EventDate = new DateTime(2026, 9, 10, 10, 0, 0, DateTimeKind.Utc),
},
new()
{
Id = 11,
BillingCode = "MBrush",
ActivityCode = "hair",
PerformerId = "perf-1",
ClientId = "cli-2",
Status = QueryStatus.InProgress,
Description = "Coupe multiple",
EventDate = new DateTime(2026, 9, 11, 10, 0, 0, DateTimeKind.Utc),
},
new()
{
Id = 12,
BillingCode = "Brush",
ActivityCode = "hair",
PerformerId = "perf-1",
ClientId = "cli-3",
Status = QueryStatus.Accepted,
Description = "Coupe simple",
EventDate = new DateTime(2026, 9, 12, 10, 0, 0, DateTimeKind.Utc),
},
new()
{
Id = 13,
BillingCode = "",
ActivityCode = "unknown",
PerformerId = "perf-1",
ClientId = "cli-4",
Status = QueryStatus.Accepted,
Description = "Doit être filtrée",
EventDate = new DateTime(2026, 9, 13, 10, 0, 0, DateTimeKind.Utc),
}
};
return Task.FromResult((T)(object)data);
}
return Task.FromResult(default(T)!);
}
public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default)
{
Paths.Add(path);
return Task.CompletedTask;
}
public Task<T> CallAsync<T>(HttpMethod method, string path, Func<HttpContent> contentFactory, CancellationToken ct = default)
=> CallAsync<T>(method, path, (object?)null, ct);
public Task CallAsync(HttpMethod method, string path, Func<HttpContent> contentFactory, CancellationToken ct = default)
=> CallAsync(method, path, (object?)null, ct);
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
}

View file

@ -59,6 +59,7 @@ public static class ServiceCollectionHelpers
services.AddTransient<BrushPage>();
services.AddTransient<BillingQueriesPage>();
services.AddTransient<BillingQueryDetailsPage>();
services.AddTransient<ProviderOngoingRequestsPage>();
// ViewModels
services.AddSingleton(settings);
services.AddSingleton<YavscApiClient>(api);

View file

@ -52,6 +52,7 @@ public class ViewLocator : IDataTemplate
PostAclDialogViewModel => services.GetRequiredService<PostAclDialog>(),
BillingQueriesPageViewModel => services.GetRequiredService<BillingQueriesPage>(),
BillingQueryDetailsPageViewModel => services.GetRequiredService<BillingQueryDetailsPage>(),
ProviderOngoingRequestsPageViewModel => services.GetRequiredService<ProviderOngoingRequestsPage>(),
null => new TextBlock { Text = "No view for <null>" },
_ => new TextBlock { Text = $"No view for {data.GetType().Name}" }
};

View file

@ -0,0 +1,356 @@
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PostIt.Helpers;
using Yavsc;
using Yavsc.Abstract.Workflow;
using Yavsc.Api.Client;
namespace PostIt.ViewModels;
public partial class ProviderOngoingRequestsPageViewModel : ViewModelBase, IActionStatusViewModel
{
public const string SortByDate = "Date (plus récent d'abord)";
public const string SortByDateAsc = "Date (plus ancien d'abord)";
public const string SortByStatus = "Statut (en cours d'abord)";
private readonly BillingApiClient _billingClient;
private readonly Settings? _settings;
private List<BillingQuerySummaryDto> _allQueries = new();
[ObservableProperty]
public partial ObservableCollection<BillingQuerySummaryDto> Queries { get; set; } = new();
[ObservableProperty]
public partial string FilterText { get; set; } = string.Empty;
public IReadOnlyList<string> SortOptions { get; } = new[]
{
SortByDate,
SortByDateAsc,
SortByStatus,
};
[ObservableProperty]
public partial string SelectedSortOption { get; set; } = SortByDate;
[ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedQueryCommand))]
public partial BillingQuerySummaryDto? SelectedQuery { get; set; }
[ObservableProperty]
public partial bool IsBusy { get; set; }
[ObservableProperty]
public partial string StatusMessage { get; set; } = "Chargement des demandes fournisseur...";
[ObservableProperty]
public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Chargement des demandes fournisseur...");
public string Title => "Mes demandes en cours";
public override bool CanNavigateNext
{
get => false;
protected set { _ = value; }
}
public override bool CanNavigatePrevious
{
get => true;
protected set { _ = value; }
}
public ProviderOngoingRequestsPageViewModel(BillingApiClient billingClient, Settings? settings = null)
{
_billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient));
_settings = settings;
if (_settings is not null)
{
var preferredSort = NormalizeSortOption(_settings.ProviderOngoingRequestsSortOption);
if (!string.Equals(preferredSort, SelectedSortOption, StringComparison.Ordinal))
{
SelectedSortOption = preferredSort;
}
}
}
public Task InitializeAsync() => RefreshAsync();
[RelayCommand]
public async Task RefreshAsync()
{
IsBusy = true;
try
{
var items = await _billingClient.GetProviderOngoingQueriesAsync().ConfigureAwait(true) ?? new();
_allQueries = items
.Where(x => !string.IsNullOrWhiteSpace(x.BillingCode))
.OrderByDescending(x => x.EventDate ?? DateTime.MinValue)
.ThenByDescending(x => x.Id)
.ToList();
ApplyFilter();
this.SetInfoStatus(_allQueries.Count == 0
? "Aucune demande en cours pour votre profil fournisseur."
: $"{_allQueries.Count} demande(s) en cours chargée(s).");
}
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{
_allQueries = new List<BillingQuerySummaryDto>();
Queries = new ObservableCollection<BillingQuerySummaryDto>();
this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous.");
}
catch (Exception ex)
{
_allQueries = new List<BillingQuerySummaryDto>();
Queries = new ObservableCollection<BillingQuerySummaryDto>();
this.SetErrorStatus($"Erreur: {ex.Message}");
}
finally
{
IsBusy = false;
}
}
private bool CanOpenSelectedQuery() => SelectedQuery is not null;
private bool CanOpenSelectedEditor() => SelectedQuery is not null;
[RelayCommand(CanExecute = nameof(CanOpenSelectedQuery))]
public async Task OpenSelectedQueryAsync()
{
if (SelectedQuery is null)
{
this.SetWarningStatus("Sélectionnez une demande.");
return;
}
var app = (App?)Application.Current;
if (app is null)
{
throw new InvalidOperationException("Application PostIt indisponible.");
}
IsBusy = true;
try
{
var details = await _billingClient
.GetQueryAsync(SelectedQuery.BillingCode, SelectedQuery.Id)
.ConfigureAwait(true);
var (activity, performer, form) = BuildNavigationContext(SelectedQuery);
var vm = new BillingQueryDetailsPageViewModel(
activity,
performer,
form,
_billingClient,
details,
isReadOnly: false);
await app.PushPageAsync(vm).ConfigureAwait(true);
}
catch (Exception ex)
{
this.SetErrorStatus($"Erreur lors de l'ouverture: {ex.Message}");
}
finally
{
IsBusy = false;
}
}
[RelayCommand(CanExecute = nameof(CanOpenSelectedEditor))]
public async Task OpenSelectedEditorAsync()
{
if (SelectedQuery is null)
{
this.SetWarningStatus("Sélectionnez une demande.");
return;
}
var app = (App?)Application.Current;
if (app is null)
{
throw new InvalidOperationException("Application PostIt indisponible.");
}
IsBusy = true;
try
{
var details = await _billingClient
.GetQueryAsync(SelectedQuery.BillingCode, SelectedQuery.Id)
.ConfigureAwait(true);
var (activity, performer, form) = BuildNavigationContext(SelectedQuery);
var vm = form.CreateCommandPageViewModel(activity, performer, _billingClient);
if (vm is null)
{
this.SetWarningStatus($"Le formulaire '{form.ActionName}' n'est pas pris en charge en édition.");
return;
}
await vm.InitializeAsync(details).ConfigureAwait(true);
await app.PushPageAsync(vm).ConfigureAwait(true);
}
catch (Exception ex)
{
this.SetErrorStatus($"Erreur lors de l'ouverture en édition: {ex.Message}");
}
finally
{
IsBusy = false;
}
}
partial void OnFilterTextChanged(string value)
{
ApplyFilter();
}
partial void OnSelectedSortOptionChanged(string value)
{
var normalized = NormalizeSortOption(value);
if (!string.Equals(normalized, value, StringComparison.Ordinal))
{
SelectedSortOption = normalized;
return;
}
PersistSortPreference(value);
ApplyFilter();
}
private void ApplyFilter()
{
var query = FilterText?.Trim();
var filtered = string.IsNullOrWhiteSpace(query)
? _allQueries
: _allQueries.Where(x =>
ContainsInsensitive(x.Description, query)
|| ContainsInsensitive(x.ActivityCode, query)
|| ContainsInsensitive(x.BillingCode, query)
|| ContainsInsensitive(x.ClientId, query)
|| ContainsInsensitive(x.Status.ToString(), query))
.ToList();
var sorted = ApplySort(filtered);
Queries = new ObservableCollection<BillingQuerySummaryDto>(sorted);
}
private List<BillingQuerySummaryDto> ApplySort(IEnumerable<BillingQuerySummaryDto> source)
{
if (string.Equals(SelectedSortOption, SortByStatus, StringComparison.Ordinal))
{
return source
.OrderBy(x => GetStatusRank(x.Status))
.ThenByDescending(x => x.EventDate ?? DateTime.MinValue)
.ThenByDescending(x => x.Id)
.ToList();
}
if (string.Equals(SelectedSortOption, SortByDateAsc, StringComparison.Ordinal))
{
return source
.OrderBy(x => x.EventDate ?? DateTime.MinValue)
.ThenBy(x => x.Id)
.ToList();
}
return source
.OrderByDescending(x => x.EventDate ?? DateTime.MinValue)
.ThenByDescending(x => x.Id)
.ToList();
}
private void PersistSortPreference(string selectedSort)
{
if (_settings is null)
{
return;
}
if (string.Equals(_settings.ProviderOngoingRequestsSortOption, selectedSort, StringComparison.Ordinal))
{
return;
}
_settings.ProviderOngoingRequestsSortOption = selectedSort;
try
{
_settings.Save();
}
catch
{
this.SetWarningStatus("Le tri a été appliqué, mais sa sauvegarde a échoué.");
}
}
private static string NormalizeSortOption(string? sortOption)
{
if (string.Equals(sortOption, SortByDate, StringComparison.Ordinal)
|| string.Equals(sortOption, SortByDateAsc, StringComparison.Ordinal)
|| string.Equals(sortOption, SortByStatus, StringComparison.Ordinal))
{
return sortOption!;
}
return SortByDate;
}
private static int GetStatusRank(QueryStatus status)
=> status switch
{
QueryStatus.InProgress => 0,
QueryStatus.Accepted => 1,
QueryStatus.Inserted => 2,
QueryStatus.Success => 3,
QueryStatus.Rejected => 4,
QueryStatus.Failed => 5,
_ => 99,
};
private static bool ContainsInsensitive(string? source, string query)
=> !string.IsNullOrWhiteSpace(source)
&& source.Contains(query, StringComparison.OrdinalIgnoreCase);
private static (ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form)
BuildNavigationContext(BillingQuerySummaryDto query)
{
var activity = new ActivityInfo
{
Code = query.ActivityCode,
Name = string.IsNullOrWhiteSpace(query.ActivityCode)
? "Activité"
: query.ActivityCode,
};
var performer = new ActivityUserDisplayItem
{
PerformerId = query.PerformerId,
UserName = "Mon profil fournisseur",
AvatarFallbackLabel = "M",
IsPerformerActive = true,
PerformerStatusBadgeLabel = "Actif",
PerformerStatusBadgeBackground = "#E6F7EC",
PerformerStatusBadgeBorder = "#2E7D32",
PerformerStatusBadgeForeground = "#1B5E20",
};
var form = new CommandFormSummary
{
ActionName = query.BillingCode,
Title = query.BillingCode,
};
return (activity, performer, form);
}
}

View file

@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
using PostIt.Helpers;
using PostIt.Services;
using Yavsc.Api.Client;
namespace PostIt.ViewModels;
public class HomePageViewModel : ViewModelBase
@ -50,7 +51,24 @@ public class HomePageViewModel : ViewModelBase
await app.PushPageAsync(vm);
}
private Task OpenProviderRequestsAsync() => OpenActivitiesAsync();
private async Task OpenProviderRequestsAsync()
{
var app = (App?)Application.Current;
if (app is null)
{
throw new InvalidOperationException("Application PostIt indisponible.");
}
var billingClient = app.ServiceProvider?.GetRequiredService<BillingApiClient>();
if (billingClient is null)
{
throw new InvalidOperationException("Client billing indisponible.");
}
var vm = new ProviderOngoingRequestsPageViewModel(billingClient, Settings);
await vm.InitializeAsync();
await app.PushPageAsync(vm);
}
/// <summary>
/// Avalonia designer constructor. Builds a self-contained VM

View file

@ -32,6 +32,9 @@ public partial class Settings : ViewModelBase
[ObservableProperty]
public partial string SearchText { get; set; } = string.Empty;
[ObservableProperty]
public partial string ProviderOngoingRequestsSortOption { get; set; } = string.Empty;
[ObservableProperty]
[JsonIgnore]
public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret.");
@ -62,6 +65,7 @@ public partial class Settings : ViewModelBase
partial void OnBlogsApiUrlChanged(string value) => MarkDirty();
partial void OnApiUrlChanged(string value) => MarkDirty();
partial void OnSearchTextChanged(string value) => MarkDirty();
partial void OnProviderOngoingRequestsSortOptionChanged(string value) => MarkDirty();
/// <summary>
/// Authentication can be reassigned wholesale by
@ -346,6 +350,7 @@ public partial class Settings : ViewModelBase
? settings.ApiUrl
: this.ApiUrl;
this.SearchText = settings.SearchText ?? string.Empty;
this.ProviderOngoingRequestsSortOption = settings.ProviderOngoingRequestsSortOption ?? string.Empty;
if (!(settings.Authentication is null))
{
this.Authentication = new AuthenticationSettings();
@ -424,6 +429,7 @@ public partial class Settings : ViewModelBase
this.BlogsApiUrl = "https://blogs.pschneider.fr/api/v1/";
this.ApiUrl = "https://api.pschneider.fr/api/v1/";
this.SearchText = string.Empty;
this.ProviderOngoingRequestsSortOption = string.Empty;
}
/// <summary>
@ -466,7 +472,7 @@ public partial class Settings : ViewModelBase
UnixFileMode.UserRead | UnixFileMode.UserWrite);
IsDirty = false;
SetActionStatus("Parametres sauvegardes.", StatusSeverity.Info);
Console.WriteLine($"💾 Settings saved to {configPath}");
}
catch (Exception ex)

View file

@ -0,0 +1,98 @@
<ContentPage xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:PostIt.ViewModels"
xmlns:dto="using:Yavsc.Api.Client"
xmlns:postitControls="using:PostIt.Controls"
x:Class="PostIt.Views.ProviderOngoingRequestsPage"
x:DataType="vm:ProviderOngoingRequestsPageViewModel"
Header="Mes demandes en cours">
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto" Margin="12">
<StackPanel Grid.Row="0" Spacing="3">
<TextBlock Text="{Binding Title}" FontSize="20" FontWeight="Bold" />
<TextBlock Text="Point de vue fournisseur" Opacity="0.75" />
</StackPanel>
<Grid Grid.Row="1" ColumnDefinitions="Auto,8,Auto,8,*,8,Auto" Margin="0,10,0,8">
<Button Grid.Column="0" Content="Rafraîchir" Command="{Binding RefreshCommand}" />
<ComboBox Grid.Column="2"
Width="220"
ItemsSource="{Binding SortOptions}"
SelectedItem="{Binding SelectedSortOption, Mode=TwoWay}" />
<TextBox Grid.Column="4"
PlaceholderText="Filtrer: activité, code, client, statut..."
Text="{Binding FilterText, Mode=TwoWay}" />
<TextBlock Grid.Column="6"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding Queries.Count, StringFormat='Résultats : {0}'}"
Opacity="0.7" />
</Grid>
<TextBlock Grid.Row="2"
Text="Astuce: sélectionnez une ligne puis ouvrez le détail ou l'édition directe."
Opacity="0.65"
FontSize="11"
Margin="0,0,0,8" />
<Grid Grid.Row="3" RowDefinitions="*,Auto">
<ListBox Grid.Row="0"
ItemsSource="{Binding Queries}"
SelectedItem="{Binding SelectedQuery, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="dto:BillingQuerySummaryDto">
<Border BorderThickness="1"
BorderBrush="#22000000"
CornerRadius="8"
Padding="10"
Margin="0,0,0,8">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="{Binding Description}"
FontWeight="Bold"
TextWrapping="Wrap" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding Status}"
FontSize="11"
Opacity="0.75"
HorizontalAlignment="Right" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Margin="0,6,0,0"
Text="{Binding ActivityCode, StringFormat='Activité : {0}'}"
FontSize="11"
Opacity="0.75" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Margin="0,6,0,0"
Text="{Binding BillingCode, StringFormat='Code : {0}'}"
FontSize="11"
Opacity="0.65"
HorizontalAlignment="Right" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,8,0,0"
Spacing="8">
<Button Content="Ouvrir le détail"
Command="{Binding OpenSelectedQueryCommand}" />
<Button Content="Ouvrir en édition"
Command="{Binding OpenSelectedEditorCommand}" />
</StackPanel>
</Grid>
<Grid Grid.Row="4" ColumnDefinitions="*,Auto" Margin="0,12,0,0">
<postitControls:StatusBar Grid.Column="0"
DataContext="{Binding ActionStatus}" />
<ProgressBar Grid.Column="1" Width="120" IsIndeterminate="True" IsVisible="{Binding IsBusy}" />
</Grid>
</Grid>
</ContentPage>

View file

@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace PostIt.Views;
public partial class ProviderOngoingRequestsPage : ContentPage
{
public ProviderOngoingRequestsPage()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View file

@ -77,6 +77,14 @@ public sealed class BillingApiClient
return items;
}
public Task<List<BillingQuerySummaryDto>> GetProviderOngoingQueriesAsync(CancellationToken ct = default)
{
return _api.CallAsync<List<BillingQuerySummaryDto>>(
HttpMethod.Get,
Absolute("bill/provider/ongoing"),
ct: ct);
}
public async Task<BillingQueryDetailsDto> GetQueryAsync(string billingCode, long queryId, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(billingCode))

View file

@ -0,0 +1,114 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Microsoft.Extensions.DependencyInjection;
using Yavsc.Api.Test.Fixtures;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Models.Workflow;
using Yavsc.Tests.Shared;
namespace Yavsc.Api.Test;
[Collection("Yavsc Api")]
public sealed class BillingControllerTests : IClassFixture<ApiWebServerFixture>
{
private readonly ApiWebServerFixture _fixture;
public BillingControllerTests(ApiWebServerFixture fixture)
{
_fixture = fixture;
}
private HttpClient NewClient(string subject = "alice", string scope = "api")
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
var http = new HttpClient(handler)
{
BaseAddress = new Uri(_fixture.BaseAddress)
};
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope));
return http;
}
[Fact]
public async Task GetProviderOngoingCommands_returns_current_provider_requests()
{
WorkflowHelpers.ConfigureBillingService();
_fixture.ResetAndSeedActivityGraph();
using (var scope = _fixture.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var location = db.Locations.Single();
db.RdvQueries.Add(new RdvQuery
{
ActivityCode = "dev",
ClientId = "bob",
PerformerId = "alice",
Consent = true,
UserCreated = "alice",
UserModified = "alice",
DateCreated = DateTime.UtcNow.AddMinutes(-10),
DateModified = DateTime.UtcNow.AddMinutes(-8),
EventDate = DateTime.UtcNow.AddDays(1),
Location = location,
Reason = "Rendez-vous fournisseur",
Status = QueryStatus.InProgress,
Description = "Commande fournisseur en cours",
});
db.RdvQueries.Add(new RdvQuery
{
ActivityCode = "dev",
ClientId = "alice",
PerformerId = "bob",
Consent = true,
UserCreated = "bob",
UserModified = "bob",
DateCreated = DateTime.UtcNow.AddMinutes(-20),
DateModified = DateTime.UtcNow.AddMinutes(-20),
EventDate = DateTime.UtcNow.AddDays(2),
Location = location,
Reason = "Commande d'un autre prestataire",
Status = QueryStatus.Accepted,
Description = "Autre prestataire",
});
db.SaveChanges();
}
using var http = NewClient();
var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken);
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}");
var payload = await response.Content.ReadFromJsonAsync<List<ProviderOngoingCommandDto>>(TestContext.Current.CancellationToken);
Assert.NotNull(payload);
Assert.NotEmpty(payload!);
Assert.All(payload!, item => Assert.Equal("alice", item.PerformerId));
Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv);
}
private sealed class ProviderOngoingCommandDto
{
public long Id { get; set; }
public string BillingCode { get; set; } = string.Empty;
public string ActivityCode { get; set; } = string.Empty;
public string PerformerId { get; set; } = string.Empty;
public string ClientId { get; set; } = string.Empty;
public QueryStatus Status { get; set; }
public string Description { get; set; } = string.Empty;
}
}

View file

@ -1,14 +1,17 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Yavsc.Controllers;
using Yavsc.Interfaces.Workflow;
using Yavsc.Models;
using Yavsc.Models.Google.Messaging;
using Yavsc.Models.Haircut;
using Yavsc.Models.Messaging;
using Yavsc.Models.Relationship;
using Yavsc.Models.Workflow;
using Yavsc.Services;
using Yavsc.Tests.Shared;
namespace Yavsc.Api.Test.Fixtures;
@ -40,6 +43,10 @@ public sealed class ApiWebServerFixture : WebHostFixture
builder.Services.AddControllers()
.AddApplicationPart(typeof(ActivityApiController).Assembly);
builder.Services.AddLocalization();
builder.Services.Configure<GoogleAuthSettings>(_ => { });
builder.Services.AddTransient<IBillingService, BillingService>();
builder.Services.AddTransient<IYavscMessageSender, NoopMessageSender>();
builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer")
@ -83,6 +90,21 @@ public sealed class ApiWebServerFixture : WebHostFixture
public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal));
private sealed class NoopMessageSender : IYavscMessageSender
{
public Task<MessageWithPayloadResponse> NotifyBookQueryAsync(IEnumerable<string> connectionIds, RdvQueryEvent ev)
=> Task.FromResult(new MessageWithPayloadResponse());
public Task<MessageWithPayloadResponse> NotifyEstimateAsync(IEnumerable<string> connectionIds, EstimationEvent ev)
=> Task.FromResult(new MessageWithPayloadResponse());
public Task<MessageWithPayloadResponse> NotifyHairCutQueryAsync(IEnumerable<string> connectionIds, HairCutQueryEvent ev)
=> Task.FromResult(new MessageWithPayloadResponse());
public Task<MessageWithPayloadResponse> NotifyAsync(IEnumerable<string> connectionIds, IEvent yaev)
=> Task.FromResult(new MessageWithPayloadResponse());
}
public void ResetAndSeedActivityGraph()
{
using var scope = Services.CreateScope();

View file

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Newtonsoft.Json;
using System.Security.Claims;
using Yavsc.Billing;
using Yavsc.Helpers;
using Yavsc.ViewModels;
using Yavsc.Models.Billing;
@ -100,6 +101,57 @@ namespace Yavsc.ApiControllers
return ViewComponent("Bill",new object[] { billingCode, bill, OutputFormat.Pdf, true } );
}
/// <summary>
/// Lists ongoing service commands for the authenticated performer.
/// This endpoint is tailored for the PostIt provider homepage flow
/// ("Mes demandes en cours").
/// </summary>
[HttpGet("provider/ongoing")]
[Produces("application/json")]
public IActionResult GetProviderOngoingCommands()
{
var uid = User.GetUserId();
if (string.IsNullOrWhiteSpace(uid))
{
return Unauthorized();
}
if (billingService.BillingMap.Count == 0)
{
WorkflowHelpers.ConfigureBillingService();
}
var commands = dbContext.Set<NominativeServiceCommand>()
.AsNoTracking()
.Where(q => q.PerformerId == uid)
.Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.InProgress)
.OrderByDescending(q => q.DateModified)
.ThenByDescending(q => q.Id)
.ToList();
var payload = commands
.Select(q => new
{
Id = q.Id,
BillingCode = ResolveBillingCode(q),
ActivityCode = q.ActivityCode,
PerformerId = q.PerformerId,
ClientId = q.ClientId,
Status = q.Status,
Description = q.Description,
EventDate = ResolveEventDate(q),
Reason = q is Models.Workflow.RdvQuery rdv ? rdv.Reason : string.Empty,
AdditionalInfo = q is Models.Haircut.HairCutQuery hc ? hc.AdditionalInfo : string.Empty,
Provisional = q.Provisional,
})
.Where(x => !string.IsNullOrWhiteSpace(x.BillingCode))
.ToList();
return Ok(payload);
}
[HttpPost("prosign/{billingCode}/{id}")]
public async Task<IActionResult> ProSign(string billingCode, long id)
@ -133,6 +185,23 @@ namespace Yavsc.ApiControllers
return Ok (new { ProviderValidationDate = estimate.ProviderValidationDate, GCMSent = gcmSent });
}
private string ResolveBillingCode(NominativeServiceCommand command)
{
var typeName = command.GetType().Name;
return billingService.BillingMap.TryGetValue(typeName, out var code)
? code
: string.Empty;
}
private static DateTime? ResolveEventDate(NominativeServiceCommand command)
=> command switch
{
Models.Workflow.RdvQuery rdv => rdv.EventDate,
Models.Haircut.HairCutQuery brush => brush.EventDate,
Models.Haircut.HairMultiCutQuery mbrush => mbrush.EventDate,
_ => null,
};
[HttpGet("prosign/{billingCode}/{id}")]
public async Task<IActionResult> GetProSign(string billingCode, long id)
{
@ -154,9 +223,28 @@ namespace Yavsc.ApiControllers
public async Task<IActionResult> CliSign(string billingCode, long id)
{
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
var estimate = dbContext.Estimates.Include( e=>e.Query
).Include(e=>e.Owner).Include(e=>e.Owner.Performer).Include(e=>e.Client)
.FirstOrDefault( e=> e.Id == id && e.Query.ClientId == uid );
var estimate = dbContext.Estimates
.Include(e => e.Owner)
.Include(e => e.Owner.Performer)
.Include(e => e.Client)
.FirstOrDefault(e => e.Id == id);
if (estimate is null)
{
return NotFound();
}
if (estimate.CommandId is null)
{
return new ChallengeResult();
}
var command = dbContext.Set<NominativeServiceCommand>()
.FirstOrDefault(c => c.Id == estimate.CommandId.Value);
if (command is null || command.ClientId != uid)
{
return new ChallengeResult();
}
if (!(await authorizationService.AuthorizeAsync(User, estimate, new ReadPermission())).Succeeded)
{
return new ChallengeResult();

View file

@ -125,7 +125,8 @@ namespace Yavsc.Controllers
if (estimate.CommandId != null)
{
var query = _context.RdvQueries.FirstOrDefault(q => q.Id == estimate.CommandId);
var query = _context.Set<NominativeServiceCommand>()
.FirstOrDefault(q => q.Id == estimate.CommandId);
if (query == null)
{
return BadRequest(ModelState);

View file

@ -1,18 +1,6 @@
/*
Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/
Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
Source code and license this software can be found
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
using Anthropic.SDK;
using IdentityModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yavsc.Abstract.Interfaces;
@ -91,7 +79,8 @@ internal class Program
.TryAddSingleton<ISmtpClientFactory, SmtpClientFactory>();
services
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>();
.AddTransient<ICalendarManager, CalendarManager>()
.AddTransient<IYavscMessageSender, YavscMessageSender>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
builder.Services.AddSession(options =>
{
@ -123,9 +112,7 @@ internal class Program
;
app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
app.MapDefaultControllerRoute();
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
);
app.UseSession();
await app.RunAsync();

View file

@ -10,10 +10,10 @@ namespace Yavsc.Org.Tests
{
[Collection("Yavsc Server")]
[Trait("regression", "oui")]
public class BaseTestContext: IClassFixture<WebServerFixture>, IDisposable
public abstract class BaseTestContext : IClassFixture<WebServerFixture>, IDisposable
{
public readonly WebServerFixture _serverFixture;
private readonly ITestOutputHelper _output;
protected readonly WebServerFixture _serverFixture;
protected readonly ITestOutputHelper _output;
public BaseTestContext(ITestOutputHelper output, WebServerFixture fixture)
{
@ -21,6 +21,45 @@ namespace Yavsc.Org.Tests
this._output = output;
}
public HttpClient CreateHttpClient()
{
return new HttpClient(new BypassSslValidationHandler())
{
BaseAddress = new Uri(this._serverFixture.HttpsAuthority ?? throw new InvalidOperationException("Missing HttpsAuthority"))
};
}
/// <summary>
/// Issue a GET against <paramref name="relativePath"/> on the
/// in-memory test server. Returns the raw HttpResponseMessage
/// without following redirects — the test asserts on the first
/// hop, not the eventual page.
/// </summary>
protected static async Task<HttpResponseMessage> GetRaw(
HttpClient client, string relativePath)
{
Assert.NotNull(client);
var request = new HttpRequestMessage(HttpMethod.Get, relativePath);
return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
}
/// <summary>
/// Smoke assertion: a GET on <paramref name="relativePath"/>
/// returns 2xx (page served) or 3xx (redirect to login) or
/// 401/403 (anonymous rejected by [Authorize]). Anything else
/// — 404 (route missing), 5xx (server crash), connection
/// refused (host not started) — fails the test.
/// </summary>
protected static async Task AssertResponds(
HttpClient client, string relativePath)
{
var response = await GetRaw(client, relativePath);
var status = (int)response.StatusCode;
Assert.True(
status >= 200 && status < 400 || status == 401 || status == 403,
$"GET {relativePath} returned {status} {response.StatusCode}, " +
"expected 2xx/3xx (page or redirect) or 401/403 (auth required).");
}
// FIXME write a scenario from an empty database [Fact]
public void GitClone()
{
@ -36,7 +75,7 @@ namespace Yavsc.Org.Tests
var firstProject = dbContext.Project.Include(p => p.Repository).FirstOrDefault(
p => p.Name == "Yavsc"
);
Assert.NotNull (firstProject);
Assert.NotNull(firstProject);
var di = new DirectoryInfo(_serverFixture.SiteSettings.GitRepository);
if (!di.Exists) di.Create();
@ -44,7 +83,7 @@ namespace Yavsc.Org.Tests
clone.Launch(firstProject);
gitRepo = di.FullName;
}
string gitRepo=null;
string gitRepo = null;
private IConfigurationRoot configurationRoot;
@ -57,9 +96,9 @@ namespace Yavsc.Org.Tests
public void Dispose()
{
if (gitRepo!=null)
if (gitRepo != null)
{
Directory.Delete(Path.Combine(gitRepo,"yavsc"), true);
Directory.Delete(Path.Combine(gitRepo, "yavsc"), true);
}
}
}

View file

@ -69,6 +69,31 @@ namespace Yavsc.Org.Tests
}
[Fact]
public async Task GetSignin_returns_a_page()
{
using var client = new HttpClient(new BypassSslValidationHandler())
{
BaseAddress = new Uri(this._serverFixture.HttpsAuthority ?? throw new InvalidOperationException("Missing HttpsAuthority"))
};
await AssertResponds(client, "/signin");
}
[Fact]
public async Task GetOpenIdConfiguration_returns_ok()
{
using var client = CreateHttpClient();
var response = await GetRaw(client, "/.well-known/openid-configuration");
var payload = await response.Content.ReadAsStringAsync(
TestContext.Current.CancellationToken
);
Assert.True(
response.IsSuccessStatusCode,
$"GET /.well-known/openid-configuration returned {(int)response.StatusCode} {response.StatusCode}. Body: {payload}");
}
public static IEnumerable<object[]> GetLoginIntentData()
{
return new object[][] { new object[] { "testuser", "test" } };
@ -124,4 +149,5 @@ namespace Yavsc.Org.Tests
return true;
}
}
}

View file

@ -1,14 +1,8 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Yavsc.Models;
namespace Yavsc.Org.Tests.Mandatory
{[Collection("Database")]
[Trait("regression", "II")]
[Trait("dev", "wip")]
public class Database: IClassFixture<WebServerFixture>, IDisposable
public class Database : IClassFixture<WebServerFixture>
{
readonly ITestOutputHelper output;
readonly WebServerFixture _serverFixture;
@ -24,15 +18,6 @@ namespace Yavsc.Org.Tests.Mandatory
/// Install all our migrations in a fresh new database.
/// </summary>
public void Dispose()
{
if (_serverFixture!=null)
{
_serverFixture.Dispose();
}
}
[Fact]
public void TestDatabaseMigration()
{

View file

@ -18,7 +18,7 @@ namespace Yavsc.Org.Tests.Smoke;
/// entire pipeline (routing + Razor + IdentityServer + EF + DI)
/// is wired correctly end-to-end.
/// </summary>
public class AccountSmokeTests : SmokeTestBase, IClassFixture<TestWebApplicationFactory>
public class AccountSmokeTests : IClassFixture<TestWebApplicationFactory>
{
private readonly TestWebApplicationFactory _factory;
@ -27,24 +27,7 @@ public class AccountSmokeTests : SmokeTestBase, IClassFixture<TestWebApplication
_factory = factory;
}
[Fact]
public async Task GetSignin_returns_a_page()
{
using var client = _factory.CreateClient();
await AssertResponds(client, "/signin");
}
[Fact]
public async Task GetOpenIdConfiguration_returns_ok()
{
using var client = _factory.CreateClient();
var response = await GetRaw(client, "/.well-known/openid-configuration");
var payload = await response.Content.ReadAsStringAsync();
Assert.True(
response.IsSuccessStatusCode,
$"GET /.well-known/openid-configuration returned {(int)response.StatusCode} {response.StatusCode}. Body: {payload}");
}
[Fact]
public async Task ResourceStore_get_all_resources_does_not_throw()

View file

@ -14,11 +14,13 @@ namespace Yavsc.Org.Tests.Smoke;
/// <c>doc/architecture/decoupage-organisation.md</c>. The smoke
/// here asserts the front-end side of the BC.
/// </summary>
public class BlogSmokeTests : SmokeTestBase, IClassFixture<TestWebApplicationFactory>
public class BlogSmokeTests : BaseTestContext, IClassFixture<TestWebApplicationFactory>
{
private readonly TestWebApplicationFactory _factory;
public BlogSmokeTests(TestWebApplicationFactory factory)
public BlogSmokeTests(TestWebApplicationFactory factory, ITestOutputHelper output,
WebServerFixture webServerFixture)
: base(output, webServerFixture)
{
_factory = factory;
}

View file

@ -1,52 +0,0 @@
namespace Yavsc.Org.Tests.Smoke;
/// <summary>
/// Base for the smoke tests covering the production hosts
/// (Yavsc.Org / Yavsc.Api / Yavsc.Blogs). One smoke test per
/// bounded context (BC): each test hits one GET endpoint and
/// asserts a 2xx or 3xx status, with no follow-up redirect.
/// Together they satisfy the 'Tests d'intégration smoke par BC'
/// item of Jalon 0 in <c>ROADMAP.md</c>.
///
/// Status code policy:
/// - 200 OK : endpoint serves a page.
/// - 302 / 301 : endpoint requires auth and redirects to login
/// (acceptable smoke signal: routing + middleware are wired).
/// - 401 / 403 : endpoint exists but rejects anonymous (acceptable
/// for API smoke tests where the smoke is "the host boots").
/// Anything else (404, 500, connection refused) is a failure.
/// </summary>
public abstract class SmokeTestBase
{
/// <summary>
/// Issue a GET against <paramref name="relativePath"/> on the
/// in-memory test server. Returns the raw HttpResponseMessage
/// without following redirects — the test asserts on the first
/// hop, not the eventual page.
/// </summary>
protected static async Task<HttpResponseMessage> GetRaw(
HttpClient client, string relativePath)
{
Assert.NotNull(client);
var request = new HttpRequestMessage(HttpMethod.Get, relativePath);
return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
}
/// <summary>
/// Smoke assertion: a GET on <paramref name="relativePath"/>
/// returns 2xx (page served) or 3xx (redirect to login) or
/// 401/403 (anonymous rejected by [Authorize]). Anything else
/// — 404 (route missing), 5xx (server crash), connection
/// refused (host not started) — fails the test.
/// </summary>
protected static async Task AssertResponds(
HttpClient client, string relativePath)
{
var response = await GetRaw(client, relativePath);
var status = (int)response.StatusCode;
Assert.True(
status >= 200 && status < 400 || status == 401 || status == 403,
$"GET {relativePath} returned {status} {response.StatusCode}, " +
"expected 2xx/3xx (page or redirect) or 401/403 (auth required).");
}
}

View file

@ -63,6 +63,7 @@ public sealed class WebServerFixture : WebHostFixture
private static string? _sharedTestingUserName;
private static string? _sharedTestingUserPassword;
private static string? _sharedTestingUserEmail;
private static string? _sharedHttpsAuthority;
private static RecordingSmtpClientFactory? _sharedSmtpClientFactory;
public IConfiguration? Configuration { get; private set; }
@ -78,9 +79,19 @@ public sealed class WebServerFixture : WebHostFixture
public RecordingSmtpClientFactory? SmtpClientFactory { get; private set; }
public ILogger? Logger { get; internal set; }
public string? HttpsAuthority { get; private set; }
protected override WebApplicationOptions CreateBuilderOptions()
{
return new WebApplicationOptions
{
ApplicationName = typeof(Yavsc.Program).Assembly.GetName().Name
};
}
protected override WebApplication BuildApp(WebApplicationBuilder builder)
{
var authority = $"https://localhost:{_httpsPort}";
HttpsAuthority = $"https://localhost:{HttpsPort}";
// WebApplication.CreateBuilder defaults WebRootPath to
// {ContentRoot}/wwwroot. The test assembly runs from
@ -99,7 +110,7 @@ public sealed class WebServerFixture : WebHostFixture
["Smtp:Port"] = "465",
["Smtp:UserName"] = "test-user",
["Smtp:Password"] = "test-pass",
["Site:Authority"] = authority
["Site:Authority"] = HttpsAuthority
});
Configuration = builder.Configuration;
@ -182,6 +193,7 @@ public sealed class WebServerFixture : WebHostFixture
_sharedTestingUserName = TestingUserName;
_sharedTestingUserPassword = TestingUserPassword;
_sharedTestingUserEmail = TestingUserEmail;
_sharedHttpsAuthority = HttpsAuthority;
_sharedLogger = app.Services.GetRequiredService<ILoggerFactory>().CreateLogger<WebServerFixture>();
Logger = _sharedLogger;
SmtpClientFactory = smtpFactory;
@ -194,13 +206,42 @@ public sealed class WebServerFixture : WebHostFixture
using var scope = Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
if (db.Database.IsRelational())
{
db.Database.Migrate();
ReseedAuthTestData(scope);
return;
}
ReseedAuthTestData(scope);
}
db.Database.EnsureCreated();
private void ReseedAuthTestData(IServiceScope scope)
{
TestingUserName ??= "Tester";
TestingUserPassword ??= "Test123!";
TestingUserEmail ??= "test@no-reply.com";
TestClientId ??= "testClientId";
TestClientSecret ??= Guid.CreateVersion7().ToString();
TestingUser = null;
EnsureUser(TestingUserName, TestingUserPassword, TestingUserEmail, scope);
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
TestingUser = db.Users.FirstOrDefault(u => u.UserName == TestingUserName);
var configDb = scope.ServiceProvider.GetRequiredService<IdentityServer8.EntityFramework.DbContexts.ConfigurationDbContext>();
var hasClient = configDb.Set<Client>().Any(c => c.ClientId == TestClientId);
if (!hasClient)
{
AddAuthorizedClient(scope, TestClientId, TestClientSecret);
}
_sharedTestClientId = TestClientId;
_sharedTestClientSecret = TestClientSecret;
_sharedTestingUserName = TestingUserName;
_sharedTestingUserPassword = TestingUserPassword;
_sharedTestingUserEmail = TestingUserEmail;
}
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
@ -225,6 +266,7 @@ public sealed class WebServerFixture : WebHostFixture
TestingUserName = _sharedTestingUserName;
TestingUserPassword = _sharedTestingUserPassword;
TestingUserEmail = _sharedTestingUserEmail;
HttpsAuthority = _sharedHttpsAuthority;
SmtpClientFactory = _sharedSmtpClientFactory;
Configuration = _sharedConfiguration;
SiteSettings = _sharedSiteSettings;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,944 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Yavsc.Migrations
{
/// <inheritdoc />
public partial class genericEstimate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Estimates_RdvQueries_CommandId",
table: "Estimates");
migrationBuilder.DropForeignKey(
name: "FK_HairPrestationCollectionItem_HairMultiCutQueries_QueryId",
table: "HairPrestationCollectionItem");
migrationBuilder.DropForeignKey(
name: "FK_ProjectBuildConfiguration_Project_ProjectId",
table: "ProjectBuildConfiguration");
migrationBuilder.DropForeignKey(
name: "FK_RdvQueries_Activities_ActivityCode",
table: "RdvQueries");
migrationBuilder.DropForeignKey(
name: "FK_RdvQueries_AspNetUsers_ClientId",
table: "RdvQueries");
migrationBuilder.DropForeignKey(
name: "FK_RdvQueries_Locations_LocationId",
table: "RdvQueries");
migrationBuilder.DropForeignKey(
name: "FK_RdvQueries_PayPalPayment_PaymentId",
table: "RdvQueries");
migrationBuilder.DropForeignKey(
name: "FK_RdvQueries_Performers_PerformerId",
table: "RdvQueries");
migrationBuilder.DropTable(
name: "HairCutQueries");
migrationBuilder.DropTable(
name: "HairMultiCutQueries");
migrationBuilder.DropTable(
name: "Project");
migrationBuilder.DropPrimaryKey(
name: "PK_RdvQueries",
table: "RdvQueries");
migrationBuilder.RenameTable(
name: "RdvQueries",
newName: "NominativeServiceCommand");
migrationBuilder.RenameIndex(
name: "IX_RdvQueries_PerformerId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_PerformerId");
migrationBuilder.RenameIndex(
name: "IX_RdvQueries_PaymentId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_PaymentId");
migrationBuilder.RenameIndex(
name: "IX_RdvQueries_LocationId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_LocationId");
migrationBuilder.RenameIndex(
name: "IX_RdvQueries_ClientId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_ClientId");
migrationBuilder.RenameIndex(
name: "IX_RdvQueries_ActivityCode",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_ActivityCode");
migrationBuilder.AlterColumn<string>(
name: "Reason",
table: "NominativeServiceCommand",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AlterColumn<int>(
name: "LocationType",
table: "NominativeServiceCommand",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AlterColumn<long>(
name: "LocationId",
table: "NominativeServiceCommand",
type: "bigint",
nullable: true,
oldClrType: typeof(long),
oldType: "bigint");
migrationBuilder.AlterColumn<DateTime>(
name: "EventDate",
table: "NominativeServiceCommand",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone");
migrationBuilder.AddColumn<string>(
name: "AdditionalInfo",
table: "NominativeServiceCommand",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Discriminator",
table: "NominativeServiceCommand",
type: "character varying(34)",
maxLength: 34,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<long>(
name: "GitId",
table: "NominativeServiceCommand",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "HairMultiCutQuery_EventDate",
table: "NominativeServiceCommand",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<long>(
name: "HairMultiCutQuery_LocationId",
table: "NominativeServiceCommand",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Name",
table: "NominativeServiceCommand",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "OwnerId",
table: "NominativeServiceCommand",
type: "text",
nullable: true);
migrationBuilder.AddColumn<long>(
name: "PrestationId",
table: "NominativeServiceCommand",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "RdvQuery_EventDate",
table: "NominativeServiceCommand",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<long>(
name: "RdvQuery_LocationId",
table: "NominativeServiceCommand",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SelectedProfileUserId",
table: "NominativeServiceCommand",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Version",
table: "NominativeServiceCommand",
type: "text",
nullable: true);
migrationBuilder.AddPrimaryKey(
name: "PK_NominativeServiceCommand",
table: "NominativeServiceCommand",
column: "Id");
migrationBuilder.CreateTable(
name: "DictionnaireMetier",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Nom = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Langue = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
DomaineActiviteCode = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DictionnaireMetier", x => x.Id);
table.ForeignKey(
name: "FK_DictionnaireMetier_Activities_DomaineActiviteCode",
column: x => x.DomaineActiviteCode,
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "TermeMetier",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
DictionnaireMetierId = table.Column<long>(type: "bigint", nullable: false),
Mot = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
Definition = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
Langue = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
StatutValidation = table.Column<int>(type: "integer", nullable: false),
ProposeParId = table.Column<string>(type: "character varying(450)", maxLength: 450, nullable: true),
ValideParId = table.Column<string>(type: "character varying(450)", maxLength: 450, nullable: true),
DateSoumission = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
DateValidation = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TermeMetier", x => x.Id);
table.ForeignKey(
name: "FK_TermeMetier_AspNetUsers_ProposeParId",
column: x => x.ProposeParId,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_TermeMetier_AspNetUsers_ValideParId",
column: x => x.ValideParId,
principalTable: "AspNetUsers",
principalColumn: "Id");
table.ForeignKey(
name: "FK_TermeMetier_DictionnaireMetier_DictionnaireMetierId",
column: x => x.DictionnaireMetierId,
principalTable: "DictionnaireMetier",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommand_GitId",
table: "NominativeServiceCommand",
column: "GitId");
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId",
table: "NominativeServiceCommand",
column: "HairMultiCutQuery_LocationId");
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommand_PrestationId",
table: "NominativeServiceCommand",
column: "PrestationId");
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommand_RdvQuery_LocationId",
table: "NominativeServiceCommand",
column: "RdvQuery_LocationId");
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommand_SelectedProfileUserId",
table: "NominativeServiceCommand",
column: "SelectedProfileUserId");
migrationBuilder.CreateIndex(
name: "IX_DictionnaireMetier_DomaineActiviteCode_Langue_Nom",
table: "DictionnaireMetier",
columns: new[] { "DomaineActiviteCode", "Langue", "Nom" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TermeMetier_DictionnaireMetierId_Langue_Mot",
table: "TermeMetier",
columns: new[] { "DictionnaireMetierId", "Langue", "Mot" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TermeMetier_ProposeParId",
table: "TermeMetier",
column: "ProposeParId");
migrationBuilder.CreateIndex(
name: "IX_TermeMetier_ValideParId",
table: "TermeMetier",
column: "ValideParId");
migrationBuilder.AddForeignKey(
name: "FK_Estimates_NominativeServiceCommand_CommandId",
table: "Estimates",
column: "CommandId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~",
table: "HairPrestationCollectionItem",
column: "QueryId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Activities_ActivityCode",
table: "NominativeServiceCommand",
column: "ActivityCode",
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_AspNetUsers_ClientId",
table: "NominativeServiceCommand",
column: "ClientId",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~",
table: "NominativeServiceCommand",
column: "SelectedProfileUserId",
principalTable: "BrusherProfile",
principalColumn: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId",
table: "NominativeServiceCommand",
column: "GitId",
principalTable: "GitRepositoryReference",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_HairPrestation_PrestationId",
table: "NominativeServiceCommand",
column: "PrestationId",
principalTable: "HairPrestation",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~",
table: "NominativeServiceCommand",
column: "HairMultiCutQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Locations_LocationId",
table: "NominativeServiceCommand",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommand",
column: "RdvQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId",
table: "NominativeServiceCommand",
column: "PaymentId",
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Performers_PerformerId",
table: "NominativeServiceCommand",
column: "PerformerId",
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId",
table: "ProjectBuildConfiguration",
column: "ProjectId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Estimates_NominativeServiceCommand_CommandId",
table: "Estimates");
migrationBuilder.DropForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~",
table: "HairPrestationCollectionItem");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Activities_ActivityCode",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_AspNetUsers_ClientId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_HairPrestation_PrestationId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Locations_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Performers_PerformerId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId",
table: "ProjectBuildConfiguration");
migrationBuilder.DropTable(
name: "TermeMetier");
migrationBuilder.DropTable(
name: "DictionnaireMetier");
migrationBuilder.DropPrimaryKey(
name: "PK_NominativeServiceCommand",
table: "NominativeServiceCommand");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommand_GitId",
table: "NominativeServiceCommand");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommand_PrestationId",
table: "NominativeServiceCommand");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommand_RdvQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommand_SelectedProfileUserId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "AdditionalInfo",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "Discriminator",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "GitId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "HairMultiCutQuery_EventDate",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "HairMultiCutQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "Name",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "OwnerId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "PrestationId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "RdvQuery_EventDate",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "RdvQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "SelectedProfileUserId",
table: "NominativeServiceCommand");
migrationBuilder.DropColumn(
name: "Version",
table: "NominativeServiceCommand");
migrationBuilder.RenameTable(
name: "NominativeServiceCommand",
newName: "RdvQueries");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_PerformerId",
table: "RdvQueries",
newName: "IX_RdvQueries_PerformerId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_PaymentId",
table: "RdvQueries",
newName: "IX_RdvQueries_PaymentId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_LocationId",
table: "RdvQueries",
newName: "IX_RdvQueries_LocationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_ClientId",
table: "RdvQueries",
newName: "IX_RdvQueries_ClientId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_ActivityCode",
table: "RdvQueries",
newName: "IX_RdvQueries_ActivityCode");
migrationBuilder.AlterColumn<string>(
name: "Reason",
table: "RdvQueries",
type: "text",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "LocationType",
table: "RdvQueries",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.AlterColumn<long>(
name: "LocationId",
table: "RdvQueries",
type: "bigint",
nullable: false,
defaultValue: 0L,
oldClrType: typeof(long),
oldType: "bigint",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "EventDate",
table: "RdvQueries",
type: "timestamp with time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AddPrimaryKey(
name: "PK_RdvQueries",
table: "RdvQueries",
column: "Id");
migrationBuilder.CreateTable(
name: "HairCutQueries",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ActivityCode = table.Column<string>(type: "text", nullable: false),
ClientId = table.Column<string>(type: "text", nullable: false),
LocationId = table.Column<long>(type: "bigint", nullable: true),
PaymentId = table.Column<string>(type: "text", nullable: true),
PerformerId = table.Column<string>(type: "text", nullable: false),
PrestationId = table.Column<long>(type: "bigint", nullable: false),
SelectedProfileUserId = table.Column<string>(type: "text", nullable: true),
AdditionalInfo = table.Column<string>(type: "text", nullable: false),
Consent = table.Column<bool>(type: "boolean", nullable: false),
DateCreated = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
DateModified = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
EventDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Provisional = table.Column<decimal>(type: "numeric", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
UserCreated = table.Column<string>(type: "text", nullable: false),
UserModified = table.Column<string>(type: "text", nullable: false),
ValidationDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HairCutQueries", x => x.Id);
table.ForeignKey(
name: "FK_HairCutQueries_Activities_ActivityCode",
column: x => x.ActivityCode,
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairCutQueries_AspNetUsers_ClientId",
column: x => x.ClientId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairCutQueries_BrusherProfile_SelectedProfileUserId",
column: x => x.SelectedProfileUserId,
principalTable: "BrusherProfile",
principalColumn: "UserId");
table.ForeignKey(
name: "FK_HairCutQueries_HairPrestation_PrestationId",
column: x => x.PrestationId,
principalTable: "HairPrestation",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairCutQueries_Locations_LocationId",
column: x => x.LocationId,
principalTable: "Locations",
principalColumn: "Id");
table.ForeignKey(
name: "FK_HairCutQueries_PayPalPayment_PaymentId",
column: x => x.PaymentId,
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
table.ForeignKey(
name: "FK_HairCutQueries_Performers_PerformerId",
column: x => x.PerformerId,
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "HairMultiCutQueries",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ActivityCode = table.Column<string>(type: "text", nullable: false),
ClientId = table.Column<string>(type: "text", nullable: false),
LocationId = table.Column<long>(type: "bigint", nullable: false),
PaymentId = table.Column<string>(type: "text", nullable: true),
PerformerId = table.Column<string>(type: "text", nullable: false),
Consent = table.Column<bool>(type: "boolean", nullable: false),
DateCreated = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
DateModified = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Description = table.Column<string>(type: "text", nullable: false),
EventDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Provisional = table.Column<decimal>(type: "numeric", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
UserCreated = table.Column<string>(type: "text", nullable: false),
UserModified = table.Column<string>(type: "text", nullable: false),
ValidationDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_HairMultiCutQueries", x => x.Id);
table.ForeignKey(
name: "FK_HairMultiCutQueries_Activities_ActivityCode",
column: x => x.ActivityCode,
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairMultiCutQueries_AspNetUsers_ClientId",
column: x => x.ClientId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairMultiCutQueries_Locations_LocationId",
column: x => x.LocationId,
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_HairMultiCutQueries_PayPalPayment_PaymentId",
column: x => x.PaymentId,
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
table.ForeignKey(
name: "FK_HairMultiCutQueries_Performers_PerformerId",
column: x => x.PerformerId,
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Project",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
ActivityCode = table.Column<string>(type: "text", nullable: false),
ClientId = table.Column<string>(type: "text", nullable: false),
GitId = table.Column<long>(type: "bigint", nullable: false),
PaymentId = table.Column<string>(type: "text", nullable: true),
PerformerId = table.Column<string>(type: "text", nullable: false),
Consent = table.Column<bool>(type: "boolean", nullable: false),
DateCreated = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
DateModified = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Description = table.Column<string>(type: "text", nullable: true),
Name = table.Column<string>(type: "text", nullable: false),
OwnerId = table.Column<string>(type: "text", nullable: true),
Provisional = table.Column<decimal>(type: "numeric", nullable: true),
Status = table.Column<int>(type: "integer", nullable: false),
UserCreated = table.Column<string>(type: "text", nullable: false),
UserModified = table.Column<string>(type: "text", nullable: false),
ValidationDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Version = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Project", x => x.Id);
table.ForeignKey(
name: "FK_Project_Activities_ActivityCode",
column: x => x.ActivityCode,
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Project_AspNetUsers_ClientId",
column: x => x.ClientId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Project_GitRepositoryReference_GitId",
column: x => x.GitId,
principalTable: "GitRepositoryReference",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Project_PayPalPayment_PaymentId",
column: x => x.PaymentId,
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
table.ForeignKey(
name: "FK_Project_Performers_PerformerId",
column: x => x.PerformerId,
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_ActivityCode",
table: "HairCutQueries",
column: "ActivityCode");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_ClientId",
table: "HairCutQueries",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_LocationId",
table: "HairCutQueries",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_PaymentId",
table: "HairCutQueries",
column: "PaymentId");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_PerformerId",
table: "HairCutQueries",
column: "PerformerId");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_PrestationId",
table: "HairCutQueries",
column: "PrestationId");
migrationBuilder.CreateIndex(
name: "IX_HairCutQueries_SelectedProfileUserId",
table: "HairCutQueries",
column: "SelectedProfileUserId");
migrationBuilder.CreateIndex(
name: "IX_HairMultiCutQueries_ActivityCode",
table: "HairMultiCutQueries",
column: "ActivityCode");
migrationBuilder.CreateIndex(
name: "IX_HairMultiCutQueries_ClientId",
table: "HairMultiCutQueries",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_HairMultiCutQueries_LocationId",
table: "HairMultiCutQueries",
column: "LocationId");
migrationBuilder.CreateIndex(
name: "IX_HairMultiCutQueries_PaymentId",
table: "HairMultiCutQueries",
column: "PaymentId");
migrationBuilder.CreateIndex(
name: "IX_HairMultiCutQueries_PerformerId",
table: "HairMultiCutQueries",
column: "PerformerId");
migrationBuilder.CreateIndex(
name: "IX_Project_ActivityCode",
table: "Project",
column: "ActivityCode");
migrationBuilder.CreateIndex(
name: "IX_Project_ClientId",
table: "Project",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_Project_GitId",
table: "Project",
column: "GitId");
migrationBuilder.CreateIndex(
name: "IX_Project_PaymentId",
table: "Project",
column: "PaymentId");
migrationBuilder.CreateIndex(
name: "IX_Project_PerformerId",
table: "Project",
column: "PerformerId");
migrationBuilder.AddForeignKey(
name: "FK_Estimates_RdvQueries_CommandId",
table: "Estimates",
column: "CommandId",
principalTable: "RdvQueries",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_HairPrestationCollectionItem_HairMultiCutQueries_QueryId",
table: "HairPrestationCollectionItem",
column: "QueryId",
principalTable: "HairMultiCutQueries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ProjectBuildConfiguration_Project_ProjectId",
table: "ProjectBuildConfiguration",
column: "ProjectId",
principalTable: "Project",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RdvQueries_Activities_ActivityCode",
table: "RdvQueries",
column: "ActivityCode",
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RdvQueries_AspNetUsers_ClientId",
table: "RdvQueries",
column: "ClientId",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RdvQueries_Locations_LocationId",
table: "RdvQueries",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RdvQueries_PayPalPayment_PaymentId",
table: "RdvQueries",
column: "PaymentId",
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
migrationBuilder.AddForeignKey(
name: "FK_RdvQueries_Performers_PerformerId",
table: "RdvQueries",
column: "PerformerId",
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -1427,6 +1427,81 @@ namespace Yavsc.Migrations
b.ToTable("ExceptionsSIREN");
});
modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ActivityCode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Consent")
.HasColumnType("boolean");
b.Property<DateTime>("DateCreated")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateModified")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(34)
.HasColumnType("character varying(34)");
b.Property<string>("PaymentId")
.HasColumnType("text");
b.Property<string>("PerformerId")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("Provisional")
.HasColumnType("numeric");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("UserCreated")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserModified")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("ValidationDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ActivityCode");
b.HasIndex("ClientId");
b.HasIndex("PaymentId");
b.HasIndex("PerformerId");
b.ToTable("NominativeServiceCommand");
b.HasDiscriminator<string>("Discriminator").HasValue("NominativeServiceCommand");
b.UseTphMappingStrategy();
});
modelBuilder.Entity("Yavsc.Models.Billing.Signature", b =>
{
b.Property<long>("Id")
@ -1914,168 +1989,6 @@ namespace Yavsc.Migrations
b.ToTable("BrusherProfile");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ActivityCode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("AdditionalInfo")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Consent")
.HasColumnType("boolean");
b.Property<DateTime>("DateCreated")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateModified")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LocationId")
.HasColumnType("bigint");
b.Property<string>("PaymentId")
.HasColumnType("text");
b.Property<string>("PerformerId")
.IsRequired()
.HasColumnType("text");
b.Property<long>("PrestationId")
.HasColumnType("bigint");
b.Property<decimal?>("Provisional")
.HasColumnType("numeric");
b.Property<string>("SelectedProfileUserId")
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("UserCreated")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserModified")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("ValidationDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ActivityCode");
b.HasIndex("ClientId");
b.HasIndex("LocationId");
b.HasIndex("PaymentId");
b.HasIndex("PerformerId");
b.HasIndex("PrestationId");
b.HasIndex("SelectedProfileUserId");
b.ToTable("HairCutQueries");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ActivityCode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Consent")
.HasColumnType("boolean");
b.Property<DateTime>("DateCreated")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateModified")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long>("LocationId")
.HasColumnType("bigint");
b.Property<string>("PaymentId")
.HasColumnType("text");
b.Property<string>("PerformerId")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("Provisional")
.HasColumnType("numeric");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("UserCreated")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserModified")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("ValidationDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ActivityCode");
b.HasIndex("ClientId");
b.HasIndex("LocationId");
b.HasIndex("PaymentId");
b.HasIndex("PerformerId");
b.ToTable("HairMultiCutQueries");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b =>
{
b.Property<long>("Id")
@ -3066,6 +2979,37 @@ namespace Yavsc.Migrations
});
});
modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("DomaineActiviteCode")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("Langue")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Nom")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.HasKey("Id");
b.HasIndex("DomaineActiviteCode", "Langue", "Nom")
.IsUnique();
b.ToTable("DictionnaireMetier");
});
modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b =>
{
b.Property<long>("Id")
@ -3180,7 +3124,7 @@ namespace Yavsc.Migrations
b.ToTable("FormationSettings");
});
modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b =>
modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
@ -3188,77 +3132,51 @@ namespace Yavsc.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ActivityCode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Consent")
.HasColumnType("boolean");
b.Property<DateTime>("DateCreated")
b.Property<DateTime>("DateSoumission")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateModified")
b.Property<DateTime?>("DateValidation")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
b.Property<string>("Definition")
.IsRequired()
.HasColumnType("text");
.HasMaxLength(2000)
.HasColumnType("character varying(2000)");
b.Property<DateTime>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long>("LocationId")
b.Property<long>("DictionnaireMetierId")
.HasColumnType("bigint");
b.Property<int>("LocationType")
b.Property<string>("Langue")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Mot")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("ProposeParId")
.HasMaxLength(450)
.HasColumnType("character varying(450)");
b.Property<int>("StatutValidation")
.HasColumnType("integer");
b.Property<string>("PaymentId")
.HasColumnType("text");
b.Property<string>("PerformerId")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("Provisional")
.HasColumnType("numeric");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("UserCreated")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserModified")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("ValidationDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("ValideParId")
.HasMaxLength(450)
.HasColumnType("character varying(450)");
b.HasKey("Id");
b.HasIndex("ActivityCode");
b.HasIndex("ProposeParId");
b.HasIndex("ClientId");
b.HasIndex("ValideParId");
b.HasIndex("LocationId");
b.HasIndex("DictionnaireMetierId", "Langue", "Mot")
.IsUnique();
b.HasIndex("PaymentId");
b.HasIndex("PerformerId");
b.ToTable("RdvQueries");
b.ToTable("TermeMetier");
});
modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b =>
@ -3327,86 +3245,6 @@ namespace Yavsc.Migrations
b.ToTable("MailingTemplate");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<string>("ActivityCode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("Consent")
.HasColumnType("boolean");
b.Property<DateTime>("DateCreated")
.HasColumnType("timestamp with time zone");
b.Property<DateTime>("DateModified")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<long>("GitId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OwnerId")
.HasColumnType("text");
b.Property<string>("PaymentId")
.HasColumnType("text");
b.Property<string>("PerformerId")
.IsRequired()
.HasColumnType("text");
b.Property<decimal?>("Provisional")
.HasColumnType("numeric");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<string>("UserCreated")
.IsRequired()
.HasColumnType("text");
b.Property<string>("UserModified")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("ValidationDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Version")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ActivityCode");
b.HasIndex("ClientId");
b.HasIndex("GitId");
b.HasIndex("PaymentId");
b.HasIndex("PerformerId");
b.ToTable("Project");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b =>
{
b.Property<long>("Id")
@ -3457,6 +3295,112 @@ namespace Yavsc.Migrations
b.ToTable("GitRepositoryReference");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b =>
{
b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand");
b.Property<string>("AdditionalInfo")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime?>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LocationId")
.HasColumnType("bigint");
b.Property<long>("PrestationId")
.HasColumnType("bigint");
b.Property<string>("SelectedProfileUserId")
.HasColumnType("text");
b.HasIndex("LocationId");
b.HasIndex("PrestationId");
b.HasIndex("SelectedProfileUserId");
b.HasDiscriminator().HasValue("HairCutQuery");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand");
b.Property<DateTime>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long>("LocationId")
.HasColumnType("bigint");
b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t =>
{
t.Property("EventDate")
.HasColumnName("HairMultiCutQuery_EventDate");
t.Property("LocationId")
.HasColumnName("HairMultiCutQuery_LocationId");
});
b.HasDiscriminator().HasValue("HairMultiCutQuery");
});
modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b =>
{
b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand");
b.Property<DateTime>("EventDate")
.HasColumnType("timestamp with time zone");
b.Property<long>("LocationId")
.HasColumnType("bigint");
b.Property<int>("LocationType")
.HasColumnType("integer");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text");
b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t =>
{
t.Property("EventDate")
.HasColumnName("RdvQuery_EventDate");
t.Property("LocationId")
.HasColumnName("RdvQuery_LocationId");
});
b.HasDiscriminator().HasValue("RdvQuery");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b =>
{
b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand");
b.Property<long>("GitId")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("OwnerId")
.HasColumnType("text");
b.Property<string>("Version")
.HasColumnType("text");
b.HasIndex("GitId");
b.HasDiscriminator().HasValue("Project");
});
modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b =>
{
b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null)
@ -3870,7 +3814,7 @@ namespace Yavsc.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query")
b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Query")
.WithMany()
.HasForeignKey("CommandId");
@ -3887,6 +3831,39 @@ namespace Yavsc.Migrations
b.Navigation("Query");
});
modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
.WithMany()
.HasForeignKey("ActivityCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.ApplicationUser", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization")
.WithMany()
.HasForeignKey("PaymentId");
b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile")
.WithMany()
.HasForeignKey("PerformerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("Context");
b.Navigation("PerformerProfile");
b.Navigation("Regularization");
});
modelBuilder.Entity("Yavsc.Models.Billing.Signature", b =>
{
b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate")
@ -4070,100 +4047,6 @@ namespace Yavsc.Migrations
b.Navigation("Schedule");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
.WithMany()
.HasForeignKey("ActivityCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.ApplicationUser", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
.WithMany()
.HasForeignKey("LocationId");
b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization")
.WithMany()
.HasForeignKey("PaymentId");
b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile")
.WithMany()
.HasForeignKey("PerformerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation")
.WithMany()
.HasForeignKey("PrestationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile")
.WithMany()
.HasForeignKey("SelectedProfileUserId");
b.Navigation("Client");
b.Navigation("Context");
b.Navigation("Location");
b.Navigation("PerformerProfile");
b.Navigation("Prestation");
b.Navigation("Regularization");
b.Navigation("SelectedProfile");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
.WithMany()
.HasForeignKey("ActivityCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.ApplicationUser", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization")
.WithMany()
.HasForeignKey("PaymentId");
b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile")
.WithMany()
.HasForeignKey("PerformerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("Context");
b.Navigation("Location");
b.Navigation("PerformerProfile");
b.Navigation("Regularization");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b =>
{
b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation")
@ -4481,6 +4364,17 @@ namespace Yavsc.Migrations
b.Navigation("Context");
});
modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "DomaineActivite")
.WithMany()
.HasForeignKey("DomaineActiviteCode")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("DomaineActivite");
});
modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b =>
{
b.HasOne("Yavsc.Models.Workflow.Country", "Country")
@ -4511,45 +4405,27 @@ namespace Yavsc.Migrations
b.Navigation("Performer");
});
modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b =>
modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
.WithMany()
.HasForeignKey("ActivityCode")
b.HasOne("Yavsc.Models.Workflow.DictionnaireMetier", "DictionnaireMetier")
.WithMany("Termes")
.HasForeignKey("DictionnaireMetierId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.ApplicationUser", "Client")
b.HasOne("Yavsc.Models.ApplicationUser", "ProposePar")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.HasForeignKey("ProposeParId");
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
b.HasOne("Yavsc.Models.ApplicationUser", "ValidePar")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.HasForeignKey("ValideParId");
b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization")
.WithMany()
.HasForeignKey("PaymentId");
b.Navigation("DictionnaireMetier");
b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile")
.WithMany()
.HasForeignKey("PerformerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProposePar");
b.Navigation("Client");
b.Navigation("Context");
b.Navigation("Location");
b.Navigation("PerformerProfile");
b.Navigation("Regularization");
b.Navigation("ValidePar");
});
modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b =>
@ -4571,47 +4447,6 @@ namespace Yavsc.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b =>
{
b.HasOne("Yavsc.Models.Workflow.Activity", "Context")
.WithMany()
.HasForeignKey("ActivityCode")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.ApplicationUser", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository")
.WithMany()
.HasForeignKey("GitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization")
.WithMany()
.HasForeignKey("PaymentId");
b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile")
.WithMany()
.HasForeignKey("PerformerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("Context");
b.Navigation("PerformerProfile");
b.Navigation("Regularization");
b.Navigation("Repository");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b =>
{
b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject")
@ -4632,6 +4467,62 @@ namespace Yavsc.Migrations
b.Navigation("Owner");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b =>
{
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
.WithMany()
.HasForeignKey("LocationId");
b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation")
.WithMany()
.HasForeignKey("PrestationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile")
.WithMany()
.HasForeignKey("SelectedProfileUserId");
b.Navigation("Location");
b.Navigation("Prestation");
b.Navigation("SelectedProfile");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b =>
{
b.HasOne("Yavsc.Models.Relationship.Location", "Location")
.WithMany()
.HasForeignKey("LocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Location");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b =>
{
b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository")
.WithMany()
.HasForeignKey("GitId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Repository");
});
modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b =>
{
b.Navigation("Properties");
@ -4746,11 +4637,6 @@ namespace Yavsc.Migrations
b.Navigation("Links");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.Navigation("Prestations");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b =>
{
b.Navigation("Taints");
@ -4795,6 +4681,11 @@ namespace Yavsc.Migrations
b.Navigation("Services");
});
modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b =>
{
b.Navigation("Termes");
});
modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b =>
{
b.Navigation("Activity");
@ -4805,6 +4696,11 @@ namespace Yavsc.Migrations
b.Navigation("CoWorking");
});
modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b =>
{
b.Navigation("Prestations");
});
modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b =>
{
b.Navigation("Configurations");

View file

@ -21,7 +21,7 @@ namespace Yavsc.Models.Billing
/// </summary>
/// <returns></returns>
[ForeignKey("CommandId"),JsonIgnore]
public RdvQuery Query { get; set; }
public NominativeServiceCommand? Query { get; set; }
public string Description { get; set; }
public string Title { get; set; }

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Yavsc.Interface;

View file

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
@ -37,6 +38,7 @@ public abstract class WebHostFixture : IBackendFixture
private static readonly object _sync = new object();
private static WebApplication? _app;
private static bool _isInitialized;
private static bool _shutdownHooksRegistered;
private static int _instanceCount;
private static readonly List<string> _sharedAddresses = new();
private static IServiceProvider? _sharedServices;
@ -63,6 +65,8 @@ public abstract class WebHostFixture : IBackendFixture
{
lock (_sync)
{
RegisterShutdownHooks();
if (!_isInitialized)
{
InitializeAsync().GetAwaiter().GetResult();
@ -114,11 +118,19 @@ public abstract class WebHostFixture : IBackendFixture
/// listen port.</summary>
protected virtual int HttpsPort => 5101;
/// <summary>Options used to create the WebApplicationBuilder.
/// Derived fixtures can override (for example, to set
/// ApplicationName for MVC controller discovery).</summary>
protected virtual WebApplicationOptions CreateBuilderOptions()
{
return new WebApplicationOptions();
}
public WebApplication App { get; private set; }
private async Task InitializeAsync()
{
var builder = WebApplication.CreateBuilder();
var builder = WebApplication.CreateBuilder(CreateBuilderOptions());
builder.WebHost.ConfigureKestrel(options =>
{
@ -158,23 +170,40 @@ public abstract class WebHostFixture : IBackendFixture
_instanceCount--;
}
IsInitialized = false;
IsInitialized = _isInitialized;
if (_instanceCount > 0)
// Keep the shared host alive for the whole test process.
// Disposing per class/collection can race with other test
// classes and intermittently drop the listener mid-run.
}
}
private static void RegisterShutdownHooks()
{
if (_shutdownHooksRegistered)
{
return;
}
AppDomain.CurrentDomain.ProcessExit += (_, __) => ShutdownSharedHost();
AssemblyLoadContext.Default.Unloading += _ => ShutdownSharedHost();
_shutdownHooksRegistered = true;
}
private static void ShutdownSharedHost()
{
lock (_sync)
{
if (!_isInitialized || _app is null)
{
return;
}
if (!_isInitialized)
{
return;
}
_app?.StopAsync().GetAwaiter().GetResult();
_app.StopAsync().GetAwaiter().GetResult();
_app = null;
_isInitialized = false;
_sharedAddresses.Clear();
_sharedServices = null;
_sharedAddresses.Clear();
}
}