From 333b066e662ee5f11b58e3b8bbca48a0e285c2c5 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 5 Jul 2026 23:56:10 +0100 Subject: [PATCH] Identity reloaded --- Directory.Packages.props | 10 +-- Dockerfile.backend | 2 +- Makefile | 6 +- src/PostIt.Tests/FakeAuthorizingBrowser.cs | 2 +- src/PostIt.Tests/LoginPageViewModelTests.cs | 6 +- src/PostIt.Tests/OidcStubAuthority.cs | 8 +-- src/PostIt.Tests/YavscApiClientTests.cs | 71 ++++++++++--------- src/PostIt/PostIt/Services/OidcLoginPhase.cs | 2 +- src/PostIt/PostIt/Services/YavscApiClient.cs | 28 ++++---- .../PostIt/ViewModels/LoginPageViewModel.cs | 22 +++--- .../Business/BookQueryApiController.cs | 6 +- .../Controllers/HairCut/HairCutController.cs | 4 +- .../NonRegression/BillingServiceTests.cs | 2 +- .../Contracting/GeneralSettingsController.cs | 14 ++-- .../Haircut/HairCutCommandController.cs | 12 ++-- .../Controllers/IT/ProjectController.cs | 4 +- src/Yavsc.Org/Directory.Packages.props | 12 ++-- src/Yavsc.Org/Helpers/EventHelpers.cs | 12 ++-- .../HairCutCommand/CommandConfirmation.cshtml | 10 +-- src/Yavsc.Org/Views/Project/Create.cshtml | 12 ++-- src/Yavsc.Org/Views/Project/Delete.cshtml | 4 +- src/Yavsc.Org/Views/Project/Details.cshtml | 4 +- src/Yavsc.Org/Views/Project/Edit.cshtml | 6 +- src/Yavsc.Org/Views/Project/Index.cshtml | 4 +- .../Components/PayPalButton/Default.cshtml | 14 ++-- .../DisplayTemplates/HairCutQuery.cshtml | 4 +- src/Yavsc.Server/Helpers/WorkflowHelpers.cs | 18 +++-- .../Models/ApplicationDbContext.cs | 12 +--- .../Billing/NominativeServiceCommand.cs | 12 ++-- .../Models/HairCut/HairCutQuery.cs | 2 +- .../Models/HairCut/HairMultiCutQuery.cs | 21 ++---- .../HairCut/HairPrestationCollectionItem.cs | 19 +++++ src/Yavsc.Server/Services/BillingService.cs | 10 ++- 33 files changed, 195 insertions(+), 180 deletions(-) create mode 100644 src/Yavsc.Server/Models/HairCut/HairPrestationCollectionItem.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index b1ed6926..31472d7f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,9 +13,9 @@ --> - - - + + + @@ -24,10 +24,10 @@ - + - \ No newline at end of file + diff --git a/Dockerfile.backend b/Dockerfile.backend index 86df9ccd..76ad9ea0 100644 --- a/Dockerfile.backend +++ b/Dockerfile.backend @@ -26,7 +26,7 @@ COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/ COPY . . # 3. Restauration des dépendances avec vos workloads actifs -RUN dotnet nuget add source https://isn.pschneider.fr/v3/index.json --allow-insecure-connections +RUN dotnet nuget add source https://isn.pschneider.fr/api/v3/index.json # 4. Restauration des dépendances pour tous les projets RUN dotnet restore diff --git a/Makefile b/Makefile index a4922a3d..2683c542 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,8 @@ include .env all: dotnet build --nologo -clean: - dotnet clean +clean: + dotnet clean -c $(CONFIG) src/Yavsc/bin/output/wwwroot: dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish @@ -31,7 +31,7 @@ src/Yavsc.Server/bin/$(CONFIG)/$(FRAMEWORK)/Yavsc.Server.dll: src/Yavsc/bin/$(CONFIG)/$(FRAMEWORK)/Yavsc.dll: dotnet build -p:Configuration=$(CONFIG) --project src/Yavsc.Org/Yavsc.Org.csproj -$(DESTDIR): +$(DESTDIR): mkdir $(DESTDIR) install: $(DESTDIR) diff --git a/src/PostIt.Tests/FakeAuthorizingBrowser.cs b/src/PostIt.Tests/FakeAuthorizingBrowser.cs index 10311500..4748425a 100644 --- a/src/PostIt.Tests/FakeAuthorizingBrowser.cs +++ b/src/PostIt.Tests/FakeAuthorizingBrowser.cs @@ -10,7 +10,7 @@ namespace PostIt.Tests; /// URL emitted by OidcClient, extracts its state, and returns a /// BrowserResult that mimics the OIDC redirect-with-code callback. /// -/// The paired 's token endpoint accepts +/// The paired 's token endpoint accepts /// any authorization code, so we don't need to mint a real one here. /// public sealed class FakeAuthorizingBrowser diff --git a/src/PostIt.Tests/LoginPageViewModelTests.cs b/src/PostIt.Tests/LoginPageViewModelTests.cs index e469c011..7a8b579f 100644 --- a/src/PostIt.Tests/LoginPageViewModelTests.cs +++ b/src/PostIt.Tests/LoginPageViewModelTests.cs @@ -14,7 +14,7 @@ public class LoginPageViewModelTests // short-circuits the system browser. The authority signs its // access_token with RS256; the fake browser captures the redirect // URI so the authority can complete the token exchange. - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var settings = new PostIt.Settings @@ -96,7 +96,7 @@ public class LoginPageViewModelTests // double slash before /.well-known/openid-configuration. The // stub advertises itself without the trailing slash; OidcClient // must bridge. - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var settings = new PostIt.Settings @@ -254,4 +254,4 @@ public class LoginPageViewModelTests "https://yavsc.example.com/.well-known/openid-configuration", vm.StatusMessage); } -} \ No newline at end of file +} diff --git a/src/PostIt.Tests/OidcStubAuthority.cs b/src/PostIt.Tests/OidcStubAuthority.cs index 552db6b0..3c6552fb 100644 --- a/src/PostIt.Tests/OidcStubAuthority.cs +++ b/src/PostIt.Tests/OidcStubAuthority.cs @@ -20,7 +20,7 @@ namespace PostIt.Tests; /// the browser intercepts the authorize redirect, the server completes /// the token exchange. /// -public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable +public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable { private readonly HttpListener _listener; private readonly RSA _rsa; @@ -30,7 +30,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable public string Issuer { get; } public string LoopbackRedirectUri { get; } - private OidcStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback) + private OIDCStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback) { _listener = listener; _rsa = rsa; @@ -39,7 +39,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable LoopbackRedirectUri = loopback; } - public static async Task StartAsync() + public static async Task StartAsync() { // Pick a free loopback port. var port = GetFreePort(); @@ -53,7 +53,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable var rsa = RSA.Create(2048); var kid = "test-key-1"; - var authority = new OidcStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback); + var authority = new OIDCStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback); _ = Task.Run(() => authority.AcceptLoopAsync(authority._cts.Token)); return authority; } diff --git a/src/PostIt.Tests/YavscApiClientTests.cs b/src/PostIt.Tests/YavscApiClientTests.cs index 1617c1b6..03e2fa3f 100644 --- a/src/PostIt.Tests/YavscApiClientTests.cs +++ b/src/PostIt.Tests/YavscApiClientTests.cs @@ -20,7 +20,7 @@ namespace PostIt.Tests; /// End-to-end coverage of : silent /// refresh on a near-expiry access token, 401-driven refresh + retry, /// and persistence of the token bundle via . -/// Uses the project's for the IdP and +/// Uses the project's for the IdP and /// a tiny in-process HTTP listener for the API server side. /// public class YavscApiClientTests @@ -45,7 +45,7 @@ public class YavscApiClientTests // in-memory access token as expired and re-run a call. The // refresh path must rotate the refresh token transparently // and the API call must succeed with the new token. - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -63,7 +63,7 @@ public class YavscApiClientTests var reloaded = new YavscApiClient(settings, new TokenStore(tokensPath)); var posts = await reloaded.CallAsync>( - HttpMethod.Get, "posts"); + HttpMethod.Get, "posts", TestContext.Current.CancellationToken); Assert.NotNull(posts); Assert.NotEmpty(posts); @@ -85,7 +85,7 @@ public class YavscApiClientTests { // API server returns 401 on the first request, 200 on the next. // YavscApiClient must refresh, then retry exactly once. - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(forceFirstRequest: true); await apiServer.StartAsync(); @@ -97,7 +97,7 @@ public class YavscApiClientTests settings, authority, tokensPath); var posts = await client.CallAsync>( - HttpMethod.Get, "posts"); + HttpMethod.Get, "posts", TestContext.Current.CancellationToken); Assert.NotEmpty(posts); Assert.Equal(2, apiServer.RequestCount); @@ -125,14 +125,15 @@ public class YavscApiClientTests var client = new YavscApiClient(settings, new TokenStore(Path.Combine( Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json"))); - await Assert.ThrowsAsync(() => - client.CallAsync(HttpMethod.Get, "posts")); + await Assert.ThrowsAsync( + () => + client.CallAsync(HttpMethod.Get, "posts", TestContext.Current.CancellationToken)); } [Fact] public async Task HasValidSession_is_true_after_login() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -154,7 +155,7 @@ public class YavscApiClientTests // --- helpers -------------------------------------------------------- - private static PostIt.Settings BuildSettings(OidcStubAuthority authority, string apiBaseUrl) => new() + private static PostIt.Settings BuildSettings(OIDCStubAuthority authority, string apiBaseUrl) => new() { Authentication = new AuthenticationSettings { @@ -167,7 +168,7 @@ public class YavscApiClientTests }; private static async Task LoginAndPersistAsync( - PostIt.Settings settings, OidcStubAuthority authority, string tokensPath) + PostIt.Settings settings, OIDCStubAuthority authority, string tokensPath) { var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); var client = new YavscApiClient(settings, new TokenStore(tokensPath)); @@ -181,7 +182,7 @@ public class YavscApiClientTests /// /// YavscApiClient.LoginInteractiveAsync delegates to /// Platform.CreateBrowser. We can't override that static cleanly - /// from xunit.v3, so we rebuild the call by re-routing the + /// from XUnit.v3, so we rebuild the call by re-routing the /// Platform.CreateBrowser delegate for the duration of the call. /// private static async Task LoginWithBrowserAsync( @@ -214,7 +215,7 @@ public class YavscApiClientTests File.WriteAllText(tokensPath, JsonSerializer.Serialize(record)); } - // --- OidcLoginPhase progress tests --------------------------------- + // --- OIDCLoginPhase progress tests --------------------------------- /// /// Collecting Progress is documented to capture reports @@ -225,7 +226,7 @@ public class YavscApiClientTests [Fact] public async Task LoginInteractiveAsync_reports_Discovering_then_Success() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -234,18 +235,18 @@ public class YavscApiClientTests var client = new YavscApiClient(settings, new TokenStore(tokensPath)); var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); try { await LoginWithBrowserAsync(client, browser.CreateBrowser(), progress); // SyncProgress captures reports synchronously — no flush needed. - Assert.Contains(OidcLoginPhase.Discovering, reported); - Assert.Contains(OidcLoginPhase.OpeningBrowser, reported); - Assert.Contains(OidcLoginPhase.ExchangingCode, reported); - Assert.Equal(OidcLoginPhase.Success, Last(reported)); + Assert.Contains(OIDCLoginPhase.Discovering, reported); + Assert.Contains(OIDCLoginPhase.OpeningBrowser, reported); + Assert.Contains(OIDCLoginPhase.ExchangingCode, reported); + Assert.Equal(OIDCLoginPhase.Success, Last(reported)); } finally { @@ -256,24 +257,24 @@ public class YavscApiClientTests [Fact] public async Task LoginInteractiveAsync_reports_Error_when_browser_missing() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); var settings = BuildSettings(authority, apiServer.BaseUrl); var client = new YavscApiClient(settings, new TokenStore(TokensPath())); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); var original = Platform.CreateBrowser; try { Platform.CreateBrowser = () => null; // simulate no browser wired up await Assert.ThrowsAsync( - () => client.LoginInteractiveAsync(progress)); + () => client.LoginInteractiveAsync(progress, TestContext.Current.CancellationToken)); // SyncProgress captures reports synchronously — no flush needed. - Assert.Equal(OidcLoginPhase.Error, Last(reported)); + Assert.Equal(OIDCLoginPhase.Error, Last(reported)); } finally { @@ -284,7 +285,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_false_when_no_bundle_on_disk() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -293,7 +294,7 @@ public class YavscApiClientTests // Tokens file deliberately doesn't exist. var client = new YavscApiClient(settings, new TokenStore(tokensPath)); - var ok = await client.TrySilentLoginAsync(); + var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken); Assert.False(ok); Assert.False(client.HasValidSession); } @@ -301,7 +302,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_true_when_access_token_still_valid() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -314,7 +315,7 @@ public class YavscApiClientTests { await LoginWithBrowserAsync(client, browser.CreateBrowser()); // Login fresh → access token is far from expiry. - var ok = await client.TrySilentLoginAsync(); + var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken); Assert.True(ok); Assert.True(client.HasValidSession); } @@ -327,7 +328,7 @@ public class YavscApiClientTests [Fact] public async Task TrySilentLoginAsync_returns_true_when_refresh_succeeds() { - using var authority = await OidcStubAuthority.StartAsync(); + using var authority = await OIDCStubAuthority.StartAsync(); using var apiServer = new StubApiServer(); await apiServer.StartAsync(); @@ -351,13 +352,13 @@ public class YavscApiClientTests // matches the disk: access expired, refresh still good. var client = new YavscApiClient(settings, store); - var reported = new System.Collections.Generic.List(); - var progress = new SyncProgress(reported); + var reported = new System.Collections.Generic.List(); + var progress = new SyncProgress(reported); - var ok = await client.TrySilentLoginAsync(progress); + var ok = await client.TrySilentLoginAsync(progress, TestContext.Current.CancellationToken); Assert.True(ok, "silent refresh should succeed via the stub authority."); - Assert.Contains(OidcLoginPhase.ExchangingCode, reported); - Assert.Equal(OidcLoginPhase.Success, Last(reported)); + Assert.Contains(OIDCLoginPhase.ExchangingCode, reported); + Assert.Equal(OIDCLoginPhase.Success, Last(reported)); } finally { @@ -430,7 +431,7 @@ public class YavscApiClientTests /// overload stays for tests that don't care about phase events. /// private static async Task LoginWithBrowserAsync( - YavscApiClient client, IBrowser browser, IProgress? progress = null) + YavscApiClient client, IBrowser browser, IProgress? progress = null) { var original = Platform.CreateBrowser; try diff --git a/src/PostIt/PostIt/Services/OidcLoginPhase.cs b/src/PostIt/PostIt/Services/OidcLoginPhase.cs index 0add3d76..c4787489 100644 --- a/src/PostIt/PostIt/Services/OidcLoginPhase.cs +++ b/src/PostIt/PostIt/Services/OidcLoginPhase.cs @@ -12,7 +12,7 @@ namespace PostIt.Services; /// The set is deliberately small: each value is a milestone an /// operator can grep for in logs / StatusMessage, not a heartbeat. /// -public enum OidcLoginPhase +public enum OIDCLoginPhase { /// No login in flight (or login has settled). Idle, diff --git a/src/PostIt/PostIt/Services/YavscApiClient.cs b/src/PostIt/PostIt/Services/YavscApiClient.cs index 6710ebb8..201a36fd 100644 --- a/src/PostIt/PostIt/Services/YavscApiClient.cs +++ b/src/PostIt/PostIt/Services/YavscApiClient.cs @@ -91,15 +91,15 @@ public class YavscApiClient : IAsyncDisposable /// for the human /// text (URLs, error detail). public async Task LoginInteractiveAsync( - IProgress? progress = null, + IProgress? progress = null, CancellationToken ct = default) { - progress?.Report(OidcLoginPhase.Discovering); + progress?.Report(OIDCLoginPhase.Discovering); var browser = Platform.CreateBrowser?.Invoke(); if (browser is null) { - progress?.Report(OidcLoginPhase.Error); + progress?.Report(OIDCLoginPhase.Error); throw new InvalidOperationException("No browser is available on this platform."); } @@ -114,20 +114,20 @@ public class YavscApiClient : IAsyncDisposable // the moment we ask the browser to open (covers the entire // user-driven window including the AwaitingCallback wait), and // the moment we trade the code for tokens. - progress?.Report(OidcLoginPhase.OpeningBrowser); + progress?.Report(OIDCLoginPhase.OpeningBrowser); var result = await client.LoginAsync(new LoginRequest(), ct).ConfigureAwait(false); if (result.IsError) { - progress?.Report(OidcLoginPhase.Error); + progress?.Report(OIDCLoginPhase.Error); throw new InvalidOperationException($"OIDC login failed: {result.Error}"); } - progress?.Report(OidcLoginPhase.ExchangingCode); + progress?.Report(OIDCLoginPhase.ExchangingCode); if (string.IsNullOrEmpty(result.RefreshToken)) { - progress?.Report(OidcLoginPhase.Error); + progress?.Report(OIDCLoginPhase.Error); throw new InvalidOperationException( "Missing refresh_token — vérifie le scope 'offline_access'."); } @@ -139,7 +139,7 @@ public class YavscApiClient : IAsyncDisposable IdToken: result.IdentityToken); _store.Save(_tokens); - progress?.Report(OidcLoginPhase.Success); + progress?.Report(OIDCLoginPhase.Success); } /// @@ -152,7 +152,7 @@ public class YavscApiClient : IAsyncDisposable /// phase and returns false so the UI can keep going. /// public async Task TrySilentLoginAsync( - IProgress? progress = null, + IProgress? progress = null, CancellationToken ct = default) { if (!HasValidSession) return false; @@ -161,7 +161,7 @@ public class YavscApiClient : IAsyncDisposable // Access token still has plenty of life — nothing to do. if (_tokens.AccessTokenExpiresAt - DateTimeOffset.UtcNow > RefreshSkew) { - progress?.Report(OidcLoginPhase.Success); + progress?.Report(OIDCLoginPhase.Success); return true; } @@ -172,19 +172,19 @@ public class YavscApiClient : IAsyncDisposable // the user back to the login page. try { - progress?.Report(OidcLoginPhase.ExchangingCode); + progress?.Report(OIDCLoginPhase.ExchangingCode); await ForceRefreshAsync(ct).ConfigureAwait(false); - progress?.Report(OidcLoginPhase.Success); + progress?.Report(OIDCLoginPhase.Success); return true; } catch (RefreshFailedException) { - progress?.Report(OidcLoginPhase.Idle); + progress?.Report(OIDCLoginPhase.Idle); return false; } catch { - progress?.Report(OidcLoginPhase.Idle); + progress?.Report(OIDCLoginPhase.Idle); return false; } } diff --git a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs index 009d67f2..a6ed595d 100644 --- a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs @@ -105,8 +105,8 @@ public partial class LoginPageViewModel : ViewModelBase /// callback hand-off: when AwaitingCallback never resolves, /// the OS never re-launched PostIt with the postit:// URL. /// - private OidcLoginPhase _phase = OidcLoginPhase.Idle; - public OidcLoginPhase Phase + private OIDCLoginPhase _phase = OIDCLoginPhase.Idle; + public OIDCLoginPhase Phase { get => _phase; private set @@ -122,13 +122,13 @@ public partial class LoginPageViewModel : ViewModelBase /// public string PhaseLabel => _phase switch { - OidcLoginPhase.Idle => "En attente", - OidcLoginPhase.Discovering => "Découverte OIDC…", - OidcLoginPhase.OpeningBrowser => "Ouverture du navigateur…", - OidcLoginPhase.AwaitingCallback => "En attente du callback postit://…", - OidcLoginPhase.ExchangingCode => "Échange du code contre les jetons…", - OidcLoginPhase.Success => "Connecté", - OidcLoginPhase.Error => "Erreur", + OIDCLoginPhase.Idle => "En attente", + OIDCLoginPhase.Discovering => "Découverte OIDC…", + OIDCLoginPhase.OpeningBrowser => "Ouverture du navigateur…", + OIDCLoginPhase.AwaitingCallback => "En attente du callback postit://…", + OIDCLoginPhase.ExchangingCode => "Échange du code contre les jetons…", + OIDCLoginPhase.Success => "Connecté", + OIDCLoginPhase.Error => "Erreur", _ => _phase.ToString(), }; @@ -275,7 +275,7 @@ public partial class LoginPageViewModel : ViewModelBase // The progress sink drives Phase / PhaseLabel; StatusMessage // keeps the text detail (URLs, error messages). Same // underlying flow, two views. - var progress = new Progress(p => Phase = p); + var progress = new Progress(p => Phase = p); await LoginInteractiveCoreAsync(_api, progress); IsBusy = false; @@ -299,7 +299,7 @@ public partial class LoginPageViewModel : ViewModelBase /// private async Task LoginInteractiveCoreAsync( YavscApiClient api, - IProgress? progress = null) + IProgress? progress = null) { var original = Platform.CreateBrowser; try diff --git a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs index ac1630e8..24fa3c28 100644 --- a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs @@ -41,7 +41,7 @@ namespace Yavsc.Controllers { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); var now = DateTime.Now; - + var result = _context.RdvQueries.Include(c => c.Location). Include(c => c.Client).Where(c => c.PerformerId == uid && c.Id < maxId && c.EventDate > now && c.ValidationDate == null). @@ -49,12 +49,12 @@ namespace Yavsc.Controllers { Client = new ClientProviderInfo { UserName = c.Client.UserName, - UserId = c.ClientId, + UserId = c.ClientId, Avatar = c.Client.Avatar }, Location = c.Location, EventDate = c.EventDate, Id = c.Id, - Previsional = c.Previsional, + Previsional = c.Provisional, Reason = c.Reason, ActivityCode = c.ActivityCode, BillingCode = BillingCodes.Rdv diff --git a/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs b/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs index 12a93758..927c0acc 100644 --- a/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs +++ b/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs @@ -41,7 +41,7 @@ namespace Yavsc.ApiControllers // user, as a client public IActionResult Index() { - + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); var now = DateTime.Now; @@ -151,7 +151,7 @@ namespace Yavsc.ApiControllers { HairCutQuery query = await _context.HairCutQueries.Include(q => q.Client). - Include(q => q.Client.PostalAddress).Include(q => q.Prestation).Include(q=>q.Regularisation) + Include(q => q.Client.PostalAddress).Include(q => q.Prestation).Include(q=>q.Regularization) .SingleAsync(q => q.Id == id); if (query.PaymentId!=null) return new BadRequestObjectResult(new { error = "An existing payment process already exists" }); diff --git a/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs index 8d15d26c..bf40438a 100644 --- a/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs @@ -37,7 +37,7 @@ namespace Yavsc WorkflowHelpers.ConfigureBillingService(); var firstRegistrar = new Func((db, id) => - db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularisation).Single(q => q.Id == id)); + db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularization).Single(q => q.Id == id)); const string testCode = "Brush"; diff --git a/src/Yavsc.Org/Controllers/Contracting/GeneralSettingsController.cs b/src/Yavsc.Org/Controllers/Contracting/GeneralSettingsController.cs index 1678f2a7..5357a59e 100644 --- a/src/Yavsc.Org/Controllers/Contracting/GeneralSettingsController.cs +++ b/src/Yavsc.Org/Controllers/Contracting/GeneralSettingsController.cs @@ -17,7 +17,7 @@ namespace Yavsc.Controllers // GET: GeneralSettings public async Task Index() { - return View(await _context.GeneralSettings.ToListAsync()); + return View(await _context.MusicLoverSettings.ToListAsync()); } // GET: GeneralSettings/Details/5 @@ -28,7 +28,7 @@ namespace Yavsc.Controllers return NotFound(); } - MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id); + MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id); if (generalSettings == null) { return NotFound(); @@ -50,7 +50,7 @@ namespace Yavsc.Controllers { if (ModelState.IsValid) { - _context.GeneralSettings.Add(generalSettings); + _context.MusicLoverSettings.Add(generalSettings); await _context.SaveChangesAsync(); return RedirectToAction("Index"); } @@ -65,7 +65,7 @@ namespace Yavsc.Controllers return NotFound(); } - MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id); + MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id); if (generalSettings == null) { return NotFound(); @@ -96,7 +96,7 @@ namespace Yavsc.Controllers return NotFound(); } - MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id); + MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id); if (generalSettings == null) { return NotFound(); @@ -110,8 +110,8 @@ namespace Yavsc.Controllers [ValidateAntiForgeryToken] public async Task DeleteConfirmed(string id) { - MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id); - _context.GeneralSettings.Remove(generalSettings); + MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id); + _context.MusicLoverSettings.Remove(generalSettings); await _context.SaveChangesAsync(); return RedirectToAction("Index"); } diff --git a/src/Yavsc.Org/Controllers/Haircut/HairCutCommandController.cs b/src/Yavsc.Org/Controllers/Haircut/HairCutCommandController.cs index 0a2610a8..80e5f80c 100644 --- a/src/Yavsc.Org/Controllers/Haircut/HairCutCommandController.cs +++ b/src/Yavsc.Org/Controllers/Haircut/HairCutCommandController.cs @@ -49,7 +49,7 @@ namespace Yavsc.Controllers this.haircutLocalizer = haircutLocalizer; } - + private async Task GetQuery(long id) { var query = await _context.HairCutQueries @@ -58,7 +58,7 @@ namespace Yavsc.Controllers .Include(x => x.Prestation) .Include(x => x.PerformerProfile.Performer) .Include(x => x.PerformerProfile.Performer.DeviceDeclaration) - .Include(x => x.Regularisation) + .Include(x => x.Regularization) .SingleAsync(m => m.Id == id); query.SelectedProfile = await _context.BrusherProfile.SingleAsync(b => b.UserId == query.PerformerId); return query; @@ -82,11 +82,11 @@ namespace Yavsc.Controllers } var paymentInfo = await _context.ConfirmPayment(User.GetUserId(), PayerID, token); ViewBag.paymentinfo = paymentInfo; - command.Regularisation = paymentInfo.DbContent; + command.Regularization = paymentInfo.DbContent; command.PaymentId = token; bool paymentOk = false; if (paymentInfo.DetailsFromPayPal != null) - if (paymentInfo.DetailsFromPayPal.Ack == AckCodeType.SUCCESS) + if (paymentInfo.DetailsFromPayPal.Ack == AckCodeType.SUCCESS) { // FIXME Assert (command.ValidationDate == null) if (command.ValidationDate == null) { @@ -174,7 +174,7 @@ namespace Yavsc.Controllers .Include(x => x.PerformerProfile) .Include(x => x.Prestation) .Include(x => x.PerformerProfile.Performer) - .Include(x => x.Regularisation) + .Include(x => x.Regularization) .SingleOrDefaultAsync(m => m.Id == id); if (command == null) { @@ -224,7 +224,7 @@ namespace Yavsc.Controllers .FirstOrDefault( x => x.PerformerId == model.PerformerId ); - + if (taintIds != null) { diff --git a/src/Yavsc.Org/Controllers/IT/ProjectController.cs b/src/Yavsc.Org/Controllers/IT/ProjectController.cs index 83ccc67a..965530f3 100644 --- a/src/Yavsc.Org/Controllers/IT/ProjectController.cs +++ b/src/Yavsc.Org/Controllers/IT/ProjectController.cs @@ -18,7 +18,7 @@ namespace Yavsc.Controllers private readonly ApplicationDbContext _context; readonly IStringLocalizer _localizer; readonly IStringLocalizer _bugLocalizer; - + public ProjectController(ApplicationDbContext context, IStringLocalizer localizer, IStringLocalizer bugLocalizer @@ -32,7 +32,7 @@ namespace Yavsc.Controllers // GET: Project public async Task Index() { - var applicationDbContext = _context.Project.Include(p => p.Client).Include(p => p.Context).Include(p => p.PerformerProfile).Include(p => p.Regularisation).Include(p => p.Repository); + var applicationDbContext = _context.Project.Include(p => p.Client).Include(p => p.Context).Include(p => p.PerformerProfile).Include(p => p.Regularization).Include(p => p.Repository); return View(await applicationDbContext.ToListAsync()); } diff --git a/src/Yavsc.Org/Directory.Packages.props b/src/Yavsc.Org/Directory.Packages.props index 6cd62331..fd16374b 100644 --- a/src/Yavsc.Org/Directory.Packages.props +++ b/src/Yavsc.Org/Directory.Packages.props @@ -4,14 +4,14 @@ - - + + - - - - + + + + diff --git a/src/Yavsc.Org/Helpers/EventHelpers.cs b/src/Yavsc.Org/Helpers/EventHelpers.cs index 48c23c1b..151f3360 100644 --- a/src/Yavsc.Org/Helpers/EventHelpers.cs +++ b/src/Yavsc.Org/Helpers/EventHelpers.cs @@ -17,11 +17,11 @@ namespace Yavsc.Helpers { Sender = query.ClientId, Reason = query.Reason, - Client = new ClientProviderInfo {  + Client = new ClientProviderInfo { UserName = query.Client.UserName , UserId = query.ClientId, Avatar = query.Client.Avatar } , - Previsional = query.Previsional, + Previsional = query.Provisional, EventDate = query.EventDate, Location = query.Location, Id = query.Id, @@ -44,7 +44,7 @@ namespace Yavsc.Helpers var yaev = query.CreateEvent("NewHairCutQuery", string.Format(SR["HairCutQueryValidation"],query.Client.UserName), $"{query.Client.Id}"); - + return yaev; } @@ -58,12 +58,12 @@ namespace Yavsc.Helpers var yaev = new HairCutQueryEvent("newCommand") { Sender = query.ClientId, - - Client = new ClientProviderInfo {  + + Client = new ClientProviderInfo { UserName = query.Client.UserName , UserId = query.ClientId, Avatar = query.Client.Avatar } , - Previsional = query.Previsional, + Previsional = query.Provisional, EventDate = query.EventDate, Location = query.Location, Id = query.Id, diff --git a/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml b/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml index ecdd8876..4031b7f0 100644 --- a/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml +++ b/src/Yavsc.Org/Views/HairCutCommand/CommandConfirmation.cshtml @@ -54,7 +54,7 @@
@Html.DisplayNameFor(m => m.Location)
@if (Model.Location == null) { -

Pas de lieu convenu ...

+

Pas de lieu convenu ...

} else { @Html.DisplayFor(m => m.Location) @@ -62,7 +62,7 @@
Notification
-
@if (ViewBag.GooglePayload !=null) +
@if (ViewBag.GooglePayload !=null) { @if (ViewBag.GooglePayload.success>0) {

GCM Notifications sent

@@ -85,10 +85,10 @@
@await Component.InvokeAsync("Bill", Model)
- -
@Html.DisplayNameFor(m=>m.Regularisation)
+ +
@Html.DisplayNameFor(m=>m.Regularization)
@await Component.InvokeAsync("PayPalButton", Model)
- + diff --git a/src/Yavsc.Org/Views/Project/Create.cshtml b/src/Yavsc.Org/Views/Project/Create.cshtml index f78c9d85..277fba56 100644 --- a/src/Yavsc.Org/Views/Project/Create.cshtml +++ b/src/Yavsc.Org/Views/Project/Create.cshtml @@ -21,9 +21,9 @@
- +
-
+
@@ -57,12 +57,12 @@
-
+
- +
- - + +
diff --git a/src/Yavsc.Org/Views/Project/Delete.cshtml b/src/Yavsc.Org/Views/Project/Delete.cshtml index f2f25646..6a9a3f20 100644 --- a/src/Yavsc.Org/Views/Project/Delete.cshtml +++ b/src/Yavsc.Org/Views/Project/Delete.cshtml @@ -48,10 +48,10 @@ @Html.DisplayFor(model => model.OwnerId)
- @Html.DisplayNameFor(model => model.Previsional) + @Html.DisplayNameFor(model => model.Provisional)
- @Html.DisplayFor(model => model.Previsional) + @Html.DisplayFor(model => model.Provisional)
@Html.DisplayNameFor(model => model.DateModified) diff --git a/src/Yavsc.Org/Views/Project/Details.cshtml b/src/Yavsc.Org/Views/Project/Details.cshtml index 48c97e8e..da6d6287 100644 --- a/src/Yavsc.Org/Views/Project/Details.cshtml +++ b/src/Yavsc.Org/Views/Project/Details.cshtml @@ -53,10 +53,10 @@ @Html.DisplayFor(model => model.OwnerId)
- @Html.DisplayNameFor(model => model.Previsional) + @Html.DisplayNameFor(model => model.Provisional)
- @Html.DisplayFor(model => model.Previsional) + @Html.DisplayFor(model => model.Provisional)
@Html.DisplayNameFor(model => model.DateModified) diff --git a/src/Yavsc.Org/Views/Project/Edit.cshtml b/src/Yavsc.Org/Views/Project/Edit.cshtml index 8c41c6e3..4cc6d3de 100644 --- a/src/Yavsc.Org/Views/Project/Edit.cshtml +++ b/src/Yavsc.Org/Views/Project/Edit.cshtml @@ -36,10 +36,10 @@
- +
- - + +
diff --git a/src/Yavsc.Org/Views/Project/Index.cshtml b/src/Yavsc.Org/Views/Project/Index.cshtml index 49f8fce6..e444a082 100644 --- a/src/Yavsc.Org/Views/Project/Index.cshtml +++ b/src/Yavsc.Org/Views/Project/Index.cshtml @@ -30,7 +30,7 @@ @Html.DisplayNameFor(model => model.OwnerId) - @Html.DisplayNameFor(model => model.Previsional) + @Html.DisplayNameFor(model => model.Provisional) @Html.DisplayNameFor(model => model.DateModified) @@ -74,7 +74,7 @@ @Html.DisplayFor(modelItem => item.OwnerId) - @Html.DisplayFor(modelItem => item.Previsional) + @Html.DisplayFor(modelItem => item.Provisional) @Html.DisplayFor(modelItem => item.DateModified) diff --git a/src/Yavsc.Org/Views/Shared/Components/PayPalButton/Default.cshtml b/src/Yavsc.Org/Views/Shared/Components/PayPalButton/Default.cshtml index 9fc4d80b..954b4eba 100644 --- a/src/Yavsc.Org/Views/Shared/Components/PayPalButton/Default.cshtml +++ b/src/Yavsc.Org/Views/Shared/Components/PayPalButton/Default.cshtml @@ -1,25 +1,25 @@ @model NominativeServiceCommand @if (Model!=null && Model.PaymentId!=null) { - - @if (Model.Regularisation.Executor.Id == User.GetUserId()) { + + @if (Model.Regularization.Executor.Id == User.GetUserId()) { Votre paiment } else { - Le paiment de @Html.DisplayFor(m=>m.Regularisation.Executor.UserName) + Le paiment de @Html.DisplayFor(m=>m.Regularization.Executor.UserName) } - : + : -} +} @if (!Model.GetIsAcquitted()) {
diff --git a/src/Yavsc.Org/Views/Shared/DisplayTemplates/HairCutQuery.cshtml b/src/Yavsc.Org/Views/Shared/DisplayTemplates/HairCutQuery.cshtml index 5af91dd1..6dfb1e54 100644 --- a/src/Yavsc.Org/Views/Shared/DisplayTemplates/HairCutQuery.cshtml +++ b/src/Yavsc.Org/Views/Shared/DisplayTemplates/HairCutQuery.cshtml @@ -24,8 +24,8 @@
@await Component.InvokeAsync("Bill", Model)
-
@Html.DisplayNameFor(m=>m.Regularisation)
-
+
@Html.DisplayNameFor(m=>m.Regularization)
+
@await Component.InvokeAsync("PayPalButton", Model)
diff --git a/src/Yavsc.Server/Helpers/WorkflowHelpers.cs b/src/Yavsc.Server/Helpers/WorkflowHelpers.cs index 1e435d3f..9caba856 100644 --- a/src/Yavsc.Server/Helpers/WorkflowHelpers.cs +++ b/src/Yavsc.Server/Helpers/WorkflowHelpers.cs @@ -76,15 +76,21 @@ namespace Yavsc.Helpers { Config.ProfileTypes.Add(c); } + if (c.IsClass && !c.IsAbstract && + c.GetInterface(nameof(IBillable)) != null) + { + BillingService.GlobalBillingMap.Add(c.Name, c.AssemblyQualifiedName); + } } } foreach (var propertyInfo in typeof(ApplicationDbContext).GetProperties()) { - foreach (var attr in propertyInfo.CustomAttributes) + if (propertyInfo.PropertyType.IsGenericType && + propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) { - // something like a DbSet? - if (typeof(Yavsc.Attributes.ActivitySettingsAttribute).IsAssignableFrom(attr.AttributeType)) + var entityType = propertyInfo.PropertyType.GetGenericArguments()[0]; + if (typeof(IUserSettings).IsAssignableFrom(entityType)) { BillingService.UserSettings.Add(propertyInfo); } @@ -94,16 +100,16 @@ namespace Yavsc.Helpers RegisterBilling(BillingCodes.Brush, new Func ((db, id) => { - var query = db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularisation).Single(q => q.Id == id); + var query = db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularization).Single(q => q.Id == id); query.SelectedProfile = db.BrusherProfile.Single(b => b.UserId == query.PerformerId); return query; })); RegisterBilling(BillingCodes.MBrush, new Func - ((db, id) => db.HairMultiCutQueries.Include(q => q.Regularisation).Single(q => q.Id == id))); + ((db, id) => db.HairMultiCutQueries.Include(q => q.Regularization).Single(q => q.Id == id))); RegisterBilling(BillingCodes.Rdv, new Func - ((db, id) => db.RdvQueries.Include(q => q.Regularisation).Single(q => q.Id == id))); + ((db, id) => db.RdvQueries.Include(q => q.Regularization).Single(q => q.Id == id))); } } diff --git a/src/Yavsc.Server/Models/ApplicationDbContext.cs b/src/Yavsc.Server/Models/ApplicationDbContext.cs index cbdb6895..94076715 100644 --- a/src/Yavsc.Server/Models/ApplicationDbContext.cs +++ b/src/Yavsc.Server/Models/ApplicationDbContext.cs @@ -290,22 +290,17 @@ namespace Yavsc.Models public DbSet Instrument { get; set; } - [ActivitySettings] public DbSet DjSettings { get; set; } - [ActivitySettings] public DbSet Instrumentation { get; set; } - [ActivitySettings] public DbSet FormationSettings { get; set; } - [ActivitySettings] - public DbSet GeneralSettings { get; set; } + public DbSet MusicLoverSettings { get; set; } public DbSet CoWorking { get; set; } - private void AddTimestamps(string userId) - { - var entities = + private void AddTimestamps(string userId)  + { var entities = ChangeTracker.Entries() .Where(x => x.Entity.GetType().GetInterface(nameof(ITrackedEntity)) != null && (x.State == EntityState.Added || x.State == EntityState.Modified)); @@ -360,7 +355,6 @@ namespace Yavsc.Models public DbSet DismissClicked { get; set; } - [ActivitySettings] public DbSet BrusherProfile { get; set; } public DbSet BankIdentity { get; set; } diff --git a/src/Yavsc.Server/Models/Billing/NominativeServiceCommand.cs b/src/Yavsc.Server/Models/Billing/NominativeServiceCommand.cs index 798313a8..b2af663b 100644 --- a/src/Yavsc.Server/Models/Billing/NominativeServiceCommand.cs +++ b/src/Yavsc.Server/Models/Billing/NominativeServiceCommand.cs @@ -59,14 +59,14 @@ namespace Yavsc.Models.Billing /// /// The performer identifier /// - [ForeignKey("PerformerId"),Display(Name="Préstataire")] + [ForeignKey("PerformerId"),Display(Name="PerformerProfile")] public PerformerProfile PerformerProfile { get; set; } public DateTime? ValidationDate {get; set;} - [Display(Name="Previsional")] - public decimal? Previsional { get; set; } + [Display(Name="Provisional")] + public decimal? Provisional { get; set; } /// /// The bill /// @@ -82,7 +82,7 @@ namespace Yavsc.Models.Billing public bool GetIsAcquitted() { - return Regularisation?.IsOk() ?? false; + return Regularization?.IsOk() ?? false; } public string GetFileBaseName(IBillingService billingService) @@ -93,11 +93,11 @@ namespace Yavsc.Models.Billing return $"facture-{bcode}-{Id}{ack}"; } - [ForeignKey("Regularisation")] + [ForeignKey("Regularization")] public string? PaymentId { get; set; } [Display(Name = "Acquittement de la facture")] - public virtual PayPalPayment Regularisation { get; set; } + public virtual PayPalPayment Regularization { get; set; } } } diff --git a/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs b/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs index c70afdf5..5aab5258 100644 --- a/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs +++ b/src/Yavsc.Server/Models/HairCut/HairCutQuery.cs @@ -400,7 +400,7 @@ Prestation.Gender == HairCutGenders.Women ? UserId = ClientId, Avatar = Client.Avatar }, - Previsional = Previsional, + Previsional = Provisional, EventDate = EventDate, Location = Location, Id = Id, diff --git a/src/Yavsc.Server/Models/HairCut/HairMultiCutQuery.cs b/src/Yavsc.Server/Models/HairCut/HairMultiCutQuery.cs index 24714ee4..b68ed38b 100644 --- a/src/Yavsc.Server/Models/HairCut/HairMultiCutQuery.cs +++ b/src/Yavsc.Server/Models/HairCut/HairMultiCutQuery.cs @@ -8,33 +8,20 @@ using Yavsc.Billing; namespace Yavsc.Models.Haircut { - public class HairPrestationCollectionItem { - - [Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public long Id { get; set; } - public long PrestationId { get; set; } - [ForeignKeyAttribute("PrestationId")] - public virtual HairPrestation Prestation { get; set; } - - public long QueryId { get; set; } - - [ForeignKeyAttribute("QueryId")] - public virtual HairMultiCutQuery Query { get; set; } - } public class HairMultiCutQuery : NominativeServiceCommand { // Bill description - string _customDescription = null; - public override string Description + string _customDescription = null; + public override string Description { - get { + get { return _customDescription ?? "Prestation en coiffure à domicile [commande groupée]" ; } set { _customDescription = value; } - } + } [Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)] override public long Id { get; set; } diff --git a/src/Yavsc.Server/Models/HairCut/HairPrestationCollectionItem.cs b/src/Yavsc.Server/Models/HairCut/HairPrestationCollectionItem.cs new file mode 100644 index 00000000..0ce57278 --- /dev/null +++ b/src/Yavsc.Server/Models/HairCut/HairPrestationCollectionItem.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Yavsc.Models.Haircut +{ + public class HairPrestationCollectionItem { + + [Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + public long PrestationId { get; set; } + [ForeignKeyAttribute("PrestationId")] + public virtual HairPrestation Prestation { get; set; } + + public long QueryId { get; set; } + + [ForeignKeyAttribute("QueryId")] + public virtual HairMultiCutQuery Query { get; set; } + } +} diff --git a/src/Yavsc.Server/Services/BillingService.cs b/src/Yavsc.Server/Services/BillingService.cs index 0738c57d..ad936176 100644 --- a/src/Yavsc.Server/Services/BillingService.cs +++ b/src/Yavsc.Server/Services/BillingService.cs @@ -12,6 +12,9 @@ namespace Yavsc.Services new Dictionary>(); public static List UserSettings = new List(); + /// + /// Mapping from activity codes to IUserSettings + /// public static Dictionary GlobalBillingMap = new Dictionary(); @@ -30,7 +33,12 @@ namespace Yavsc.Services return Task.FromResult(GetBillable(DbContext, billingCode, queryId)); } - public static IQuery GetBillable(ApplicationDbContext context, string billingCode, long queryId) => Billing[billingCode](context, queryId); + public static IQuery GetBillable(ApplicationDbContext context, string billingCode, long queryId) + { + throw new NotImplementedException(); + } + + public async Task GetPerformersSettingsAsync(string activityCode, string userId) { var activity = await DbContext.Activities.SingleAsync(a => a.Code == activityCode);