From 6dae9c4e94c3d070a227c71baf5c89fa7353711c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Tue, 7 Jul 2026 21:19:37 +0300 Subject: [PATCH] IsNotAllowed_BranchLimit --- .../PlatformSignInResultExtensions.cs | 4 ++ .../PlatformConsts.cs | 2 + .../Identity/PlatformSignInResult.cs | 3 + .../Identity/PlatformLoginResult.cs | 5 ++ .../Identity/PlatformLoginResultType.cs | 1 + .../Identity/PlatformSignInManager.cs | 60 ++++++++++++++++--- ui/src/components/orders/PaymentForm.tsx | 11 +++- 7 files changed, 78 insertions(+), 8 deletions(-) diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs index 8d60949..66c81ef 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformSignInResultExtensions.cs @@ -58,6 +58,10 @@ public static class PlatformSignInResultExtensions { return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_WorkHour; } + if (resultP.IsNotAllowed_BranchLimit) + { + return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_BranchLimit; + } if (resultP.IsNotAllowed_ConcurrentUserLimit) { return PlatformConsts.UserCannotSignInErrors.LoginNotAllowed_ConcurrentUserLimit; diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index a484fe1..9a82aa4 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -162,6 +162,7 @@ public static class PlatformConsts public const string TenantIsPassive = GroupName + ".TenantIsPassive"; public const string LoginNotAllowed_WorkHour = GroupName + ".LoginNotAllowed_WorkHour"; public const string ConcurrentUserLimitError = GroupName + ".ConcurrentUserLimitError"; + public const string BranchLimitError = GroupName + ".BranchLimitError"; public const string CaptchaWrongCode = GroupName + ".CaptchaWrongCode"; public const string TwoFactorWrongCode = GroupName + ".TwoFactorWrongCode"; public const string SignOut = GroupName + ".SignOut"; @@ -548,6 +549,7 @@ public static class PlatformConsts public static string LoginNotAllowed_TenantIsPassive { get; set; } = "UserCannotSignInTenantIsPassive"; public static string LoginNotAllowed_TenantNotFound { get; set; } = "UserCannotSignInTenantNotFound"; public static string LoginNotAllowed_WorkHour { get; set; } = "UserCannotSignInWorkHour"; + public static string LoginNotAllowed_BranchLimit { get; set; } = "UserCannotSignInBranchLimit"; public static string LoginNotAllowed_ConcurrentUserLimit { get; set; } = "UserCannotSignInConcurrentUserLimit"; } diff --git a/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs b/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs index 0801a36..7038f00 100644 --- a/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs +++ b/api/src/Sozsoft.Platform.Domain/Identity/PlatformSignInResult.cs @@ -30,6 +30,8 @@ public class PlatformSignInResult : SignInResult public bool IsNotAllowed_WorkHour { get; set; } + public bool IsNotAllowed_BranchLimit { get; set; } + public bool IsNotAllowed_ConcurrentUserLimit { get; set; } public override string ToString() @@ -42,6 +44,7 @@ public class PlatformSignInResult : SignInResult ShouldChangePasswordPeriodic ? "ShouldChangePasswordPeriodic" : IsNotAllowed_TenantIsPassive ? "NotAllowed_TenantIsPassive" : IsNotAllowed_WorkHour ? "NotAllowed_WorkHour" : + IsNotAllowed_BranchLimit ? "NotAllowed_BranchLimit" : IsNotAllowed_ConcurrentUserLimit ? "NotAllowed_ConcurrentUserLimit" : base.ToString(); } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs index d6f8aba..eed1e5f 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResult.cs @@ -64,6 +64,11 @@ public class PlatformLoginResult : AbpLoginResult PResult = PlatformLoginResultType.NotAllowedWorkHour; Description = L[PlatformConsts.AbpIdentity.User.LoginNotAllowed_WorkHour]; } + else if (resultP.IsNotAllowed_BranchLimit) + { + PResult = PlatformLoginResultType.BranchLimit; + Description = L[PlatformConsts.AbpIdentity.User.BranchLimitError]; + } else if (resultP.IsNotAllowed_ConcurrentUserLimit) { PResult = PlatformLoginResultType.ConcurrentUserLimit; diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs index 08ba3a7..4497e77 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformLoginResultType.cs @@ -16,6 +16,7 @@ public enum PlatformLoginResultType : byte ShowCaptcha, TenantIsPassive, NotAllowedWorkHour, + BranchLimit, ConcurrentUserLimit } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs index 0f460d1..c20dd93 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/Identity/PlatformSignInManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data.Common; using System.Linq; using System.Threading.Tasks; using Sozsoft.Platform.Entities; @@ -33,6 +32,8 @@ public interface IPlatformSignInManager public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager { private const string UserLicenseProductName = "Public.products.userLicense"; + private const string BranchLicenseProductName = "Public.products.branchHosting"; + private readonly IClock clock; private readonly IRepository repositoryIp; private readonly IRepository repositoryWorkHour; @@ -42,6 +43,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager private readonly ICurrentTenant currentTenant; private readonly IUnitOfWorkManager unitOfWorkManager; private readonly IRepository orderRepository; + private readonly IRepository branchRepository; public PlatformSignInManager( IdentityUserManager userManager, @@ -60,7 +62,8 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager IIdentitySessionRepository identitySessionRepository, ICurrentTenant currentTenant, IUnitOfWorkManager unitOfWorkManager, - IRepository orderRepository + IRepository orderRepository, + IRepository branchRepository ) : base( userManager, contextAccessor, @@ -81,6 +84,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager this.currentTenant = currentTenant; this.unitOfWorkManager = unitOfWorkManager; this.orderRepository = orderRepository; + this.branchRepository = branchRepository; } public async Task PreSignInCheckAsync(IdentityUser user) @@ -115,6 +119,10 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager { return new PlatformSignInResult() { IsNotAllowed_WorkHour = true }; } + if (!await CheckBranchLicenseLimitAsync(user)) + { + return new PlatformSignInResult() { IsNotAllowed_BranchLimit = true }; + } if (!await CheckConcurrentLimitAsync(user)) { return new PlatformSignInResult() { IsNotAllowed_ConcurrentUserLimit = true }; @@ -284,16 +292,14 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager /// public async Task CheckConcurrentLimitAsync(IdentityUser user) { + // Eğer tenantId yoksa, kullanıcı tenant'a bağlı değil demektir. + // Bu durumda concurrent limit kontrolü yapılmaz. if (!user.TenantId.HasValue) { return true; } var maxConcurrentUsers = await GetActiveUserLicenseQuantityAsync(user.TenantId.Value); - if (maxConcurrentUsers == 0) - { - return true; - } // Tenant bağlamını explicit olarak set et — hem password hem refresh token akışlarında // doğru tenant izolasyonu için güvenli bir şekilde değiştirilir. @@ -324,7 +330,47 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager return true; } + private async Task CheckBranchLicenseLimitAsync(IdentityUser user) + { + if (!user.TenantId.HasValue) + { + return true; + } + + var tenantId = user.TenantId.Value; + var activeBranchCount = await GetActiveBranchCountAsync(tenantId); + var branchLicenseQuantity = await GetActiveBranchLicenseQuantityAsync(tenantId); + + if (activeBranchCount > branchLicenseQuantity) + { + Logger.LogWarning(PlatformEventIds.UserCannotSignInConcurrentUserLimit, + "Tenant {TenantId} branch license quantity of {Limit} is lower than active branch count {ActiveCount}.", + tenantId, branchLicenseQuantity, activeBranchCount); + return false; + } + + return true; + } + + private async Task GetActiveBranchCountAsync(Guid tenantId) + { + using (currentTenant.Change(tenantId)) + { + return await branchRepository.CountAsync(branch => branch.TenantId == tenantId && branch.IsActive == true); + } + } + private async Task GetActiveUserLicenseQuantityAsync(Guid tenantId) + { + return await GetActiveOrderItemQuantityAsync(tenantId, UserLicenseProductName); + } + + private async Task GetActiveBranchLicenseQuantityAsync(Guid tenantId) + { + return await GetActiveOrderItemQuantityAsync(tenantId, BranchLicenseProductName); + } + + private async Task GetActiveOrderItemQuantityAsync(Guid tenantId, string productName) { using (currentTenant.Change(null)) { @@ -335,7 +381,7 @@ public class PlatformSignInManager : AbpSignInManager, IPlatformSignInManager return activeOrders .SelectMany(order => order.Items) - .Where(item => item.ProductName == UserLicenseProductName) + .Where(item => item.ProductName == productName) .Sum(item => item.Quantity); } } diff --git a/ui/src/components/orders/PaymentForm.tsx b/ui/src/components/orders/PaymentForm.tsx index f90b20c..ea050d2 100644 --- a/ui/src/components/orders/PaymentForm.tsx +++ b/ui/src/components/orders/PaymentForm.tsx @@ -362,7 +362,16 @@ export const PaymentForm: React.FC = ({ onBack, onComplete, ca className="h-10 w-10 shrink-0 rounded-md object-cover" /> )} -
{translate('::' + item.product.name)}
+
+
+ {translate('::' + item.product.name)} +
+ {item.product.isQuantityBased && ( +
+ x {item.quantity} +
+ )} +
{formatPrice(item.totalPrice)}