Exploration des activités
This commit is contained in:
parent
2a8a4fc4a4
commit
7b92745153
20 changed files with 743 additions and 61 deletions
|
|
@ -1,5 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## [1.0.8-rc10] - unstable
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## [1.0.8-rc9] - unstable
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ public class ActivitiesPageViewModelTests
|
|||
var client = new ActivityApiClient(api, "https://business.example/api/v1/");
|
||||
|
||||
await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken);
|
||||
await client.GetPerformersAsync("brush-pro", TestContext.Current.CancellationToken);
|
||||
await client.GetUsersAsync("brush-pro", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("https://business.example/api/v1/activity/catalog?parentCode=brush", api.Paths[0]);
|
||||
Assert.Equal("https://business.example/api/v1/activity/brush-pro/performers", api.Paths[1]);
|
||||
Assert.Equal("https://business.example/api/v1/activity/brush-pro/users", api.Paths[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -34,12 +34,20 @@ public class ActivitiesPageViewModelTests
|
|||
Assert.Equal("brush", vm.CurrentActivity?.Code);
|
||||
Assert.Single(vm.Performers);
|
||||
Assert.Equal("Alice", vm.Performers[0].UserName);
|
||||
Assert.True(vm.Performers[0].HasPerformerProfile);
|
||||
Assert.True(vm.Performers[0].IsPerformerActive);
|
||||
Assert.Equal("Actif", vm.Performers[0].PerformerStatusBadgeLabel);
|
||||
Assert.Equal("Pas d'autre activité", vm.Performers[0].ExtraActivityLabel);
|
||||
|
||||
await vm.ShowSpecializationAsync(vm.Specializations[0]);
|
||||
|
||||
Assert.Equal("brush-pro", vm.CurrentActivity?.Code);
|
||||
Assert.Single(vm.Performers);
|
||||
Assert.Equal("Bob", vm.Performers[0].UserName);
|
||||
Assert.True(vm.Performers[0].HasPerformerProfile);
|
||||
Assert.False(vm.Performers[0].IsPerformerActive);
|
||||
Assert.Equal("Inactif", vm.Performers[0].PerformerStatusBadgeLabel);
|
||||
Assert.Equal("Autres spécialisations: 2", vm.Performers[0].ExtraActivityLabel);
|
||||
Assert.Contains("brush pro", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await vm.ShowSpecializationAsync(null);
|
||||
|
|
@ -86,14 +94,14 @@ public class ActivitiesPageViewModelTests
|
|||
|
||||
if (typeof(T) == typeof(List<ActivityPerformerDto>))
|
||||
{
|
||||
var performers = path.EndsWith("brush-pro/performers", StringComparison.Ordinal)
|
||||
var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal)
|
||||
? new List<ActivityPerformerDto>
|
||||
{
|
||||
new() { PerformerId = "pro-2", UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro" }
|
||||
new() { PerformerId = "pro-2", HasPerformerProfile = true, Active = false, UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro", ExtraActivityCount = 2 }
|
||||
}
|
||||
: new List<ActivityPerformerDto>
|
||||
{
|
||||
new() { PerformerId = "pro-1", UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush" }
|
||||
new() { PerformerId = "pro-1", HasPerformerProfile = true, Active = true, UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush", ExtraActivityCount = 0 }
|
||||
};
|
||||
|
||||
return Task.FromResult((T)(object)performers);
|
||||
|
|
|
|||
|
|
@ -86,11 +86,6 @@ private void ConfigureRootView(MainView rootView)
|
|||
rootView.NavRoot.PopToRootAsync();
|
||||
};
|
||||
|
||||
sessionStatus.LoginSucceeded += async () =>
|
||||
{
|
||||
await PushMainPageAsync();
|
||||
};
|
||||
|
||||
rootView.SessionBanner.DataContext = sessionStatus;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
|||
public partial ActivityBrowseItemDto? SelectedSpecialization { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<ActivityPerformerDto> Performers { get; set; } = new();
|
||||
public partial ObservableCollection<ActivityUserDisplayItem> Performers { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsBusy { get; set; }
|
||||
|
|
@ -122,14 +122,14 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
|||
{
|
||||
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
||||
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
||||
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Activities = new ObservableCollection<ActivityBrowseItemDto>();
|
||||
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
||||
StatusMessage = $"Erreur: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
|
|
@ -158,7 +158,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
|||
|
||||
if (activity is null)
|
||||
{
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -197,18 +197,19 @@ public partial class ActivitiesPageViewModel : ViewModelBase
|
|||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var list = await _client.GetPerformersAsync(activity.Code);
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>(list ?? new());
|
||||
StatusMessage = $"{activity.Name} · {Performers.Count} prestataire(s)";
|
||||
var list = await _client.GetUsersAsync(activity.Code);
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>((list ?? new())
|
||||
.Select(ActivityUserDisplayItem.FromDto));
|
||||
StatusMessage = $"{activity.Name} · {Performers.Count} utilisateur(s)";
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
|
||||
{
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
||||
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Performers = new ObservableCollection<ActivityPerformerDto>();
|
||||
Performers = new ObservableCollection<ActivityUserDisplayItem>();
|
||||
StatusMessage = $"Erreur: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
39
src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs
Normal file
39
src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using Yavsc.Abstract.Workflow;
|
||||
|
||||
namespace PostIt.ViewModels;
|
||||
|
||||
public sealed class ActivityUserDisplayItem
|
||||
{
|
||||
public string PerformerId { get; init; } = string.Empty;
|
||||
public bool HasPerformerProfile { get; init; }
|
||||
public string PerformerBadgeLabel { get; init; } = "Profil pro";
|
||||
public bool IsPerformerActive { get; init; }
|
||||
public string PerformerStatusBadgeLabel { get; init; } = "Inactif";
|
||||
public string PerformerStatusBadgeBackground { get; init; } = "#FDECEA";
|
||||
public string PerformerStatusBadgeBorder { get; init; } = "#C62828";
|
||||
public string PerformerStatusBadgeForeground { get; init; } = "#8E0000";
|
||||
public string UserName { get; init; } = string.Empty;
|
||||
public string WebSite { get; init; } = string.Empty;
|
||||
public int ExtraActivityCount { get; init; }
|
||||
public string ExtraActivityLabel { get; init; } = "Pas d'autre activité";
|
||||
|
||||
public static ActivityUserDisplayItem FromDto(ActivityPerformerDto dto)
|
||||
{
|
||||
return new ActivityUserDisplayItem
|
||||
{
|
||||
PerformerId = dto.PerformerId,
|
||||
HasPerformerProfile = dto.HasPerformerProfile,
|
||||
UserName = dto.UserName,
|
||||
WebSite = dto.WebSite,
|
||||
IsPerformerActive = dto.Active,
|
||||
PerformerStatusBadgeLabel = dto.Active ? "Actif" : "Inactif",
|
||||
PerformerStatusBadgeBackground = dto.Active ? "#E6F7EC" : "#FDECEA",
|
||||
PerformerStatusBadgeBorder = dto.Active ? "#2E7D32" : "#C62828",
|
||||
PerformerStatusBadgeForeground = dto.Active ? "#1B5E20" : "#8E0000",
|
||||
ExtraActivityCount = dto.ExtraActivityCount,
|
||||
ExtraActivityLabel = dto.ExtraActivityCount == 0
|
||||
? "Pas d'autre activité"
|
||||
: $"Autres spécialisations: {dto.ExtraActivityCount}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,20 @@
|
|||
x:DataType="vm:ActivitiesPageViewModel"
|
||||
Header="Activités">
|
||||
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
|
||||
<Grid.Styles>
|
||||
<Style Selector="Border.user-badge">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="Padding" Value="5,0" />
|
||||
<Setter Property="MinHeight" Value="16" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.user-badge-text">
|
||||
<Setter Property="FontSize" Value="9" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Rafraîchir" Command="{Binding RefreshCommand}" />
|
||||
<TextBlock Text="Catalogue des activités" FontWeight="Bold" VerticalAlignment="Center" />
|
||||
|
|
@ -22,7 +36,7 @@
|
|||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
|
||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
|
||||
FontSize="11" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
|
@ -43,7 +57,7 @@
|
|||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
|
||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
|
||||
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
|
||||
FontSize="11" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
|
@ -52,17 +66,39 @@
|
|||
</Grid>
|
||||
|
||||
<Grid Grid.Column="4" RowDefinitions="Auto,Auto,*">
|
||||
<TextBlock Grid.Row="0" Text="Prestataires" FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock Grid.Row="0" Text="Utilisateurs" FontWeight="Bold" Margin="0,0,0,8" />
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{Binding CurrentActivityLabel, StringFormat='Activité affichée : {0}'}"
|
||||
FontSize="11" Opacity="0.7" Margin="0,0,0,8" />
|
||||
<ListBox Grid.Row="2" ItemsSource="{Binding Performers}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="wf:ActivityPerformerDto">
|
||||
<DataTemplate x:DataType="vm:ActivityUserDisplayItem">
|
||||
<StackPanel Spacing="2" Margin="0,0,0,8">
|
||||
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
|
||||
<Border IsVisible="{Binding HasPerformerProfile}"
|
||||
Classes="user-badge"
|
||||
Background="#D9F5E5"
|
||||
BorderBrush="#2E7D32"
|
||||
>
|
||||
<TextBlock Text="{Binding PerformerBadgeLabel}"
|
||||
Classes="user-badge-text"
|
||||
Foreground="#1B5E20"
|
||||
/>
|
||||
</Border>
|
||||
<Border IsVisible="{Binding HasPerformerProfile}"
|
||||
Classes="user-badge"
|
||||
Background="{Binding PerformerStatusBadgeBackground}"
|
||||
BorderBrush="{Binding PerformerStatusBadgeBorder}"
|
||||
>
|
||||
<TextBlock Text="{Binding PerformerStatusBadgeLabel}"
|
||||
Classes="user-badge-text"
|
||||
Foreground="{Binding PerformerStatusBadgeForeground}"
|
||||
/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding WebSite}" FontSize="11" Opacity="0.7" />
|
||||
<TextBlock Text="{Binding ExtraActivityCount, StringFormat='Autres spécialisations: {0}'}"
|
||||
<TextBlock Text="{Binding ExtraActivityLabel}"
|
||||
FontSize="11" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace Yavsc.Abstract.Workflow;
|
|||
public sealed class ActivityPerformerDto
|
||||
{
|
||||
public string PerformerId { get; set; } = string.Empty;
|
||||
public bool HasPerformerProfile { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public bool Active { get; set; }
|
||||
public bool AcceptNotifications { get; set; }
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public sealed class ActivityApiClient
|
|||
return _api.CallAsync<List<ActivityBrowseItemDto>>(HttpMethod.Get, Absolute(path), ct: ct);
|
||||
}
|
||||
|
||||
public Task<List<ActivityPerformerDto>> GetPerformersAsync(
|
||||
public Task<List<ActivityPerformerDto>> GetUsersAsync(
|
||||
string activityCode,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
|
|
@ -48,9 +48,14 @@ public sealed class ActivityApiClient
|
|||
|
||||
return _api.CallAsync<List<ActivityPerformerDto>>(
|
||||
HttpMethod.Get,
|
||||
Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/performers"),
|
||||
Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/users"),
|
||||
ct: ct);
|
||||
}
|
||||
|
||||
public Task<List<ActivityPerformerDto>> GetPerformersAsync(
|
||||
string activityCode,
|
||||
CancellationToken ct = default)
|
||||
=> GetUsersAsync(activityCode, ct);
|
||||
|
||||
private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString();
|
||||
}
|
||||
|
|
|
|||
161
src/Yavsc.Api.Test/ActivityApiControllerTests.cs
Normal file
161
src/Yavsc.Api.Test/ActivityApiControllerTests.cs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yavsc.Abstract.Workflow;
|
||||
using Yavsc.Api.Test.Fixtures;
|
||||
using Yavsc.Tests.Shared;
|
||||
|
||||
namespace Yavsc.Api.Test;
|
||||
|
||||
[Collection("Yavsc Api")]
|
||||
public sealed class ActivityApiControllerTests : IClassFixture<ApiWebServerFixture>
|
||||
{
|
||||
private readonly ApiWebServerFixture _fixture;
|
||||
|
||||
public ActivityApiControllerTests(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 GetUsers_returns_declared_user_for_exact_activity_code()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using var http = NewClient();
|
||||
|
||||
var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(payload);
|
||||
Assert.Single(payload!);
|
||||
Assert.Equal("alice", payload[0].PerformerId);
|
||||
Assert.Equal("alice", payload[0].UserName);
|
||||
Assert.True(payload[0].HasPerformerProfile);
|
||||
Assert.True(payload[0].Active);
|
||||
Assert.Equal("dev", payload[0].ActivityCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUsers_returns_user_even_when_performer_inactive()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using (var scope = _fixture.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
||||
var performer = db.Performers.Single(p => p.PerformerId == "alice");
|
||||
performer.Active = false;
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
using var http = NewClient();
|
||||
var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(payload);
|
||||
Assert.Single(payload!);
|
||||
Assert.Equal("alice", payload[0].PerformerId);
|
||||
Assert.False(payload[0].Active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Catalog_and_Performers_are_consistent_for_dev_activity()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using var http = NewClient();
|
||||
|
||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
||||
|
||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(catalog);
|
||||
|
||||
var dev = catalog!.Single(a => a.Code == "dev");
|
||||
Assert.True(dev.PerformerCount > 0);
|
||||
|
||||
var performersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
|
||||
|
||||
var performers = await performersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(performers);
|
||||
Assert.Equal(dev.PerformerCount, performers!.Count);
|
||||
Assert.Contains(performers, p => p.PerformerId == "alice");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Catalog_does_not_list_activity_without_declaration()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using var http = NewClient();
|
||||
|
||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
||||
|
||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(catalog);
|
||||
Assert.DoesNotContain(catalog!, a => a.Code == "ghost");
|
||||
Assert.Contains(catalog!, a => a.Code == "dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Catalog_lists_declared_activity_even_when_performer_inactive()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using var http = NewClient();
|
||||
|
||||
var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode);
|
||||
|
||||
var catalog = await catalogResponse.Content.ReadFromJsonAsync<List<ActivityBrowseItemDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(catalog);
|
||||
Assert.Contains(catalog!, a => a.Code == "declared-only");
|
||||
|
||||
var response = await http.GetAsync("/api/v1/activity/declared-only/users", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var payload = await response.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(payload);
|
||||
Assert.Single(payload!);
|
||||
Assert.Equal("bob", payload[0].PerformerId);
|
||||
Assert.Equal("bob", payload[0].UserName);
|
||||
Assert.True(payload[0].HasPerformerProfile);
|
||||
Assert.False(payload[0].Active);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Performers_alias_returns_same_payload_as_users_endpoint()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
using var http = NewClient();
|
||||
|
||||
var usersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken);
|
||||
var performersResponse = await http.GetAsync("/api/v1/activity/dev/performers", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, usersResponse.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode);
|
||||
|
||||
var usersPayload = await usersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
var performersPayload = await performersResponse.Content.ReadFromJsonAsync<List<ActivityPerformerDto>>(TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(usersPayload);
|
||||
Assert.NotNull(performersPayload);
|
||||
Assert.Equal(usersPayload!.Count, performersPayload!.Count);
|
||||
Assert.Equal(usersPayload[0].PerformerId, performersPayload[0].PerformerId);
|
||||
Assert.Equal(usersPayload[0].UserName, performersPayload[0].UserName);
|
||||
}
|
||||
}
|
||||
6
src/Yavsc.Api.Test/ApiCollection.cs
Normal file
6
src/Yavsc.Api.Test/ApiCollection.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace Yavsc.Api.Test;
|
||||
|
||||
[CollectionDefinition("Yavsc Api")]
|
||||
public sealed class ApiCollection
|
||||
{
|
||||
}
|
||||
7
src/Yavsc.Api.Test/Directory.Packages.props
Normal file
7
src/Yavsc.Api.Test/Directory.Packages.props
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<Project>
|
||||
<!--
|
||||
Yavsc.Api.Test has no project-specific package versions. All
|
||||
package versions are declared at the repository root.
|
||||
-->
|
||||
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
|
||||
</Project>
|
||||
210
src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs
Normal file
210
src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
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.Models;
|
||||
using Yavsc.Models.Relationship;
|
||||
using Yavsc.Models.Workflow;
|
||||
using Yavsc.Tests.Shared;
|
||||
|
||||
namespace Yavsc.Api.Test.Fixtures;
|
||||
|
||||
public sealed class ApiWebServerFixture : WebHostFixture
|
||||
{
|
||||
protected override int HttpsPort => 5104;
|
||||
|
||||
private static SqliteConnection? _sharedSqliteConnection;
|
||||
private static readonly object _sqliteLock = new();
|
||||
|
||||
protected override WebApplication BuildApp(WebApplicationBuilder builder)
|
||||
{
|
||||
SqliteConnection sharedConnection;
|
||||
lock (_sqliteLock)
|
||||
{
|
||||
if (_sharedSqliteConnection is null)
|
||||
{
|
||||
_sharedSqliteConnection = new SqliteConnection(
|
||||
"Data Source=YavscApiTests;Mode=Memory;Cache=Shared");
|
||||
_sharedSqliteConnection.Open();
|
||||
}
|
||||
sharedConnection = _sharedSqliteConnection;
|
||||
}
|
||||
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(opt =>
|
||||
opt.UseSqlite(sharedConnection));
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddApplicationPart(typeof(ActivityApiController).Assembly);
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddAuthentication("Bearer")
|
||||
.AddJwtBearer("Bearer", options =>
|
||||
{
|
||||
options.IncludeErrorDetails = true;
|
||||
options.MapInboundClaims = false;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = TestTokenIssuer.Issuer,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = TestTokenIssuer.SigningKey,
|
||||
NameClaimType = "sub",
|
||||
RoleClaimType = Yavsc.Constants.RoleClaimType,
|
||||
};
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
protected override async Task<WebApplication> ConfigurePipelineAsync(WebApplication app)
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
return app;
|
||||
}
|
||||
|
||||
public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal));
|
||||
|
||||
public void ResetAndSeedActivityGraph()
|
||||
{
|
||||
using var scope = Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
|
||||
db.Database.EnsureDeleted();
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
var user = new ApplicationUser
|
||||
{
|
||||
Id = "alice",
|
||||
UserName = "alice",
|
||||
Email = "alice@example.test",
|
||||
EmailConfirmed = true,
|
||||
FullName = "Alice",
|
||||
};
|
||||
db.Users.Add(user);
|
||||
|
||||
var location = new Location
|
||||
{
|
||||
Address = "1 rue du Test",
|
||||
Latitude = 48.8566,
|
||||
Longitude = 2.3522,
|
||||
};
|
||||
db.Add(location);
|
||||
db.SaveChanges();
|
||||
|
||||
var activity = new Activity
|
||||
{
|
||||
Code = "dev",
|
||||
Name = "Dev",
|
||||
Hidden = false,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
DateModified = DateTime.UtcNow,
|
||||
};
|
||||
db.Activities.Add(activity);
|
||||
|
||||
db.Activities.Add(new Activity
|
||||
{
|
||||
Code = "ghost",
|
||||
Name = "Ghost",
|
||||
Hidden = false,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
DateModified = DateTime.UtcNow,
|
||||
});
|
||||
|
||||
var performer = new PerformerProfile
|
||||
{
|
||||
PerformerId = "alice",
|
||||
SIREN = "123456789",
|
||||
OrganizationAddressId = location.Id,
|
||||
AcceptNotifications = true,
|
||||
AcceptPublicContact = true,
|
||||
Active = true,
|
||||
Rate = 5,
|
||||
WebSite = "https://alice.dev",
|
||||
};
|
||||
db.Performers.Add(performer);
|
||||
|
||||
db.UserActivities.Add(new UserActivity
|
||||
{
|
||||
UserId = "alice",
|
||||
DoesCode = "dev",
|
||||
Weight = 100,
|
||||
});
|
||||
|
||||
db.Users.Add(new ApplicationUser
|
||||
{
|
||||
Id = "bob",
|
||||
UserName = "bob",
|
||||
Email = "bob@example.test",
|
||||
EmailConfirmed = true,
|
||||
FullName = "Bob",
|
||||
});
|
||||
|
||||
db.Activities.Add(new Activity
|
||||
{
|
||||
Code = "declared-only",
|
||||
Name = "Declared Only",
|
||||
Hidden = false,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
DateModified = DateTime.UtcNow,
|
||||
});
|
||||
|
||||
db.Performers.Add(new PerformerProfile
|
||||
{
|
||||
PerformerId = "bob",
|
||||
SIREN = "987654321",
|
||||
OrganizationAddressId = location.Id,
|
||||
AcceptNotifications = false,
|
||||
AcceptPublicContact = false,
|
||||
Active = false,
|
||||
Rate = 0,
|
||||
WebSite = "",
|
||||
});
|
||||
|
||||
db.UserActivities.Add(new UserActivity
|
||||
{
|
||||
UserId = "bob",
|
||||
DoesCode = "declared-only",
|
||||
Weight = 10,
|
||||
});
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_sqliteLock)
|
||||
{
|
||||
if (_sharedSqliteConnection is not null)
|
||||
{
|
||||
_sharedSqliteConnection.Close();
|
||||
_sharedSqliteConnection.Dispose();
|
||||
_sharedSqliteConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/Yavsc.Api.Test/HomeControllerTests.cs
Normal file
69
src/Yavsc.Api.Test/HomeControllerTests.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Yavsc.Api.Test.Fixtures;
|
||||
using Yavsc.Controllers;
|
||||
using Yavsc.Models.Workflow;
|
||||
|
||||
namespace Yavsc.Api.Test;
|
||||
|
||||
[Collection("Yavsc Api")]
|
||||
public sealed class HomeControllerTests : IClassFixture<ApiWebServerFixture>
|
||||
{
|
||||
private readonly ApiWebServerFixture _fixture;
|
||||
|
||||
public HomeControllerTests(ApiWebServerFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Index_does_not_list_activity_without_declaration()
|
||||
{
|
||||
_fixture.ResetAndSeedActivityGraph();
|
||||
|
||||
using var scope = _fixture.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<Yavsc.Models.ApplicationDbContext>();
|
||||
|
||||
var controller = new HomeController(
|
||||
NullLogger<HomeController>.Instance,
|
||||
localizer: null!,
|
||||
context: db,
|
||||
settingsOptions: Options.Create(new SiteSettings()),
|
||||
env: new TestEnvironment());
|
||||
|
||||
controller.ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext
|
||||
{
|
||||
User = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim("sub", "alice"),
|
||||
new Claim(ClaimTypes.NameIdentifier, "alice")
|
||||
}, "Bearer"))
|
||||
}
|
||||
};
|
||||
|
||||
var result = await controller.Index(id: null);
|
||||
var view = Assert.IsType<ViewResult>(result);
|
||||
var model = Assert.IsAssignableFrom<IEnumerable<Activity>>(view.Model);
|
||||
|
||||
Assert.Contains(model, a => a.Code == "dev");
|
||||
Assert.DoesNotContain(model, a => a.Code == "ghost");
|
||||
}
|
||||
|
||||
private sealed class TestEnvironment : IWebHostEnvironment
|
||||
{
|
||||
public string ApplicationName { get; set; } = "Yavsc.Api.Test";
|
||||
public IFileProvider WebRootFileProvider { get; set; } = null!;
|
||||
public string WebRootPath { get; set; } = string.Empty;
|
||||
public string EnvironmentName { get; set; } = "Development";
|
||||
public string ContentRootPath { get; set; } = string.Empty;
|
||||
public IFileProvider ContentRootFileProvider { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
33
src/Yavsc.Api.Test/Yavsc.Api.Test.csproj
Normal file
33
src/Yavsc.Api.Test/Yavsc.Api.Test.csproj
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Yavsc.Api.Test</RootNamespace>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<PackageReference Include="xunit.v3.common" />
|
||||
<PackageReference Include="xunit.v3.extensibility.core" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Yavsc.Abstract\Yavsc.Abstract.csproj" />
|
||||
<ProjectReference Include="..\Yavsc.Server\Yavsc.Server.csproj" />
|
||||
<ProjectReference Include="..\Yavsc.Api\Yavsc.Api.csproj" />
|
||||
<ProjectReference Include="..\Yavsc.Org\Yavsc.Org.csproj" />
|
||||
<ProjectReference Include="..\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -53,8 +53,7 @@ namespace Yavsc.Controllers
|
|||
|
||||
var performerCounts = await (
|
||||
from ua in _context.UserActivities.AsNoTracking()
|
||||
join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId
|
||||
where p.Active && !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode)
|
||||
where !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode)
|
||||
group ua by ua.DoesCode into g
|
||||
select new
|
||||
{
|
||||
|
|
@ -63,11 +62,19 @@ namespace Yavsc.Controllers
|
|||
})
|
||||
.ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken);
|
||||
|
||||
return Ok(activities.Select(a => ToBrowseItem(a, performerCounts)).ToList());
|
||||
var filteredActivities = activities
|
||||
.Where(a =>
|
||||
(performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
|
||||
|| (a.Children ?? new List<Activity>())
|
||||
.Where(c => !c.Hidden)
|
||||
.Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
|
||||
.ToList();
|
||||
|
||||
return Ok(filteredActivities.Select(a => ToBrowseItem(a, performerCounts)).ToList());
|
||||
}
|
||||
|
||||
[HttpGet("{id}/performers")]
|
||||
public async Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetPerformers(
|
||||
[HttpGet("{id}/users")]
|
||||
public async Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetUsers(
|
||||
[FromRoute] string id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
|
@ -84,35 +91,54 @@ namespace Yavsc.Controllers
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
var performers = await (
|
||||
from p in _context.Performers.AsNoTracking()
|
||||
join ua in _context.UserActivities.AsNoTracking() on p.PerformerId equals ua.UserId
|
||||
join u in _context.ApplicationUser.AsNoTracking() on p.PerformerId equals u.Id into users
|
||||
var users = await QueryDeclaredUsersAsync(id, activity.Name, cancellationToken);
|
||||
|
||||
return Ok(users);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/performers")]
|
||||
public Task<ActionResult<IEnumerable<ActivityPerformerDto>>> GetPerformers(
|
||||
[FromRoute] string id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Backward-compatible alias kept for existing clients.
|
||||
return GetUsers(id, cancellationToken);
|
||||
}
|
||||
|
||||
private Task<List<ActivityPerformerDto>> QueryDeclaredUsersAsync(
|
||||
string activityCode,
|
||||
string activityName,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return (
|
||||
from ua in _context.UserActivities.AsNoTracking()
|
||||
join u in _context.ApplicationUser.AsNoTracking() on ua.UserId equals u.Id into users
|
||||
from user in users.DefaultIfEmpty()
|
||||
where p.Active && ua.DoesCode == id
|
||||
orderby p.Rate
|
||||
join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId into performerProfiles
|
||||
from performer in performerProfiles.DefaultIfEmpty()
|
||||
where ua.DoesCode == activityCode
|
||||
orderby user != null ? user.UserName : ua.UserId
|
||||
select new ActivityPerformerDto
|
||||
{
|
||||
PerformerId = p.PerformerId,
|
||||
PerformerId = ua.UserId,
|
||||
HasPerformerProfile = performer != null,
|
||||
UserName = user != null ? (user.UserName ?? string.Empty) : string.Empty,
|
||||
Active = p.Active,
|
||||
AcceptNotifications = p.AcceptNotifications,
|
||||
AcceptPublicContact = p.AcceptPublicContact,
|
||||
WebSite = p.WebSite ?? string.Empty,
|
||||
ActivityCode = id,
|
||||
ActivityName = activity.Name,
|
||||
Active = performer != null && performer.Active,
|
||||
AcceptNotifications = performer != null && performer.AcceptNotifications,
|
||||
AcceptPublicContact = performer != null && performer.AcceptPublicContact,
|
||||
WebSite = performer != null ? (performer.WebSite ?? string.Empty) : string.Empty,
|
||||
ActivityCode = activityCode,
|
||||
ActivityName = activityName,
|
||||
SettingsClassName = _context.Activities
|
||||
.Where(a => a.Code == id)
|
||||
.Where(a => a.Code == activityCode)
|
||||
.Select(a => a.SettingsClassName)
|
||||
.FirstOrDefault() ?? string.Empty,
|
||||
ExtraActivityCount = _context.UserActivities
|
||||
.Where(x => x.UserId == p.PerformerId && x.DoesCode != id)
|
||||
.Where(x => x.UserId == ua.UserId && x.DoesCode != activityCode)
|
||||
.Count()
|
||||
})
|
||||
.Distinct()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return Ok(performers);
|
||||
}
|
||||
|
||||
// GET: api/ActivityApi/5
|
||||
|
|
@ -257,6 +283,7 @@ namespace Yavsc.Controllers
|
|||
.ToList(),
|
||||
Children = (activity.Children ?? Enumerable.Empty<Activity>())
|
||||
.Where(c => !c.Hidden)
|
||||
.Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
|
||||
.OrderByDescending(c => c.Rate)
|
||||
.Select(c => new ActivityBrowseItemDto
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Yavsc.Models;
|
||||
using Yavsc.Models.Billing;
|
||||
using Yavsc.Server.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace Yavsc.Controllers
|
||||
{
|
||||
|
|
@ -13,7 +15,7 @@ namespace Yavsc.Controllers
|
|||
|
||||
public SIRENExceptionsController(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: SIRENExceptions
|
||||
|
|
@ -50,15 +52,41 @@ namespace Yavsc.Controllers
|
|||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Create(ExceptionSIREN exceptionSIREN)
|
||||
{
|
||||
exceptionSIREN ??= new ExceptionSIREN();
|
||||
exceptionSIREN.SIREN = NormalizeSiren(exceptionSIREN.SIREN);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(exceptionSIREN.SIREN) || exceptionSIREN.SIREN.Length != 9 || !exceptionSIREN.SIREN.All(char.IsDigit))
|
||||
{
|
||||
ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Le SIREN doit contenir exactement 9 chiffres.");
|
||||
}
|
||||
|
||||
if (_context.ExceptionsSIREN.Any(e => e.SIREN == exceptionSIREN.SIREN))
|
||||
{
|
||||
ModelState.AddModelError(nameof(ExceptionSIREN.SIREN), "Ce SIREN est deja dans la liste des exceptions.");
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.ExceptionsSIREN.Add(exceptionSIREN);
|
||||
_context.SaveChanges(User.GetUserId());
|
||||
return RedirectToAction("Index");
|
||||
try
|
||||
{
|
||||
_context.SaveChanges(User.GetUserId());
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Impossible d'enregistrer cette exception SIREN.");
|
||||
}
|
||||
}
|
||||
return View(exceptionSIREN);
|
||||
}
|
||||
|
||||
private static string NormalizeSiren(string? siren)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(siren)) return string.Empty;
|
||||
return new string(siren.Where(char.IsDigit).ToArray());
|
||||
}
|
||||
|
||||
// GET: SIRENExceptions/Edit/5
|
||||
public IActionResult Edit(string id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -63,9 +63,35 @@ namespace Yavsc.Controllers
|
|||
.Where(a => a.ParentCode == id)
|
||||
.OrderByDescending(a => a.Rate).ToList();
|
||||
|
||||
var candidateCodes = toShow
|
||||
.Select(a => a.Code)
|
||||
.Concat(toShow.SelectMany(a => (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
||||
.Where(c => !c.Hidden)
|
||||
.Select(c => c.Code)))
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
var performerCounts = _dbContext.UserActivities
|
||||
.Where(ua => candidateCodes.Contains(ua.DoesCode))
|
||||
.GroupBy(ua => ua.DoesCode)
|
||||
.Select(g => new { Code = g.Key, Count = g.Select(x => x.UserId).Distinct().Count() })
|
||||
.ToDictionary(x => x.Code, x => x.Count);
|
||||
|
||||
toShow = toShow
|
||||
.Where(a =>
|
||||
(performerCounts.TryGetValue(a.Code, out var ownCount) && ownCount > 0)
|
||||
|| (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
||||
.Where(c => !c.Hidden)
|
||||
.Any(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0))
|
||||
.ToList();
|
||||
|
||||
foreach (var a in toShow)
|
||||
{
|
||||
a.Children = a.Children.Where(c => !c.Hidden).ToList();
|
||||
a.Children = (a.Children ?? new List<Yavsc.Models.Workflow.Activity>())
|
||||
.Where(c => !c.Hidden)
|
||||
.Where(c => performerCounts.TryGetValue(c.Code, out var childCount) && childCount > 0)
|
||||
.ToList();
|
||||
}
|
||||
return View(toShow);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
<label asp-for="Active" class="col-md-2 control-label">ActivateMyProSettings</label>
|
||||
<div>
|
||||
<input asp-for="Active" class="form-control" />
|
||||
<input class="form-check-input" type="checkbox" asp-for="Active" class="form-control" />
|
||||
|
||||
<span asp-validation-for="Active" class="text-danger"></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,21 +5,28 @@
|
|||
}
|
||||
|
||||
<form asp-action="Create" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="form-horizontal">
|
||||
<h4>Exception SIREN</h4>
|
||||
<hr />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label asp-for="SIREN" class="control-label">SIREN</label>
|
||||
<input asp-for="SIREN" class="form-control" maxlength="9" pattern="[0-9]{9}" inputmode="numeric" autocomplete="off" />
|
||||
<span asp-validation-for="SIREN" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
@Html.TextBox("SIREN")
|
||||
<div class="col-md-offset-1 col-md-10">
|
||||
<div class="col-md-offset-2 col-md-10">
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" value="Create" class="btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<a asp-action="Index">Back to List</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
||||
}
|
||||
|
|
|
|||
15
yavsc.sln
15
yavsc.sln
|
|
@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Client", "src\Yav
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt\PostIt.Tests\PostIt.Tests.csproj", "{021E6F5A-B81D-42A7-9918-DFE39B424FC2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Test", "src\Yavsc.Api.Test\Yavsc.Api.Test.csproj", "{49A42F0E-6E18-4617-9716-3E728966D96F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -229,6 +231,18 @@ Global
|
|||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -250,5 +264,6 @@ Global
|
|||
{34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||
{59AF5DEA-D349-495A-BC44-FC7BD4E55099} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2}
|
||||
{021E6F5A-B81D-42A7-9918-DFE39B424FC2} = {E13D107F-4053-D0DE-6394-453609595BFE}
|
||||
{49A42F0E-6E18-4617-9716-3E728966D96F} = {76A7FBA6-B7EC-4864-85C0-1F470794BA0F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue