using CMSMicroservice.Application.ClubFeatureCQ.Queries.GetUserClubFeatures; using CMSMicroservice.Application.Common.Authorization; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Common; using CMSMicroservice.Domain.Entities; using CMSMicroservice.Protobuf.Protos.Configuration; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace CMSMicroservice.WebApi.Services; /// /// سرویس تنظیمات سیستم — مقادیر پکیجی از Package entity خوانده می‌شوند /// public class ConfigurationService : ConfigurationContract.ConfigurationContractBase { private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; private readonly IMediator _mediator; private readonly IApplicationDbContext _context; public ConfigurationService( ILogger logger, ICurrentUserService currentUserService, IMediator mediator, IApplicationDbContext context) { _logger = logger; _currentUserService = currentUserService; _mediator = mediator; _context = context; } /// /// دریافت تنظیمات با کلید خاص /// public override async Task GetConfigurationByKey(GetConfigurationByKeyRequest request, ServerCallContext context) { var response = new GetConfigurationByKeyResponse { Key = request.Key, Scope = request.Scope, IsActive = true, Created = Timestamp.FromDateTime(DateTime.UtcNow), LastModified = Timestamp.FromDateTime(DateTime.UtcNow) }; // برای کلیدهای پکیجی، از دیتابیس می‌خوانیم var package = await GetBasePackageAsync(context.CancellationToken); response.Value = GetConfigurationValue(request.Key, package); response.Description = GetConfigurationDescription(request.Key); _logger.LogDebug("Configuration requested: Key={Key}, Value={Value}", request.Key, response.Value); return response; } /// /// دریافت تنظیمات باشگاه مشتریان /// public override async Task GetClubConfiguration(Empty request, ServerCallContext context) { var package = await GetBasePackageAsync(context.CancellationToken); var response = new GetClubConfigurationResponse { ActivationFee = package.ActivationFee, MembershipGiftValue = package.ActivationFee }; _logger.LogDebug("Club configuration requested: ActivationFee={ActivationFee}, GiftValue={GiftValue}", response.ActivationFee, response.MembershipGiftValue); return response; } /// /// دریافت ویژگی‌های باشگاه مشتریان برای کاربر جاری /// public override async Task GetClubFeatures(Empty request, ServerCallContext context) { // دریافت UserId از JWT if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) { _logger.LogWarning("GetClubFeatures called without valid user authentication"); return new GetClubFeaturesResponse(); // لیست خالی برای کاربران غیر احراز هویت شده } // فراخوانی GetUserClubFeatures از طریق MediatR (CQRS داخلی) var query = new GetUserClubFeaturesQuery { UserId = userId }; var userFeatures = await _mediator.Send(query, context.CancellationToken); // تبدیل به فرمت GetClubFeaturesResponse var response = new GetClubFeaturesResponse(); foreach (var feature in userFeatures) { response.Features.Add(new ClubFeatureModel { Id = feature.ClubFeatureId, Title = feature.FeatureTitle ?? string.Empty, Description = feature.FeatureDescription ?? string.Empty, IsEnabled = feature.IsActive, DisplayOrder = feature.SortOrder, GrantedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.GrantedAt, DateTimeKind.Utc)), CreatedAt = Timestamp.FromDateTime(DateTime.SpecifyKind(feature.CreatedAt, DateTimeKind.Utc)), Notes = feature.Notes ?? string.Empty }); } _logger.LogDebug("Club features requested for user {UserId}: {Count} features returned", userId, response.Features.Count); return response; } /// /// دریافت تمام تنظیمات /// [RequiresPermission(PermissionNames.SettingsView)] public override async Task GetAllConfigurations(GetAllConfigurationsRequest request, ServerCallContext context) { var package = await GetBasePackageAsync(context.CancellationToken); var response = new GetAllConfigurationsResponse(); // Club Settings (از Package) response.Models.Add(CreateConfigModel("Club.ActivationFee", package.ActivationFee.ToString(), "هزینه فعال‌سازی عضویت باشگاه", 2)); response.Models.Add(CreateConfigModel("Club.MembershipGiftValue", package.ActivationFee.ToString(), "مبلغ هدیه حق عضویت باشگاه", 2)); // Commission Settings (سقف‌ها از Package) response.Models.Add(CreateConfigModel("Commission.MinWithdrawalAmount", SystemConstants.CommissionMinWithdrawalAmount.ToString(), "حداقل مبلغ برداشت", 3)); response.Models.Add(CreateConfigModel("Commission.MaxWeeklyBalancesPerLeg", package.MaxBalancesPerLeg.ToString(), "سقف تعادل هفتگی برای هر دست", 3)); response.Models.Add(CreateConfigModel("Commission.MaxNetworkLevel", package.MaxNetworkLevel.ToString(), "حداکثر عمق شبکه برای محاسبه کمیسیون", 3)); response.Models.Add(CreateConfigModel("Commission.CashWithdrawalEnabled", SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "امکان برداشت نقدی", 3)); response.Models.Add(CreateConfigModel("Commission.CalculationStrategy", SystemConstants.CommissionCalculationStrategy, "روش محاسبه کمیسیون", 3)); // Network Settings response.Models.Add(CreateConfigModel("Network.AllowOrphanNodes", SystemConstants.NetworkAllowOrphanNodes.ToString(), "اجازه حذف والدین با فرزند", 1)); response.Models.Add(CreateConfigModel("Network.MaxChildrenPerLeg", SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "حداکثر تعداد فرزند مستقیم در هر پا", 1)); // Package Settings (از Package) response.Models.Add(CreateConfigModel("Package.BasePackageAmount", package.Price.ToString(), "مبلغ پکیج پایه", 0)); response.Models.Add(CreateConfigModel("Package.DayaLoanAmount", package.Price.ToString(), "مبلغ وام دایا", 0)); // Magic Wallet Settings (از Package) response.Models.Add(CreateConfigModel("MagicWallet.Multiplier", package.MagicWalletMultiplier.ToString(), "ضریب شارژ کیف‌پول جادویی", 0)); response.Models.Add(CreateConfigModel("MagicWallet.MaxDeposit", package.MagicWalletMaxDeposit.ToString(), "سقف واریز هر دور جادویی", 0)); response.Models.Add(CreateConfigModel("MagicWallet.MaxCredit", package.MagicWalletMaxCredit.ToString(), "سقف اعتبار هر دور جادویی", 0)); // System Settings response.Models.Add(CreateConfigModel("System.MaintenanceMode", SystemConstants.SystemMaintenanceMode.ToString(), "حالت تعمیر و نگهداری", 0)); response.Models.Add(CreateConfigModel("System.EnableAuditLog", SystemConstants.SystemEnableAuditLog.ToString(), "فعال‌سازی لاگ تغییرات", 0)); // Shop Settings response.Models.Add(CreateConfigModel("Shop.VAT", SystemConstants.ShopVAT.ToString(), "مالیات بر ارزش افزوده", 0)); return response; } /// /// سایر عملیات‌ها که فعلاً پیاده‌سازی نشده‌اند (چون از constant استفاده می‌کنیم) /// [RequiresPermission(PermissionNames.SettingsManageConfiguration)] public override Task CreateOrUpdateConfiguration(CreateOrUpdateConfigurationRequest request, ServerCallContext context) { _logger.LogWarning("CreateOrUpdateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. تغییر از طریق کد انجام می‌شود")); } #region [ARCHIVED] DeactivateConfiguration & GetConfigurationHistory — SystemConstants ثابت هستند، این RPCها بی‌معنی [RequiresPermission(PermissionNames.SettingsManageConfiguration)] [Obsolete("ARCHIVED: SystemConstants are read-only — deactivation is meaningless")] public override Task DeactivateConfiguration(DeactivateConfigurationRequest request, ServerCallContext context) { _logger.LogWarning("[ARCHIVED] DeactivateConfiguration called but SystemConstants are read-only. Key: {Key}", request.Key); throw new RpcException(new Status(StatusCode.FailedPrecondition, "تنظیمات سیستم فقط خواندنی هستند. غیرفعال‌سازی از طریق کد انجام می‌شود")); } [Obsolete("ARCHIVED: SystemConstants have no history — always returns empty")] public override Task GetConfigurationHistory(GetConfigurationHistoryRequest request, ServerCallContext context) { // [ARCHIVED] چون constant هستند، تاریخچه‌ای وجود نداره return Task.FromResult(new GetConfigurationHistoryResponse()); } #endregion #region Private Helpers private static string GetConfigurationValue(string key, Package? package) { return key switch { "Club.ActivationFee" => (package?.ActivationFee ?? 0).ToString(), "Club.MembershipGiftValue" => (package?.ActivationFee ?? 0).ToString(), "Commission.MinWithdrawalAmount" => SystemConstants.CommissionMinWithdrawalAmount.ToString(), "Commission.MaxWeeklyBalancesPerLeg" => (package?.MaxBalancesPerLeg ?? 0).ToString(), "Commission.MaxNetworkLevel" => (package?.MaxNetworkLevel ?? 0).ToString(), "Commission.CashWithdrawalEnabled" => SystemConstants.CommissionCashWithdrawalEnabled.ToString(), "Commission.CalculationStrategy" => SystemConstants.CommissionCalculationStrategy, "Network.AllowOrphanNodes" => SystemConstants.NetworkAllowOrphanNodes.ToString(), "Network.MaxChildrenPerLeg" => SystemConstants.NetworkMaxChildrenPerLeg.ToString(), "Package.BasePackageAmount" => (package?.Price ?? 0).ToString(), "Package.DayaLoanAmount" => (package?.Price ?? 0).ToString(), "MagicWallet.Multiplier" => (package?.MagicWalletMultiplier ?? 0).ToString(), "MagicWallet.MaxDeposit" => (package?.MagicWalletMaxDeposit ?? 0).ToString(), "MagicWallet.MaxCredit" => (package?.MagicWalletMaxCredit ?? 0).ToString(), "System.MaintenanceMode" => SystemConstants.SystemMaintenanceMode.ToString(), "System.EnableAuditLog" => SystemConstants.SystemEnableAuditLog.ToString(), "Shop.VAT" => SystemConstants.ShopVAT.ToString(), _ => string.Empty }; } private async Task GetBasePackageAsync(CancellationToken cancellationToken) { return await _context.Packages .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken) ?? throw new RpcException(new Status(StatusCode.Internal, "پکیج پایه یافت نشد")); } private static string GetConfigurationDescription(string key) { return key switch { "Club.ActivationFee" => "هزینه فعال‌سازی عضویت باشگاه (تومان)", "Club.MembershipGiftValue" => "مبلغ هدیه حق عضویت باشگاه (تومان)", "Commission.MinWithdrawalAmount" => "حداقل مبلغ برداشت (تومان)", "Commission.MaxWeeklyBalancesPerLeg" => "سقف تعادل هفتگی برای هر دست", "Commission.MaxNetworkLevel" => "حداکثر عمق شبکه برای محاسبه کمیسیون", "Commission.CashWithdrawalEnabled" => "امکان برداشت نقدی", "Commission.CalculationStrategy" => "روش محاسبه کمیسیون", "Network.AllowOrphanNodes" => "اجازه حذف والدین با فرزند", "Network.MaxChildrenPerLeg" => "حداکثر تعداد فرزند مستقیم در هر پا", "Package.BasePackageAmount" => "مبلغ پکیج پایه (تومان)", "Package.DayaLoanAmount" => "مبلغ وام دایا (تومان)", "MagicWallet.Multiplier" => "ضریب شارژ کیف‌پول جادویی", "MagicWallet.MaxDeposit" => "سقف واریز هر دور جادویی (تومان)", "MagicWallet.MaxCredit" => "سقف اعتبار هر دور جادویی (تومان)", "System.MaintenanceMode" => "حالت تعمیر و نگهداری سیستم", "System.EnableAuditLog" => "فعال‌سازی لاگ تغییرات", "Shop.VAT" => "مالیات بر ارزش افزوده", _ => string.Empty }; } private static ConfigurationModel CreateConfigModel(string key, string value, string description, int scope) { return new ConfigurationModel { Id = key.GetHashCode(), Key = key, Value = value, Description = description, Scope = scope, IsActive = true, Created = Timestamp.FromDateTime(DateTime.UtcNow) }; } #endregion }